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 simple_quote_literal(StringInfo buf, const char *val);
508 : : static void get_sublink_expr(SubLink *sublink, deparse_context *context);
509 : : static void get_tablefunc(TableFunc *tf, deparse_context *context,
510 : : bool showimplicit);
511 : : static void get_from_clause(Query *query, const char *prefix,
512 : : deparse_context *context);
513 : : static void get_from_clause_item(Node *jtnode, Query *query,
514 : : deparse_context *context);
515 : : static void get_rte_alias(RangeTblEntry *rte, int varno, bool use_as,
516 : : deparse_context *context);
517 : : static void get_column_alias_list(deparse_columns *colinfo,
518 : : deparse_context *context);
519 : : static void get_from_clause_coldeflist(RangeTblFunction *rtfunc,
520 : : deparse_columns *colinfo,
521 : : deparse_context *context);
522 : : static void get_tablesample_def(TableSampleClause *tablesample,
523 : : deparse_context *context);
524 : : static void get_opclass_name(Oid opclass, Oid actual_datatype,
525 : : StringInfo buf);
526 : : static Node *processIndirection(Node *node, deparse_context *context);
527 : : static void printSubscripts(SubscriptingRef *sbsref, deparse_context *context);
528 : : static char *get_relation_name(Oid relid);
529 : : static char *generate_relation_name(Oid relid, List *namespaces);
530 : : static char *generate_qualified_relation_name(Oid relid);
531 : : static char *generate_function_name(Oid funcid, int nargs,
532 : : List *argnames, Oid *argtypes,
533 : : bool has_variadic, bool *use_variadic_p,
534 : : bool inGroupBy);
535 : : static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
536 : : static void add_cast_to(StringInfo buf, Oid typid);
537 : : static char *generate_qualified_type_name(Oid typid);
538 : : static text *string_to_text(char *str);
539 : : static char *flatten_reloptions(Oid relid);
540 : : static void get_reloptions(StringInfo buf, Datum reloptions);
541 : : static void get_json_path_spec(Node *path_spec, deparse_context *context,
542 : : bool showimplicit);
543 : : static void get_json_table_columns(TableFunc *tf, JsonTablePathScan *scan,
544 : : deparse_context *context,
545 : : bool showimplicit);
546 : : static void get_json_table_nested_columns(TableFunc *tf, JsonTablePlan *plan,
547 : : deparse_context *context,
548 : : bool showimplicit,
549 : : bool needcomma);
550 : :
551 : : #define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
552 : :
553 : :
554 : : /* ----------
555 : : * pg_get_ruledef - Do it all and return a text
556 : : * that could be used as a statement
557 : : * to recreate the rule
558 : : * ----------
559 : : */
560 : : Datum
9383 tgl@sss.pgh.pa.us 561 :CBC 228 : pg_get_ruledef(PG_FUNCTION_ARGS)
562 : : {
8732 563 : 228 : Oid ruleoid = PG_GETARG_OID(0);
564 : : int prettyFlags;
565 : : char *res;
566 : :
4788 567 : 228 : prettyFlags = PRETTYFLAG_INDENT;
568 : :
3519 rhaas@postgresql.org 569 : 228 : res = pg_get_ruledef_worker(ruleoid, prettyFlags);
570 : :
571 [ + + ]: 228 : if (res == NULL)
572 : 3 : PG_RETURN_NULL();
573 : :
574 : 225 : PG_RETURN_TEXT_P(string_to_text(res));
575 : : }
576 : :
577 : :
578 : : Datum
8264 tgl@sss.pgh.pa.us 579 : 57 : pg_get_ruledef_ext(PG_FUNCTION_ARGS)
580 : : {
581 : 57 : Oid ruleoid = PG_GETARG_OID(0);
582 : 57 : bool pretty = PG_GETARG_BOOL(1);
583 : : int prettyFlags;
584 : : char *res;
585 : :
1448 586 [ + - ]: 57 : prettyFlags = GET_PRETTY_FLAGS(pretty);
587 : :
3519 rhaas@postgresql.org 588 : 57 : res = pg_get_ruledef_worker(ruleoid, prettyFlags);
589 : :
590 [ - + ]: 57 : if (res == NULL)
3519 rhaas@postgresql.org 591 :UBC 0 : PG_RETURN_NULL();
592 : :
3519 rhaas@postgresql.org 593 :CBC 57 : PG_RETURN_TEXT_P(string_to_text(res));
594 : : }
595 : :
596 : :
597 : : static char *
8264 tgl@sss.pgh.pa.us 598 : 285 : pg_get_ruledef_worker(Oid ruleoid, int prettyFlags)
599 : : {
600 : : Datum args[1];
601 : : char nulls[1];
602 : : int spirc;
603 : : HeapTuple ruletup;
604 : : TupleDesc rulettc;
605 : : StringInfoData buf;
606 : :
607 : : /*
608 : : * Do this first so that string is alloc'd in outer context not SPI's.
609 : : */
7984 610 : 285 : initStringInfo(&buf);
611 : :
612 : : /*
613 : : * Connect to SPI manager
614 : : */
552 615 : 285 : SPI_connect();
616 : :
617 : : /*
618 : : * On the first call prepare the plan to lookup pg_rewrite. We read
619 : : * pg_rewrite over the SPI manager instead of using the syscache to be
620 : : * checked for read access on pg_rewrite.
621 : : */
8732 622 [ + + ]: 285 : if (plan_getrulebyoid == NULL)
623 : : {
624 : : Oid argtypes[1];
625 : : SPIPlanPtr plan;
626 : :
627 : 20 : argtypes[0] = OIDOID;
628 : 20 : plan = SPI_prepare(query_getrulebyoid, 1, argtypes);
10057 bruce@momjian.us 629 [ - + ]: 20 : if (plan == NULL)
8267 tgl@sss.pgh.pa.us 630 [ # # ]:UBC 0 : elog(ERROR, "SPI_prepare failed for \"%s\"", query_getrulebyoid);
5294 tgl@sss.pgh.pa.us 631 :CBC 20 : SPI_keepplan(plan);
632 : 20 : plan_getrulebyoid = plan;
633 : : }
634 : :
635 : : /*
636 : : * Get the pg_rewrite tuple for this rule
637 : : */
8732 638 : 285 : args[0] = ObjectIdGetDatum(ruleoid);
639 : 285 : nulls[0] = ' ';
4495 peter_e@gmx.net 640 : 285 : spirc = SPI_execute_plan(plan_getrulebyoid, args, nulls, true, 0);
10057 bruce@momjian.us 641 [ - + ]: 285 : if (spirc != SPI_OK_SELECT)
8267 tgl@sss.pgh.pa.us 642 [ # # ]:UBC 0 : elog(ERROR, "failed to get pg_rewrite tuple for rule %u", ruleoid);
10057 bruce@momjian.us 643 [ + + ]:CBC 285 : if (SPI_processed != 1)
644 : : {
645 : : /*
646 : : * There is no tuple data available here, just keep the output buffer
647 : : * empty.
648 : : */
649 : : }
650 : : else
651 : : {
652 : : /*
653 : : * Get the rule's definition and put it into executor's memory
654 : : */
7984 tgl@sss.pgh.pa.us 655 : 282 : ruletup = SPI_tuptable->vals[0];
656 : 282 : rulettc = SPI_tuptable->tupdesc;
657 : 282 : make_ruledef(&buf, ruletup, rulettc, prettyFlags);
658 : : }
659 : :
660 : : /*
661 : : * Disconnect from SPI manager
662 : : */
10057 bruce@momjian.us 663 [ - + ]: 285 : if (SPI_finish() != SPI_OK_FINISH)
8267 tgl@sss.pgh.pa.us 664 [ # # ]:UBC 0 : elog(ERROR, "SPI_finish failed");
665 : :
3519 rhaas@postgresql.org 666 [ + + ]:CBC 285 : if (buf.len == 0)
667 : 3 : return NULL;
668 : :
7984 tgl@sss.pgh.pa.us 669 : 282 : return buf.data;
670 : : }
671 : :
672 : :
673 : : /* ----------
674 : : * pg_get_viewdef - Mainly the same thing, but we
675 : : * only return the SELECT part of a view
676 : : * ----------
677 : : */
678 : : Datum
9383 679 : 1252 : pg_get_viewdef(PG_FUNCTION_ARGS)
680 : : {
681 : : /* By OID */
8732 682 : 1252 : Oid viewoid = PG_GETARG_OID(0);
683 : : int prettyFlags;
684 : : char *res;
685 : :
4788 686 : 1252 : prettyFlags = PRETTYFLAG_INDENT;
687 : :
3519 rhaas@postgresql.org 688 : 1252 : res = pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT);
689 : :
690 [ + + ]: 1252 : if (res == NULL)
691 : 3 : PG_RETURN_NULL();
692 : :
693 : 1249 : PG_RETURN_TEXT_P(string_to_text(res));
694 : : }
695 : :
696 : :
697 : : Datum
8264 tgl@sss.pgh.pa.us 698 : 282 : pg_get_viewdef_ext(PG_FUNCTION_ARGS)
699 : : {
700 : : /* By OID */
701 : 282 : Oid viewoid = PG_GETARG_OID(0);
702 : 282 : bool pretty = PG_GETARG_BOOL(1);
703 : : int prettyFlags;
704 : : char *res;
705 : :
1448 706 [ + - ]: 282 : prettyFlags = GET_PRETTY_FLAGS(pretty);
707 : :
3519 rhaas@postgresql.org 708 : 282 : res = pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT);
709 : :
710 [ - + ]: 282 : if (res == NULL)
3519 rhaas@postgresql.org 711 :UBC 0 : PG_RETURN_NULL();
712 : :
3519 rhaas@postgresql.org 713 :CBC 282 : PG_RETURN_TEXT_P(string_to_text(res));
714 : : }
715 : :
716 : : Datum
5138 andrew@dunslane.net 717 : 3 : pg_get_viewdef_wrap(PG_FUNCTION_ARGS)
718 : : {
719 : : /* By OID */
720 : 3 : Oid viewoid = PG_GETARG_OID(0);
5026 bruce@momjian.us 721 : 3 : int wrap = PG_GETARG_INT32(1);
722 : : int prettyFlags;
723 : : char *res;
724 : :
725 : : /* calling this implies we want pretty printing */
1448 tgl@sss.pgh.pa.us 726 : 3 : prettyFlags = GET_PRETTY_FLAGS(true);
727 : :
3519 rhaas@postgresql.org 728 : 3 : res = pg_get_viewdef_worker(viewoid, prettyFlags, wrap);
729 : :
730 [ - + ]: 3 : if (res == NULL)
3519 rhaas@postgresql.org 731 :UBC 0 : PG_RETURN_NULL();
732 : :
3519 rhaas@postgresql.org 733 :CBC 3 : PG_RETURN_TEXT_P(string_to_text(res));
734 : : }
735 : :
736 : : Datum
8732 tgl@sss.pgh.pa.us 737 : 39 : pg_get_viewdef_name(PG_FUNCTION_ARGS)
738 : : {
739 : : /* By qualified name */
3290 noah@leadboat.com 740 : 39 : text *viewname = PG_GETARG_TEXT_PP(0);
741 : : int prettyFlags;
742 : : RangeVar *viewrel;
743 : : Oid viewoid;
744 : : char *res;
745 : :
4788 tgl@sss.pgh.pa.us 746 : 39 : prettyFlags = PRETTYFLAG_INDENT;
747 : :
748 : : /* Look up view name. Can't lock it - we might not have privileges. */
7597 neilc@samurai.com 749 : 39 : viewrel = makeRangeVarFromNameList(textToQualifiedNameList(viewname));
5219 rhaas@postgresql.org 750 : 39 : viewoid = RangeVarGetRelid(viewrel, NoLock, false);
751 : :
3519 752 : 39 : res = pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT);
753 : :
754 [ - + ]: 39 : if (res == NULL)
3519 rhaas@postgresql.org 755 :UBC 0 : PG_RETURN_NULL();
756 : :
3519 rhaas@postgresql.org 757 :CBC 39 : PG_RETURN_TEXT_P(string_to_text(res));
758 : : }
759 : :
760 : :
761 : : Datum
8264 tgl@sss.pgh.pa.us 762 : 201 : pg_get_viewdef_name_ext(PG_FUNCTION_ARGS)
763 : : {
764 : : /* By qualified name */
3290 noah@leadboat.com 765 : 201 : text *viewname = PG_GETARG_TEXT_PP(0);
8264 tgl@sss.pgh.pa.us 766 : 201 : bool pretty = PG_GETARG_BOOL(1);
767 : : int prettyFlags;
768 : : RangeVar *viewrel;
769 : : Oid viewoid;
770 : : char *res;
771 : :
1448 772 [ + - ]: 201 : prettyFlags = GET_PRETTY_FLAGS(pretty);
773 : :
774 : : /* Look up view name. Can't lock it - we might not have privileges. */
7597 neilc@samurai.com 775 : 201 : viewrel = makeRangeVarFromNameList(textToQualifiedNameList(viewname));
5219 rhaas@postgresql.org 776 : 201 : viewoid = RangeVarGetRelid(viewrel, NoLock, false);
777 : :
3507 tgl@sss.pgh.pa.us 778 : 201 : res = pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT);
779 : :
780 [ - + ]: 201 : if (res == NULL)
3507 tgl@sss.pgh.pa.us 781 :UBC 0 : PG_RETURN_NULL();
782 : :
3507 tgl@sss.pgh.pa.us 783 :CBC 201 : PG_RETURN_TEXT_P(string_to_text(res));
784 : : }
785 : :
786 : : /*
787 : : * Common code for by-OID and by-name variants of pg_get_viewdef
788 : : */
789 : : static char *
4829 790 : 1777 : pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn)
791 : : {
792 : : Datum args[2];
793 : : char nulls[2];
794 : : int spirc;
795 : : HeapTuple ruletup;
796 : : TupleDesc rulettc;
797 : : StringInfoData buf;
798 : :
799 : : /*
800 : : * Do this first so that string is alloc'd in outer context not SPI's.
801 : : */
7984 802 : 1777 : initStringInfo(&buf);
803 : :
804 : : /*
805 : : * Connect to SPI manager
806 : : */
552 807 : 1777 : SPI_connect();
808 : :
809 : : /*
810 : : * On the first call prepare the plan to lookup pg_rewrite. We read
811 : : * pg_rewrite over the SPI manager instead of using the syscache to be
812 : : * checked for read access on pg_rewrite.
813 : : */
8732 814 [ + + ]: 1777 : if (plan_getviewrule == NULL)
815 : : {
816 : : Oid argtypes[2];
817 : : SPIPlanPtr plan;
818 : :
819 : 127 : argtypes[0] = OIDOID;
820 : 127 : argtypes[1] = NAMEOID;
821 : 127 : plan = SPI_prepare(query_getviewrule, 2, argtypes);
10057 bruce@momjian.us 822 [ - + ]: 127 : if (plan == NULL)
8267 tgl@sss.pgh.pa.us 823 [ # # ]:UBC 0 : elog(ERROR, "SPI_prepare failed for \"%s\"", query_getviewrule);
5294 tgl@sss.pgh.pa.us 824 :CBC 127 : SPI_keepplan(plan);
825 : 127 : plan_getviewrule = plan;
826 : : }
827 : :
828 : : /*
829 : : * Get the pg_rewrite tuple for the view's SELECT rule
830 : : */
8732 831 : 1777 : args[0] = ObjectIdGetDatum(viewoid);
4495 peter_e@gmx.net 832 : 1777 : args[1] = DirectFunctionCall1(namein, CStringGetDatum(ViewSelectRuleName));
10057 bruce@momjian.us 833 : 1777 : nulls[0] = ' ';
8732 tgl@sss.pgh.pa.us 834 : 1777 : nulls[1] = ' ';
4495 peter_e@gmx.net 835 : 1777 : spirc = SPI_execute_plan(plan_getviewrule, args, nulls, true, 0);
10057 bruce@momjian.us 836 [ - + ]: 1777 : if (spirc != SPI_OK_SELECT)
8717 tgl@sss.pgh.pa.us 837 [ # # ]:UBC 0 : elog(ERROR, "failed to get pg_rewrite tuple for view %u", viewoid);
10057 bruce@momjian.us 838 [ + + ]:CBC 1777 : if (SPI_processed != 1)
839 : : {
840 : : /*
841 : : * There is no tuple data available here, just keep the output buffer
842 : : * empty.
843 : : */
844 : : }
845 : : else
846 : : {
847 : : /*
848 : : * Get the rule's definition and put it into executor's memory
849 : : */
850 : 1774 : ruletup = SPI_tuptable->vals[0];
851 : 1774 : rulettc = SPI_tuptable->tupdesc;
4829 tgl@sss.pgh.pa.us 852 : 1774 : make_viewdef(&buf, ruletup, rulettc, prettyFlags, wrapColumn);
853 : : }
854 : :
855 : : /*
856 : : * Disconnect from SPI manager
857 : : */
10057 bruce@momjian.us 858 [ - + ]: 1777 : if (SPI_finish() != SPI_OK_FINISH)
8267 tgl@sss.pgh.pa.us 859 [ # # ]:UBC 0 : elog(ERROR, "SPI_finish failed");
860 : :
3519 rhaas@postgresql.org 861 [ + + ]:CBC 1777 : if (buf.len == 0)
862 : 3 : return NULL;
863 : :
7984 tgl@sss.pgh.pa.us 864 : 1774 : return buf.data;
865 : : }
866 : :
867 : : /* ----------
868 : : * pg_get_triggerdef - Get the definition of a trigger
869 : : * ----------
870 : : */
871 : : Datum
8396 bruce@momjian.us 872 : 82 : pg_get_triggerdef(PG_FUNCTION_ARGS)
873 : : {
874 : 82 : Oid trigid = PG_GETARG_OID(0);
875 : : char *res;
876 : :
3519 rhaas@postgresql.org 877 : 82 : res = pg_get_triggerdef_worker(trigid, false);
878 : :
879 [ + + ]: 82 : if (res == NULL)
880 : 3 : PG_RETURN_NULL();
881 : :
882 : 79 : PG_RETURN_TEXT_P(string_to_text(res));
883 : : }
884 : :
885 : : Datum
6001 peter_e@gmx.net 886 : 613 : pg_get_triggerdef_ext(PG_FUNCTION_ARGS)
887 : : {
888 : 613 : Oid trigid = PG_GETARG_OID(0);
889 : 613 : bool pretty = PG_GETARG_BOOL(1);
890 : : char *res;
891 : :
3519 rhaas@postgresql.org 892 : 613 : res = pg_get_triggerdef_worker(trigid, pretty);
893 : :
894 [ - + ]: 613 : if (res == NULL)
3519 rhaas@postgresql.org 895 :UBC 0 : PG_RETURN_NULL();
896 : :
3519 rhaas@postgresql.org 897 :CBC 613 : PG_RETURN_TEXT_P(string_to_text(res));
898 : : }
899 : :
900 : : static char *
6001 peter_e@gmx.net 901 : 695 : pg_get_triggerdef_worker(Oid trigid, bool pretty)
902 : : {
903 : : HeapTuple ht_trig;
904 : : Form_pg_trigger trigrec;
905 : : StringInfoData buf;
906 : : Relation tgrel;
907 : : ScanKeyData skey[1];
908 : : SysScanDesc tgscan;
8335 tgl@sss.pgh.pa.us 909 : 695 : int findx = 0;
910 : : char *tgname;
911 : : char *tgoldtable;
912 : : char *tgnewtable;
913 : : Datum value;
914 : : bool isnull;
915 : :
916 : : /*
917 : : * Fetch the pg_trigger tuple by the Oid of the trigger
918 : : */
2610 andres@anarazel.de 919 : 695 : tgrel = table_open(TriggerRelationId, AccessShareLock);
920 : :
8159 tgl@sss.pgh.pa.us 921 : 695 : ScanKeyInit(&skey[0],
922 : : Anum_pg_trigger_oid,
923 : : BTEqualStrategyNumber, F_OIDEQ,
924 : : ObjectIdGetDatum(trigid));
925 : :
7640 926 : 695 : tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true,
927 : : NULL, 1, skey);
928 : :
8335 929 : 695 : ht_trig = systable_getnext(tgscan);
930 : :
931 [ + + ]: 695 : if (!HeapTupleIsValid(ht_trig))
932 : : {
3519 rhaas@postgresql.org 933 : 3 : systable_endscan(tgscan);
2610 andres@anarazel.de 934 : 3 : table_close(tgrel, AccessShareLock);
3519 rhaas@postgresql.org 935 : 3 : return NULL;
936 : : }
937 : :
8396 bruce@momjian.us 938 : 692 : trigrec = (Form_pg_trigger) GETSTRUCT(ht_trig);
939 : :
940 : : /*
941 : : * Start the trigger definition. Note that the trigger's name should never
942 : : * be schema-qualified, but the trigger rel's name may be.
943 : : */
944 : 692 : initStringInfo(&buf);
945 : :
946 : 692 : tgname = NameStr(trigrec->tgname);
5897 itagaki.takahiro@gma 947 : 1384 : appendStringInfo(&buf, "CREATE %sTRIGGER %s ",
5901 tgl@sss.pgh.pa.us 948 [ - + ]: 692 : OidIsValid(trigrec->tgconstraint) ? "CONSTRAINT " : "",
949 : : quote_identifier(tgname));
950 : :
8396 bruce@momjian.us 951 [ + + ]: 692 : if (TRIGGER_FOR_BEFORE(trigrec->tgtype))
4518 rhaas@postgresql.org 952 : 265 : appendStringInfoString(&buf, "BEFORE");
5635 tgl@sss.pgh.pa.us 953 [ + + ]: 427 : else if (TRIGGER_FOR_AFTER(trigrec->tgtype))
4518 rhaas@postgresql.org 954 : 415 : appendStringInfoString(&buf, "AFTER");
5635 tgl@sss.pgh.pa.us 955 [ + - ]: 12 : else if (TRIGGER_FOR_INSTEAD(trigrec->tgtype))
4518 rhaas@postgresql.org 956 : 12 : appendStringInfoString(&buf, "INSTEAD OF");
957 : : else
5635 tgl@sss.pgh.pa.us 958 [ # # ]:UBC 0 : elog(ERROR, "unexpected tgtype value: %d", trigrec->tgtype);
959 : :
8396 bruce@momjian.us 960 [ + + ]:CBC 692 : if (TRIGGER_FOR_INSERT(trigrec->tgtype))
961 : : {
4518 rhaas@postgresql.org 962 : 483 : appendStringInfoString(&buf, " INSERT");
8396 bruce@momjian.us 963 : 483 : findx++;
964 : : }
965 [ + + ]: 692 : if (TRIGGER_FOR_DELETE(trigrec->tgtype))
966 : : {
967 [ + + ]: 105 : if (findx > 0)
4518 rhaas@postgresql.org 968 : 45 : appendStringInfoString(&buf, " OR DELETE");
969 : : else
970 : 60 : appendStringInfoString(&buf, " DELETE");
8396 bruce@momjian.us 971 : 105 : findx++;
972 : : }
973 [ + + ]: 692 : if (TRIGGER_FOR_UPDATE(trigrec->tgtype))
974 : : {
975 [ + + ]: 324 : if (findx > 0)
4518 rhaas@postgresql.org 976 : 175 : appendStringInfoString(&buf, " OR UPDATE");
977 : : else
978 : 149 : appendStringInfoString(&buf, " UPDATE");
5959 tgl@sss.pgh.pa.us 979 : 324 : findx++;
980 : : /* tgattr is first var-width field, so OK to access directly */
5996 981 [ + + ]: 324 : if (trigrec->tgattr.dim1 > 0)
982 : : {
983 : : int i;
984 : :
985 : 38 : appendStringInfoString(&buf, " OF ");
986 [ + + ]: 84 : for (i = 0; i < trigrec->tgattr.dim1; i++)
987 : : {
988 : : char *attname;
989 : :
990 [ + + ]: 46 : if (i > 0)
991 : 8 : appendStringInfoString(&buf, ", ");
2953 alvherre@alvh.no-ip. 992 : 46 : attname = get_attname(trigrec->tgrelid,
993 : 46 : trigrec->tgattr.values[i], false);
5996 tgl@sss.pgh.pa.us 994 : 46 : appendStringInfoString(&buf, quote_identifier(attname));
995 : : }
996 : : }
997 : : }
6561 998 [ - + ]: 692 : if (TRIGGER_FOR_TRUNCATE(trigrec->tgtype))
999 : : {
6561 tgl@sss.pgh.pa.us 1000 [ # # ]:UBC 0 : if (findx > 0)
4518 rhaas@postgresql.org 1001 : 0 : appendStringInfoString(&buf, " OR TRUNCATE");
1002 : : else
1003 : 0 : appendStringInfoString(&buf, " TRUNCATE");
5959 tgl@sss.pgh.pa.us 1004 : 0 : findx++;
1005 : : }
1006 : :
1007 : : /*
1008 : : * In non-pretty mode, always schema-qualify the target table name for
1009 : : * safety. In pretty mode, schema-qualify only if not visible.
1010 : : */
5897 itagaki.takahiro@gma 1011 [ + + ]:CBC 1384 : appendStringInfo(&buf, " ON %s ",
1012 : : pretty ?
2939 tgl@sss.pgh.pa.us 1013 : 87 : generate_relation_name(trigrec->tgrelid, NIL) :
1014 : 605 : generate_qualified_relation_name(trigrec->tgrelid));
1015 : :
5901 1016 [ - + ]: 692 : if (OidIsValid(trigrec->tgconstraint))
1017 : : {
5959 tgl@sss.pgh.pa.us 1018 [ # # ]:UBC 0 : if (OidIsValid(trigrec->tgconstrrelid))
5897 itagaki.takahiro@gma 1019 : 0 : appendStringInfo(&buf, "FROM %s ",
1020 : : generate_relation_name(trigrec->tgconstrrelid, NIL));
8396 bruce@momjian.us 1021 [ # # ]: 0 : if (!trigrec->tgdeferrable)
4518 rhaas@postgresql.org 1022 : 0 : appendStringInfoString(&buf, "NOT ");
1023 : 0 : appendStringInfoString(&buf, "DEFERRABLE INITIALLY ");
8396 bruce@momjian.us 1024 [ # # ]: 0 : if (trigrec->tginitdeferred)
4518 rhaas@postgresql.org 1025 : 0 : appendStringInfoString(&buf, "DEFERRED ");
1026 : : else
1027 : 0 : appendStringInfoString(&buf, "IMMEDIATE ");
1028 : : }
1029 : :
3418 kgrittn@postgresql.o 1030 :CBC 692 : value = fastgetattr(ht_trig, Anum_pg_trigger_tgoldtable,
1031 : : tgrel->rd_att, &isnull);
1032 [ + + ]: 692 : if (!isnull)
2704 tgl@sss.pgh.pa.us 1033 : 49 : tgoldtable = NameStr(*DatumGetName(value));
1034 : : else
3418 kgrittn@postgresql.o 1035 : 643 : tgoldtable = NULL;
1036 : 692 : value = fastgetattr(ht_trig, Anum_pg_trigger_tgnewtable,
1037 : : tgrel->rd_att, &isnull);
1038 [ + + ]: 692 : if (!isnull)
2704 tgl@sss.pgh.pa.us 1039 : 54 : tgnewtable = NameStr(*DatumGetName(value));
1040 : : else
3418 kgrittn@postgresql.o 1041 : 638 : tgnewtable = NULL;
1042 [ + + + + ]: 692 : if (tgoldtable != NULL || tgnewtable != NULL)
1043 : : {
1044 : 76 : appendStringInfoString(&buf, "REFERENCING ");
1045 [ + + ]: 76 : if (tgoldtable != NULL)
2704 tgl@sss.pgh.pa.us 1046 : 49 : appendStringInfo(&buf, "OLD TABLE AS %s ",
1047 : : quote_identifier(tgoldtable));
3418 kgrittn@postgresql.o 1048 [ + + ]: 76 : if (tgnewtable != NULL)
2704 tgl@sss.pgh.pa.us 1049 : 54 : appendStringInfo(&buf, "NEW TABLE AS %s ",
1050 : : quote_identifier(tgnewtable));
1051 : : }
1052 : :
8396 bruce@momjian.us 1053 [ + + ]: 692 : if (TRIGGER_FOR_ROW(trigrec->tgtype))
4518 rhaas@postgresql.org 1054 : 533 : appendStringInfoString(&buf, "FOR EACH ROW ");
1055 : : else
1056 : 159 : appendStringInfoString(&buf, "FOR EACH STATEMENT ");
1057 : :
1058 : : /* If the trigger has a WHEN qualification, add that */
5959 tgl@sss.pgh.pa.us 1059 : 692 : value = fastgetattr(ht_trig, Anum_pg_trigger_tgqual,
1060 : : tgrel->rd_att, &isnull);
1061 [ + + ]: 692 : if (!isnull)
1062 : : {
1063 : : Node *qual;
1064 : : char relkind;
1065 : : deparse_context context;
1066 : : deparse_namespace dpns;
1067 : : RangeTblEntry *oldrte;
1068 : : RangeTblEntry *newrte;
1069 : :
1070 : 76 : appendStringInfoString(&buf, "WHEN (");
1071 : :
1072 : 76 : qual = stringToNode(TextDatumGetCString(value));
1073 : :
5500 1074 : 76 : relkind = get_rel_relkind(trigrec->tgrelid);
1075 : :
1076 : : /* Build minimal OLD and NEW RTEs for the rel */
5959 1077 : 76 : oldrte = makeNode(RangeTblEntry);
1078 : 76 : oldrte->rtekind = RTE_RELATION;
1079 : 76 : oldrte->relid = trigrec->tgrelid;
5500 1080 : 76 : oldrte->relkind = relkind;
2723 1081 : 76 : oldrte->rellockmode = AccessShareLock;
4923 1082 : 76 : oldrte->alias = makeAlias("old", NIL);
1083 : 76 : oldrte->eref = oldrte->alias;
4968 1084 : 76 : oldrte->lateral = false;
5959 1085 : 76 : oldrte->inh = false;
1086 : 76 : oldrte->inFromCl = true;
1087 : :
1088 : 76 : newrte = makeNode(RangeTblEntry);
1089 : 76 : newrte->rtekind = RTE_RELATION;
1090 : 76 : newrte->relid = trigrec->tgrelid;
5500 1091 : 76 : newrte->relkind = relkind;
2723 1092 : 76 : newrte->rellockmode = AccessShareLock;
4923 1093 : 76 : newrte->alias = makeAlias("new", NIL);
1094 : 76 : newrte->eref = newrte->alias;
4968 1095 : 76 : newrte->lateral = false;
5959 1096 : 76 : newrte->inh = false;
1097 : 76 : newrte->inFromCl = true;
1098 : :
1099 : : /* Build two-element rtable */
5724 1100 : 76 : memset(&dpns, 0, sizeof(dpns));
5959 1101 : 76 : dpns.rtable = list_make2(oldrte, newrte);
2286 1102 : 76 : dpns.subplans = NIL;
5959 1103 : 76 : dpns.ctes = NIL;
2286 1104 : 76 : dpns.appendrels = NULL;
4923 1105 : 76 : set_rtable_names(&dpns, NIL, NULL);
4822 1106 : 76 : set_simple_column_names(&dpns);
1107 : :
1108 : : /* Set up context with one-deep namespace stack */
5959 1109 : 76 : context.buf = &buf;
1110 : 76 : context.namespaces = list_make1(&dpns);
563 1111 : 76 : context.resultDesc = NULL;
1112 : 76 : context.targetList = NIL;
5959 1113 : 76 : context.windowClause = NIL;
1114 : 76 : context.varprefix = true;
1448 1115 [ + + ]: 76 : context.prettyFlags = GET_PRETTY_FLAGS(pretty);
4829 1116 : 76 : context.wrapColumn = WRAP_COLUMN_DEFAULT;
5959 1117 : 76 : context.indentLevel = PRETTYINDENT_STD;
563 1118 : 76 : context.colNamesVisible = true;
1119 : 76 : context.inGroupBy = false;
1120 : 76 : context.varInOrderBy = false;
2286 1121 : 76 : context.appendparents = NULL;
1122 : :
5959 1123 : 76 : get_rule_expr(qual, &context, false);
1124 : :
4518 rhaas@postgresql.org 1125 : 76 : appendStringInfoString(&buf, ") ");
1126 : : }
1127 : :
2593 peter@eisentraut.org 1128 : 692 : appendStringInfo(&buf, "EXECUTE FUNCTION %s(",
1129 : : generate_function_name(trigrec->tgfoid, 0,
1130 : : NIL, NULL,
1131 : : false, NULL, false));
1132 : :
8335 tgl@sss.pgh.pa.us 1133 [ + + ]: 692 : if (trigrec->tgnargs > 0)
1134 : : {
1135 : : char *p;
1136 : : int i;
1137 : :
5959 1138 : 223 : value = fastgetattr(ht_trig, Anum_pg_trigger_tgargs,
1139 : : tgrel->rd_att, &isnull);
8335 1140 [ - + ]: 223 : if (isnull)
8335 tgl@sss.pgh.pa.us 1141 [ # # ]:UBC 0 : elog(ERROR, "tgargs is null for trigger %u", trigid);
3290 noah@leadboat.com 1142 [ + - ]:CBC 223 : p = (char *) VARDATA_ANY(DatumGetByteaPP(value));
8335 tgl@sss.pgh.pa.us 1143 [ + + ]: 502 : for (i = 0; i < trigrec->tgnargs; i++)
1144 : : {
1145 [ + + ]: 279 : if (i > 0)
4518 rhaas@postgresql.org 1146 : 56 : appendStringInfoString(&buf, ", ");
6399 tgl@sss.pgh.pa.us 1147 : 279 : simple_quote_literal(&buf, p);
1148 : : /* advance p to next string embedded in tgargs */
1149 [ + + ]: 2746 : while (*p)
1150 : 2467 : p++;
7231 1151 : 279 : p++;
1152 : : }
1153 : : }
1154 : :
1155 : : /* We deliberately do not put semi-colon at end */
4518 rhaas@postgresql.org 1156 : 692 : appendStringInfoChar(&buf, ')');
1157 : :
1158 : : /* Clean up */
8335 tgl@sss.pgh.pa.us 1159 : 692 : systable_endscan(tgscan);
1160 : :
2610 andres@anarazel.de 1161 : 692 : table_close(tgrel, AccessShareLock);
1162 : :
6001 peter_e@gmx.net 1163 : 692 : return buf.data;
1164 : : }
1165 : :
1166 : : /* ----------
1167 : : * pg_get_indexdef - Get the definition of an index
1168 : : *
1169 : : * In the extended version, there is a colno argument as well as pretty bool.
1170 : : * if colno == 0, we want a complete index definition.
1171 : : * if colno > 0, we only want the Nth index key's variable or expression.
1172 : : *
1173 : : * Note that the SQL-function versions of this omit any info about the
1174 : : * index tablespace; this is intentional because pg_dump wants it that way.
1175 : : * However pg_get_indexdef_string() includes the index tablespace.
1176 : : * ----------
1177 : : */
1178 : : Datum
9410 tgl@sss.pgh.pa.us 1179 : 2855 : pg_get_indexdef(PG_FUNCTION_ARGS)
1180 : : {
1181 : 2855 : Oid indexrelid = PG_GETARG_OID(0);
1182 : : int prettyFlags;
1183 : : char *res;
1184 : :
4788 1185 : 2855 : prettyFlags = PRETTYFLAG_INDENT;
1186 : :
2796 1187 : 2855 : res = pg_get_indexdef_worker(indexrelid, 0, NULL,
1188 : : false, false,
1189 : : false, false,
1190 : : prettyFlags, true);
1191 : :
3519 rhaas@postgresql.org 1192 [ + + ]: 2855 : if (res == NULL)
1193 : 3 : PG_RETURN_NULL();
1194 : :
1195 : 2852 : PG_RETURN_TEXT_P(string_to_text(res));
1196 : : }
1197 : :
1198 : : Datum
8264 tgl@sss.pgh.pa.us 1199 : 1015 : pg_get_indexdef_ext(PG_FUNCTION_ARGS)
1200 : : {
1201 : 1015 : Oid indexrelid = PG_GETARG_OID(0);
8259 bruce@momjian.us 1202 : 1015 : int32 colno = PG_GETARG_INT32(1);
8264 tgl@sss.pgh.pa.us 1203 : 1015 : bool pretty = PG_GETARG_BOOL(2);
1204 : : int prettyFlags;
1205 : : char *res;
1206 : :
1448 1207 [ + - ]: 1015 : prettyFlags = GET_PRETTY_FLAGS(pretty);
1208 : :
2796 1209 : 1015 : res = pg_get_indexdef_worker(indexrelid, colno, NULL,
1210 : : colno != 0, false,
1211 : : false, false,
1212 : : prettyFlags, true);
1213 : :
3519 rhaas@postgresql.org 1214 [ - + ]: 1015 : if (res == NULL)
3519 rhaas@postgresql.org 1215 :UBC 0 : PG_RETURN_NULL();
1216 : :
3519 rhaas@postgresql.org 1217 :CBC 1015 : PG_RETURN_TEXT_P(string_to_text(res));
1218 : : }
1219 : :
1220 : : /*
1221 : : * Internal version for use by ALTER TABLE.
1222 : : * Includes a tablespace clause in the result.
1223 : : * Returns a palloc'd C string; no pretty-printing.
1224 : : */
1225 : : char *
7984 tgl@sss.pgh.pa.us 1226 : 117 : pg_get_indexdef_string(Oid indexrelid)
1227 : : {
2796 1228 : 117 : return pg_get_indexdef_worker(indexrelid, 0, NULL,
1229 : : false, false,
1230 : : true, true,
1231 : : 0, false);
1232 : : }
1233 : :
1234 : : /* Internal version that just reports the key-column definitions */
1235 : : char *
6070 1236 : 546 : pg_get_indexdef_columns(Oid indexrelid, bool pretty)
1237 : : {
1238 : : int prettyFlags;
1239 : :
1448 1240 [ + - ]: 546 : prettyFlags = GET_PRETTY_FLAGS(pretty);
1241 : :
2796 1242 : 546 : return pg_get_indexdef_worker(indexrelid, 0, NULL,
1243 : : true, true,
1244 : : false, false,
1245 : : prettyFlags, false);
1246 : : }
1247 : :
1248 : : /* Internal version, extensible with flags to control its behavior */
1249 : : char *
1031 michael@paquier.xyz 1250 : 4 : pg_get_indexdef_columns_extended(Oid indexrelid, bits16 flags)
1251 : : {
1252 : 4 : bool pretty = ((flags & RULE_INDEXDEF_PRETTY) != 0);
1253 : 4 : bool keys_only = ((flags & RULE_INDEXDEF_KEYS_ONLY) != 0);
1254 : : int prettyFlags;
1255 : :
1256 [ + - ]: 4 : prettyFlags = GET_PRETTY_FLAGS(pretty);
1257 : :
1258 : 4 : return pg_get_indexdef_worker(indexrelid, 0, NULL,
1259 : : true, keys_only,
1260 : : false, false,
1261 : : prettyFlags, false);
1262 : : }
1263 : :
1264 : : /*
1265 : : * Internal workhorse to decompile an index definition.
1266 : : *
1267 : : * This is now used for exclusion constraints as well: if excludeOps is not
1268 : : * NULL then it points to an array of exclusion operator OIDs.
1269 : : */
1270 : : static char *
6070 tgl@sss.pgh.pa.us 1271 : 4589 : pg_get_indexdef_worker(Oid indexrelid, int colno,
1272 : : const Oid *excludeOps,
1273 : : bool attrsOnly, bool keysOnly,
1274 : : bool showTblSpc, bool inherits,
1275 : : int prettyFlags, bool missing_ok)
1276 : : {
1277 : : /* might want a separate isConstraint parameter later */
5942 1278 : 4589 : bool isConstraint = (excludeOps != NULL);
1279 : : HeapTuple ht_idx;
1280 : : HeapTuple ht_idxrel;
1281 : : HeapTuple ht_am;
1282 : : Form_pg_index idxrec;
1283 : : Form_pg_class idxrelrec;
1284 : : Form_pg_am amrec;
1285 : : const IndexAmRoutine *amroutine;
1286 : : List *indexprs;
1287 : : ListCell *indexpr_item;
1288 : : List *context;
1289 : : Oid indrelid;
1290 : : int keyno;
1291 : : Datum indcollDatum;
1292 : : Datum indclassDatum;
1293 : : Datum indoptionDatum;
1294 : : oidvector *indcollation;
1295 : : oidvector *indclass;
1296 : : int2vector *indoption;
1297 : : StringInfoData buf;
1298 : : char *str;
1299 : : char *sep;
1300 : :
1301 : : /*
1302 : : * Fetch the pg_index tuple by the Oid of the index
1303 : : */
5873 rhaas@postgresql.org 1304 : 4589 : ht_idx = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexrelid));
10026 bruce@momjian.us 1305 [ + + ]: 4589 : if (!HeapTupleIsValid(ht_idx))
1306 : : {
3519 rhaas@postgresql.org 1307 [ + - ]: 3 : if (missing_ok)
1308 : 3 : return NULL;
8267 tgl@sss.pgh.pa.us 1309 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for index %u", indexrelid);
1310 : : }
9791 bruce@momjian.us 1311 :CBC 4586 : idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
1312 : :
8717 tgl@sss.pgh.pa.us 1313 : 4586 : indrelid = idxrec->indrelid;
1314 [ - + ]: 4586 : Assert(indexrelid == idxrec->indexrelid);
1315 : :
1316 : : /* Must get indcollation, indclass, and indoption the hard way */
1086 dgustafsson@postgres 1317 : 4586 : indcollDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx,
1318 : : Anum_pg_index_indcollation);
5514 peter_e@gmx.net 1319 : 4586 : indcollation = (oidvector *) DatumGetPointer(indcollDatum);
1320 : :
1086 dgustafsson@postgres 1321 : 4586 : indclassDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx,
1322 : : Anum_pg_index_indclass);
7656 tgl@sss.pgh.pa.us 1323 : 4586 : indclass = (oidvector *) DatumGetPointer(indclassDatum);
1324 : :
1086 dgustafsson@postgres 1325 : 4586 : indoptionDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx,
1326 : : Anum_pg_index_indoption);
7005 tgl@sss.pgh.pa.us 1327 : 4586 : indoption = (int2vector *) DatumGetPointer(indoptionDatum);
1328 : :
1329 : : /*
1330 : : * Fetch the pg_class tuple of the index relation
1331 : : */
5873 rhaas@postgresql.org 1332 : 4586 : ht_idxrel = SearchSysCache1(RELOID, ObjectIdGetDatum(indexrelid));
10026 bruce@momjian.us 1333 [ - + ]: 4586 : if (!HeapTupleIsValid(ht_idxrel))
8267 tgl@sss.pgh.pa.us 1334 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", indexrelid);
9791 bruce@momjian.us 1335 :CBC 4586 : idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
1336 : :
1337 : : /*
1338 : : * Fetch the pg_am tuple of the index' access method
1339 : : */
5873 rhaas@postgresql.org 1340 : 4586 : ht_am = SearchSysCache1(AMOID, ObjectIdGetDatum(idxrelrec->relam));
8790 tgl@sss.pgh.pa.us 1341 [ - + ]: 4586 : if (!HeapTupleIsValid(ht_am))
8267 tgl@sss.pgh.pa.us 1342 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for access method %u",
1343 : : idxrelrec->relam);
8790 tgl@sss.pgh.pa.us 1344 :CBC 4586 : amrec = (Form_pg_am) GETSTRUCT(ht_am);
1345 : :
1346 : : /* Fetch the index AM's API struct */
3710 1347 : 4586 : amroutine = GetIndexAmRoutine(amrec->amhandler);
1348 : :
1349 : : /*
1350 : : * Get the index expressions, if any. (NOTE: we do not use the relcache
1351 : : * versions of the expressions and predicate, because we want to display
1352 : : * non-const-folded expressions.)
1353 : : */
2909 andrew@dunslane.net 1354 [ + + ]: 4586 : if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs, NULL))
1355 : : {
1356 : : Datum exprsDatum;
1357 : : char *exprsString;
1358 : :
1086 dgustafsson@postgres 1359 : 329 : exprsDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx,
1360 : : Anum_pg_index_indexprs);
6564 tgl@sss.pgh.pa.us 1361 : 329 : exprsString = TextDatumGetCString(exprsDatum);
8327 1362 : 329 : indexprs = (List *) stringToNode(exprsString);
1363 : 329 : pfree(exprsString);
1364 : : }
1365 : : else
1366 : 4257 : indexprs = NIL;
1367 : :
7963 neilc@samurai.com 1368 : 4586 : indexpr_item = list_head(indexprs);
1369 : :
5612 tgl@sss.pgh.pa.us 1370 : 4586 : context = deparse_context_for(get_relation_name(indrelid), indrelid);
1371 : :
1372 : : /*
1373 : : * Start the index definition. Note that the index's name should never be
1374 : : * schema-qualified, but the indexed rel's name may be.
1375 : : */
9661 1376 : 4586 : initStringInfo(&buf);
1377 : :
6070 1378 [ + + ]: 4586 : if (!attrsOnly)
1379 : : {
5942 1380 [ + + ]: 3805 : if (!isConstraint)
2977 alvherre@alvh.no-ip. 1381 : 7506 : appendStringInfo(&buf, "CREATE %sINDEX %s ON %s%s USING %s (",
5942 tgl@sss.pgh.pa.us 1382 [ + + ]: 3753 : idxrec->indisunique ? "UNIQUE " : "",
1383 : 3753 : quote_identifier(NameStr(idxrelrec->relname)),
2977 alvherre@alvh.no-ip. 1384 [ + + ]: 3753 : idxrelrec->relkind == RELKIND_PARTITIONED_INDEX
1385 [ + + ]: 350 : && !inherits ? "ONLY " : "",
2939 tgl@sss.pgh.pa.us 1386 [ + + ]: 3753 : (prettyFlags & PRETTYFLAG_SCHEMA) ?
1387 : 784 : generate_relation_name(indrelid, NIL) :
1388 : 2969 : generate_qualified_relation_name(indrelid),
5942 1389 : 3753 : quote_identifier(NameStr(amrec->amname)));
1390 : : else /* currently, must be EXCLUDE constraint */
1391 : 52 : appendStringInfo(&buf, "EXCLUDE USING %s (",
1392 : 52 : quote_identifier(NameStr(amrec->amname)));
1393 : : }
1394 : :
1395 : : /*
1396 : : * Report the indexed attributes
1397 : : */
10026 bruce@momjian.us 1398 : 4586 : sep = "";
8327 tgl@sss.pgh.pa.us 1399 [ + + ]: 11480 : for (keyno = 0; keyno < idxrec->indnatts; keyno++)
1400 : : {
7656 1401 : 6943 : AttrNumber attnum = idxrec->indkey.values[keyno];
1402 : : Oid keycoltype;
1403 : : Oid keycolcollation;
1404 : :
1405 : : /*
1406 : : * Ignore non-key attributes if told to.
1407 : : */
2796 1408 [ + + + + ]: 6943 : if (keysOnly && keyno >= idxrec->indnkeyatts)
2899 teodor@sigaev.ru 1409 : 49 : break;
1410 : :
1411 : : /* Otherwise, print INCLUDE to divide key and non-key attrs. */
2796 tgl@sss.pgh.pa.us 1412 [ + + + + ]: 6894 : if (!colno && keyno == idxrec->indnkeyatts)
1413 : : {
2899 teodor@sigaev.ru 1414 : 125 : appendStringInfoString(&buf, ") INCLUDE (");
1415 : 125 : sep = "";
1416 : : }
1417 : :
8264 tgl@sss.pgh.pa.us 1418 [ + + ]: 6894 : if (!colno)
7624 neilc@samurai.com 1419 : 6573 : appendStringInfoString(&buf, sep);
10026 bruce@momjian.us 1420 : 6894 : sep = ", ";
1421 : :
8327 tgl@sss.pgh.pa.us 1422 [ + + ]: 6894 : if (attnum != 0)
1423 : : {
1424 : : /* Simple index column */
1425 : : char *attname;
1426 : : int32 keycoltypmod;
1427 : :
2953 alvherre@alvh.no-ip. 1428 : 6488 : attname = get_attname(indrelid, attnum, false);
8259 bruce@momjian.us 1429 [ + + + + ]: 6488 : if (!colno || colno == keyno + 1)
8079 neilc@samurai.com 1430 : 6404 : appendStringInfoString(&buf, quote_identifier(attname));
5468 tgl@sss.pgh.pa.us 1431 : 6488 : get_atttypetypmodcoll(indrelid, attnum,
1432 : : &keycoltype, &keycoltypmod,
1433 : : &keycolcollation);
1434 : : }
1435 : : else
1436 : : {
1437 : : /* expressional index */
1438 : : Node *indexkey;
1439 : :
7963 neilc@samurai.com 1440 [ - + ]: 406 : if (indexpr_item == NULL)
8327 tgl@sss.pgh.pa.us 1441 [ # # ]:UBC 0 : elog(ERROR, "too few entries in indexprs list");
7963 neilc@samurai.com 1442 :CBC 406 : indexkey = (Node *) lfirst(indexpr_item);
2435 tgl@sss.pgh.pa.us 1443 : 406 : indexpr_item = lnext(indexprs, indexpr_item);
1444 : : /* Deparse */
8264 1445 : 406 : str = deparse_expression_pretty(indexkey, context, false, false,
1446 : : prettyFlags, 0);
8259 bruce@momjian.us 1447 [ + + + + ]: 406 : if (!colno || colno == keyno + 1)
1448 : : {
1449 : : /* Need parens if it's not a bare function call */
3167 tgl@sss.pgh.pa.us 1450 [ + + ]: 400 : if (looks_like_function(indexkey))
8079 neilc@samurai.com 1451 : 26 : appendStringInfoString(&buf, str);
1452 : : else
8264 tgl@sss.pgh.pa.us 1453 : 374 : appendStringInfo(&buf, "(%s)", str);
1454 : : }
8327 1455 : 406 : keycoltype = exprType(indexkey);
5470 1456 : 406 : keycolcollation = exprCollation(indexkey);
1457 : : }
1458 : :
1459 : : /* Print additional decoration for (selected) key columns */
2796 1460 [ + + + + : 6894 : if (!attrsOnly && keyno < idxrec->indnkeyatts &&
- + ]
2796 tgl@sss.pgh.pa.us 1461 [ # # ]:UBC 0 : (!colno || colno == keyno + 1))
1462 : : {
2534 tgl@sss.pgh.pa.us 1463 :CBC 5605 : int16 opt = indoption->values[keyno];
1464 : 5605 : Oid indcoll = indcollation->values[keyno];
2176 akorotkov@postgresql 1465 : 5605 : Datum attoptions = get_attoptions(indexrelid, keyno + 1);
1466 : 5605 : bool has_options = attoptions != (Datum) 0;
1467 : :
1468 : : /* Add collation, if not default for column */
5470 tgl@sss.pgh.pa.us 1469 [ + + + + ]: 5605 : if (OidIsValid(indcoll) && indcoll != keycolcollation)
1470 : 47 : appendStringInfo(&buf, " COLLATE %s",
1471 : : generate_collation_name((indcoll)));
1472 : :
1473 : : /* Add the operator class name, if not default */
2176 akorotkov@postgresql 1474 [ + + ]: 5605 : get_opclass_name(indclass->values[keyno],
1475 : : has_options ? InvalidOid : keycoltype, &buf);
1476 : :
1477 [ + + ]: 5605 : if (has_options)
1478 : : {
1479 : 17 : appendStringInfoString(&buf, " (");
1480 : 17 : get_reloptions(&buf, attoptions);
1481 : 17 : appendStringInfoChar(&buf, ')');
1482 : : }
1483 : :
1484 : : /* Add options if relevant */
3710 tgl@sss.pgh.pa.us 1485 [ + + ]: 5605 : if (amroutine->amcanorder)
1486 : : {
1487 : : /* if it supports sort ordering, report DESC and NULLS opts */
6660 1488 [ - + ]: 4552 : if (opt & INDOPTION_DESC)
1489 : : {
4518 rhaas@postgresql.org 1490 :UBC 0 : appendStringInfoString(&buf, " DESC");
1491 : : /* NULLS FIRST is the default in this case */
6660 tgl@sss.pgh.pa.us 1492 [ # # ]: 0 : if (!(opt & INDOPTION_NULLS_FIRST))
4518 rhaas@postgresql.org 1493 : 0 : appendStringInfoString(&buf, " NULLS LAST");
1494 : : }
1495 : : else
1496 : : {
6660 tgl@sss.pgh.pa.us 1497 [ - + ]:CBC 4552 : if (opt & INDOPTION_NULLS_FIRST)
4518 rhaas@postgresql.org 1498 :UBC 0 : appendStringInfoString(&buf, " NULLS FIRST");
1499 : : }
1500 : : }
1501 : :
1502 : : /* Add the exclusion operator if relevant */
5942 tgl@sss.pgh.pa.us 1503 [ + + ]:CBC 5605 : if (excludeOps != NULL)
1504 : 62 : appendStringInfo(&buf, " WITH %s",
1505 : 62 : generate_operator_name(excludeOps[keyno],
1506 : : keycoltype,
1507 : : keycoltype));
1508 : : }
1509 : : }
1510 : :
6070 1511 [ + + ]: 4586 : if (!attrsOnly)
1512 : : {
8259 bruce@momjian.us 1513 : 3805 : appendStringInfoChar(&buf, ')');
1514 : :
1501 peter@eisentraut.org 1515 [ + + ]: 3805 : if (idxrec->indnullsnotdistinct)
1286 drowley@postgresql.o 1516 : 6 : appendStringInfoString(&buf, " NULLS NOT DISTINCT");
1517 : :
1518 : : /*
1519 : : * If it has options, append "WITH (options)"
1520 : : */
7195 tgl@sss.pgh.pa.us 1521 : 3805 : str = flatten_reloptions(indexrelid);
1522 [ + + ]: 3805 : if (str)
1523 : : {
1524 : 105 : appendStringInfo(&buf, " WITH (%s)", str);
1525 : 105 : pfree(str);
1526 : : }
1527 : :
1528 : : /*
1529 : : * Print tablespace, but only if requested
1530 : : */
6728 1531 [ + + ]: 3805 : if (showTblSpc)
1532 : : {
1533 : : Oid tblspc;
1534 : :
1535 : 117 : tblspc = get_rel_tablespace(indexrelid);
2516 alvherre@alvh.no-ip. 1536 [ + + ]: 117 : if (OidIsValid(tblspc))
1537 : : {
1538 [ - + ]: 27 : if (isConstraint)
2516 alvherre@alvh.no-ip. 1539 :UBC 0 : appendStringInfoString(&buf, " USING INDEX");
2516 alvherre@alvh.no-ip. 1540 :CBC 27 : appendStringInfo(&buf, " TABLESPACE %s",
1541 : 27 : quote_identifier(get_tablespace_name(tblspc)));
1542 : : }
1543 : : }
1544 : :
1545 : : /*
1546 : : * If it's a partial index, decompile and append the predicate
1547 : : */
2909 andrew@dunslane.net 1548 [ + + ]: 3805 : if (!heap_attisnull(ht_idx, Anum_pg_index_indpred, NULL))
1549 : : {
1550 : : Node *node;
1551 : : Datum predDatum;
1552 : : char *predString;
1553 : :
1554 : : /* Convert text string to node tree */
1086 dgustafsson@postgres 1555 : 162 : predDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx,
1556 : : Anum_pg_index_indpred);
6564 tgl@sss.pgh.pa.us 1557 : 162 : predString = TextDatumGetCString(predDatum);
8264 1558 : 162 : node = (Node *) stringToNode(predString);
1559 : 162 : pfree(predString);
1560 : :
1561 : : /* Deparse */
1562 : 162 : str = deparse_expression_pretty(node, context, false, false,
1563 : : prettyFlags, 0);
5942 1564 [ + + ]: 162 : if (isConstraint)
1565 : 21 : appendStringInfo(&buf, " WHERE (%s)", str);
1566 : : else
1567 : 141 : appendStringInfo(&buf, " WHERE %s", str);
1568 : : }
1569 : : }
1570 : :
1571 : : /* Clean up */
9250 1572 : 4586 : ReleaseSysCache(ht_idx);
1573 : 4586 : ReleaseSysCache(ht_idxrel);
8790 1574 : 4586 : ReleaseSysCache(ht_am);
1575 : :
7984 1576 : 4586 : return buf.data;
1577 : : }
1578 : :
1579 : : /* ----------
1580 : : * pg_get_querydef
1581 : : *
1582 : : * Public entry point to deparse one query parsetree.
1583 : : * The pretty flags are determined by GET_PRETTY_FLAGS(pretty).
1584 : : *
1585 : : * The result is a palloc'd C string.
1586 : : * ----------
1587 : : */
1588 : : char *
1448 tgl@sss.pgh.pa.us 1589 :UBC 0 : pg_get_querydef(Query *query, bool pretty)
1590 : : {
1591 : : StringInfoData buf;
1592 : : int prettyFlags;
1593 : :
1594 [ # # ]: 0 : prettyFlags = GET_PRETTY_FLAGS(pretty);
1595 : :
1596 : 0 : initStringInfo(&buf);
1597 : :
1394 1598 : 0 : get_query_def(query, &buf, NIL, NULL, true,
1599 : : prettyFlags, WRAP_COLUMN_DEFAULT, 0);
1600 : :
1448 1601 : 0 : return buf.data;
1602 : : }
1603 : :
1604 : : /*
1605 : : * pg_get_statisticsobjdef
1606 : : * Get the definition of an extended statistics object
1607 : : */
1608 : : Datum
3227 tgl@sss.pgh.pa.us 1609 :CBC 153 : pg_get_statisticsobjdef(PG_FUNCTION_ARGS)
1610 : : {
3278 alvherre@alvh.no-ip. 1611 : 153 : Oid statextid = PG_GETARG_OID(0);
1612 : : char *res;
1613 : :
1815 tomas.vondra@postgre 1614 : 153 : res = pg_get_statisticsobj_worker(statextid, false, true);
1615 : :
1616 [ + + ]: 153 : if (res == NULL)
1617 : 3 : PG_RETURN_NULL();
1618 : :
1619 : 150 : PG_RETURN_TEXT_P(string_to_text(res));
1620 : : }
1621 : :
1622 : : /*
1623 : : * Internal version for use by ALTER TABLE.
1624 : : * Returns a palloc'd C string; no pretty-printing.
1625 : : */
1626 : : char *
1627 : 40 : pg_get_statisticsobjdef_string(Oid statextid)
1628 : : {
1629 : 40 : return pg_get_statisticsobj_worker(statextid, false, false);
1630 : : }
1631 : :
1632 : : /*
1633 : : * pg_get_statisticsobjdef_columns
1634 : : * Get columns and expressions for an extended statistics object
1635 : : */
1636 : : Datum
1637 : 213 : pg_get_statisticsobjdef_columns(PG_FUNCTION_ARGS)
1638 : : {
1639 : 213 : Oid statextid = PG_GETARG_OID(0);
1640 : : char *res;
1641 : :
1642 : 213 : res = pg_get_statisticsobj_worker(statextid, true, true);
1643 : :
3278 alvherre@alvh.no-ip. 1644 [ - + ]: 213 : if (res == NULL)
3278 alvherre@alvh.no-ip. 1645 :UBC 0 : PG_RETURN_NULL();
1646 : :
3278 alvherre@alvh.no-ip. 1647 :CBC 213 : PG_RETURN_TEXT_P(string_to_text(res));
1648 : : }
1649 : :
1650 : : /*
1651 : : * Internal workhorse to decompile an extended statistics object.
1652 : : */
1653 : : static char *
1815 tomas.vondra@postgre 1654 : 406 : pg_get_statisticsobj_worker(Oid statextid, bool columns_only, bool missing_ok)
1655 : : {
1656 : : Form_pg_statistic_ext statextrec;
1657 : : HeapTuple statexttup;
1658 : : StringInfoData buf;
1659 : : int colno;
1660 : : char *nsp;
1661 : : ArrayType *arr;
1662 : : char *enabled;
1663 : : Datum datum;
1664 : : bool ndistinct_enabled;
1665 : : bool dependencies_enabled;
1666 : : bool mcv_enabled;
1667 : : int i;
1668 : : List *context;
1669 : : ListCell *lc;
1670 : 406 : List *exprs = NIL;
1671 : : bool has_exprs;
1672 : : int ncolumns;
1673 : :
3278 alvherre@alvh.no-ip. 1674 : 406 : statexttup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statextid));
1675 : :
1676 [ + + ]: 406 : if (!HeapTupleIsValid(statexttup))
1677 : : {
1678 [ + - ]: 3 : if (missing_ok)
1679 : 3 : return NULL;
3227 tgl@sss.pgh.pa.us 1680 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for statistics object %u", statextid);
1681 : : }
1682 : :
1683 : : /* has the statistics expressions? */
1815 tomas.vondra@postgre 1684 :CBC 403 : has_exprs = !heap_attisnull(statexttup, Anum_pg_statistic_ext_stxexprs, NULL);
1685 : :
1686 : 403 : statextrec = (Form_pg_statistic_ext) GETSTRUCT(statexttup);
1687 : :
1688 : : /*
1689 : : * Get the statistics expressions, if any. (NOTE: we do not use the
1690 : : * relcache versions of the expressions, because we want to display
1691 : : * non-const-folded expressions.)
1692 : : */
1693 [ + + ]: 403 : if (has_exprs)
1694 : : {
1695 : : Datum exprsDatum;
1696 : : char *exprsString;
1697 : :
1086 dgustafsson@postgres 1698 : 111 : exprsDatum = SysCacheGetAttrNotNull(STATEXTOID, statexttup,
1699 : : Anum_pg_statistic_ext_stxexprs);
1815 tomas.vondra@postgre 1700 : 111 : exprsString = TextDatumGetCString(exprsDatum);
1701 : 111 : exprs = (List *) stringToNode(exprsString);
1702 : 111 : pfree(exprsString);
1703 : : }
1704 : : else
1705 : 292 : exprs = NIL;
1706 : :
1707 : : /* count the number of columns (attributes and expressions) */
1708 : 403 : ncolumns = statextrec->stxkeys.dim1 + list_length(exprs);
1709 : :
1710 : 403 : initStringInfo(&buf);
1711 : :
1712 [ + + ]: 403 : if (!columns_only)
1713 : : {
1692 tgl@sss.pgh.pa.us 1714 : 190 : nsp = get_namespace_name_or_temp(statextrec->stxnamespace);
1815 tomas.vondra@postgre 1715 : 190 : appendStringInfo(&buf, "CREATE STATISTICS %s",
1716 : : quote_qualified_identifier(nsp,
1717 : 190 : NameStr(statextrec->stxname)));
1718 : :
1719 : : /*
1720 : : * Decode the stxkind column so that we know which stats types to
1721 : : * print.
1722 : : */
1086 dgustafsson@postgres 1723 : 190 : datum = SysCacheGetAttrNotNull(STATEXTOID, statexttup,
1724 : : Anum_pg_statistic_ext_stxkind);
1815 tomas.vondra@postgre 1725 : 190 : arr = DatumGetArrayTypeP(datum);
1726 [ + - ]: 190 : if (ARR_NDIM(arr) != 1 ||
1727 [ + - ]: 190 : ARR_HASNULL(arr) ||
1728 [ - + ]: 190 : ARR_ELEMTYPE(arr) != CHAROID)
1815 tomas.vondra@postgre 1729 [ # # ]:UBC 0 : elog(ERROR, "stxkind is not a 1-D char array");
1815 tomas.vondra@postgre 1730 [ - + ]:CBC 190 : enabled = (char *) ARR_DATA_PTR(arr);
1731 : :
1732 : 190 : ndistinct_enabled = false;
1733 : 190 : dependencies_enabled = false;
1734 : 190 : mcv_enabled = false;
1735 : :
1736 [ + + ]: 597 : for (i = 0; i < ARR_DIMS(arr)[0]; i++)
1737 : : {
1738 [ + + ]: 407 : if (enabled[i] == STATS_EXT_NDISTINCT)
1739 : 122 : ndistinct_enabled = true;
1740 [ + + ]: 285 : else if (enabled[i] == STATS_EXT_DEPENDENCIES)
1741 : 99 : dependencies_enabled = true;
1742 [ + + ]: 186 : else if (enabled[i] == STATS_EXT_MCV)
1743 : 108 : mcv_enabled = true;
1744 : :
1745 : : /* ignore STATS_EXT_EXPRESSIONS (it's built automatically) */
1746 : : }
1747 : :
1748 : : /*
1749 : : * If any option is disabled, then we'll need to append the types
1750 : : * clause to show which options are enabled. We omit the types clause
1751 : : * on purpose when all options are enabled, so a pg_dump/pg_restore
1752 : : * will create all statistics types on a newer postgres version, if
1753 : : * the statistics had all options enabled on the original version.
1754 : : *
1755 : : * But if the statistics is defined on just a single column, it has to
1756 : : * be an expression statistics. In that case we don't need to specify
1757 : : * kinds.
1758 : : */
1759 [ + + + + : 190 : if ((!ndistinct_enabled || !dependencies_enabled || !mcv_enabled) &&
- + + + ]
1760 : : (ncolumns > 1))
1761 : : {
1762 : 59 : bool gotone = false;
1763 : :
1764 : 59 : appendStringInfoString(&buf, " (");
1765 : :
1766 [ + + ]: 59 : if (ndistinct_enabled)
1767 : : {
1768 : 32 : appendStringInfoString(&buf, "ndistinct");
1769 : 32 : gotone = true;
1770 : : }
1771 : :
1772 [ + + ]: 59 : if (dependencies_enabled)
1773 : : {
1774 [ - + ]: 9 : appendStringInfo(&buf, "%sdependencies", gotone ? ", " : "");
1775 : 9 : gotone = true;
1776 : : }
1777 : :
1778 [ + + ]: 59 : if (mcv_enabled)
1779 [ - + ]: 18 : appendStringInfo(&buf, "%smcv", gotone ? ", " : "");
1780 : :
1781 : 59 : appendStringInfoChar(&buf, ')');
1782 : : }
1783 : :
1784 : 190 : appendStringInfoString(&buf, " ON ");
1785 : : }
1786 : :
1787 : : /* decode simple column references */
3254 alvherre@alvh.no-ip. 1788 [ + + ]: 1133 : for (colno = 0; colno < statextrec->stxkeys.dim1; colno++)
1789 : : {
1790 : 730 : AttrNumber attnum = statextrec->stxkeys.values[colno];
1791 : : char *attname;
1792 : :
3278 1793 [ + + ]: 730 : if (colno > 0)
1794 : 400 : appendStringInfoString(&buf, ", ");
1795 : :
2953 1796 : 730 : attname = get_attname(statextrec->stxrelid, attnum, false);
1797 : :
3278 1798 : 730 : appendStringInfoString(&buf, quote_identifier(attname));
1799 : : }
1800 : :
1815 tomas.vondra@postgre 1801 : 403 : context = deparse_context_for(get_relation_name(statextrec->stxrelid),
1802 : : statextrec->stxrelid);
1803 : :
1804 [ + + + + : 557 : foreach(lc, exprs)
+ + ]
1805 : : {
1806 : 154 : Node *expr = (Node *) lfirst(lc);
1807 : : char *str;
1656 1808 : 154 : int prettyFlags = PRETTYFLAG_PAREN;
1809 : :
1815 1810 : 154 : str = deparse_expression_pretty(expr, context, false, false,
1811 : : prettyFlags, 0);
1812 : :
1813 [ + + ]: 154 : if (colno > 0)
1814 : 81 : appendStringInfoString(&buf, ", ");
1815 : :
1816 : : /* Need parens if it's not a bare function call */
1817 [ + + ]: 154 : if (looks_like_function(expr))
1818 : 17 : appendStringInfoString(&buf, str);
1819 : : else
1820 : 137 : appendStringInfo(&buf, "(%s)", str);
1821 : :
1822 : 154 : colno++;
1823 : : }
1824 : :
1825 [ + + ]: 403 : if (!columns_only)
1826 : 190 : appendStringInfo(&buf, " FROM %s",
1827 : : generate_relation_name(statextrec->stxrelid, NIL));
1828 : :
3278 alvherre@alvh.no-ip. 1829 : 403 : ReleaseSysCache(statexttup);
1830 : :
1831 : 403 : return buf.data;
1832 : : }
1833 : :
1834 : : /*
1835 : : * Generate text array of expressions for statistics object.
1836 : : */
1837 : : Datum
1815 tomas.vondra@postgre 1838 : 103 : pg_get_statisticsobjdef_expressions(PG_FUNCTION_ARGS)
1839 : : {
1840 : 103 : Oid statextid = PG_GETARG_OID(0);
1841 : : Form_pg_statistic_ext statextrec;
1842 : : HeapTuple statexttup;
1843 : : Datum datum;
1844 : : List *context;
1845 : : ListCell *lc;
1846 : 103 : List *exprs = NIL;
1847 : : bool has_exprs;
1848 : : char *tmp;
1849 : 103 : ArrayBuildState *astate = NULL;
1850 : :
1851 : 103 : statexttup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statextid));
1852 : :
1853 [ - + ]: 103 : if (!HeapTupleIsValid(statexttup))
1773 tomas.vondra@postgre 1854 :UBC 0 : PG_RETURN_NULL();
1855 : :
1856 : : /* Does the stats object have expressions? */
1815 tomas.vondra@postgre 1857 :CBC 103 : has_exprs = !heap_attisnull(statexttup, Anum_pg_statistic_ext_stxexprs, NULL);
1858 : :
1859 : : /* no expressions? we're done */
1860 [ + + ]: 103 : if (!has_exprs)
1861 : : {
1862 : 9 : ReleaseSysCache(statexttup);
1863 : 9 : PG_RETURN_NULL();
1864 : : }
1865 : :
1866 : 94 : statextrec = (Form_pg_statistic_ext) GETSTRUCT(statexttup);
1867 : :
1868 : : /*
1869 : : * Get the statistics expressions, and deparse them into text values.
1870 : : */
1086 dgustafsson@postgres 1871 : 94 : datum = SysCacheGetAttrNotNull(STATEXTOID, statexttup,
1872 : : Anum_pg_statistic_ext_stxexprs);
1815 tomas.vondra@postgre 1873 : 94 : tmp = TextDatumGetCString(datum);
1874 : 94 : exprs = (List *) stringToNode(tmp);
1875 : 94 : pfree(tmp);
1876 : :
1877 : 94 : context = deparse_context_for(get_relation_name(statextrec->stxrelid),
1878 : : statextrec->stxrelid);
1879 : :
1880 [ + - + + : 223 : foreach(lc, exprs)
+ + ]
1881 : : {
1882 : 129 : Node *expr = (Node *) lfirst(lc);
1883 : : char *str;
1884 : 129 : int prettyFlags = PRETTYFLAG_INDENT;
1885 : :
1886 : 129 : str = deparse_expression_pretty(expr, context, false, false,
1887 : : prettyFlags, 0);
1888 : :
1889 : 129 : astate = accumArrayResult(astate,
1890 : 129 : PointerGetDatum(cstring_to_text(str)),
1891 : : false,
1892 : : TEXTOID,
1893 : : CurrentMemoryContext);
1894 : : }
1895 : :
1896 : 94 : ReleaseSysCache(statexttup);
1897 : :
1898 : 94 : PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext));
1899 : : }
1900 : :
1901 : : /*
1902 : : * pg_get_partkeydef
1903 : : *
1904 : : * Returns the partition key specification, ie, the following:
1905 : : *
1906 : : * { RANGE | LIST | HASH } (column opt_collation opt_opclass [, ...])
1907 : : */
1908 : : Datum
3385 rhaas@postgresql.org 1909 : 739 : pg_get_partkeydef(PG_FUNCTION_ARGS)
1910 : : {
1911 : 739 : Oid relid = PG_GETARG_OID(0);
1912 : : char *res;
1913 : :
3245 sfrost@snowman.net 1914 : 739 : res = pg_get_partkeydef_worker(relid, PRETTYFLAG_INDENT, false, true);
1915 : :
1916 [ + + ]: 739 : if (res == NULL)
1917 : 3 : PG_RETURN_NULL();
1918 : :
1919 : 736 : PG_RETURN_TEXT_P(string_to_text(res));
1920 : : }
1921 : :
1922 : : /* Internal version that just reports the column definitions */
1923 : : char *
3299 rhaas@postgresql.org 1924 : 71 : pg_get_partkeydef_columns(Oid relid, bool pretty)
1925 : : {
1926 : : int prettyFlags;
1927 : :
1448 tgl@sss.pgh.pa.us 1928 [ + - ]: 71 : prettyFlags = GET_PRETTY_FLAGS(pretty);
1929 : :
3245 sfrost@snowman.net 1930 : 71 : return pg_get_partkeydef_worker(relid, prettyFlags, true, false);
1931 : : }
1932 : :
1933 : : /*
1934 : : * Internal workhorse to decompile a partition key definition.
1935 : : */
1936 : : static char *
3299 rhaas@postgresql.org 1937 : 810 : pg_get_partkeydef_worker(Oid relid, int prettyFlags,
1938 : : bool attrsOnly, bool missing_ok)
1939 : : {
1940 : : Form_pg_partitioned_table form;
1941 : : HeapTuple tuple;
1942 : : oidvector *partclass;
1943 : : oidvector *partcollation;
1944 : : List *partexprs;
1945 : : ListCell *partexpr_item;
1946 : : List *context;
1947 : : Datum datum;
1948 : : StringInfoData buf;
1949 : : int keyno;
1950 : : char *str;
1951 : : char *sep;
1952 : :
3385 1953 : 810 : tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
1954 [ + + ]: 810 : if (!HeapTupleIsValid(tuple))
1955 : : {
3245 sfrost@snowman.net 1956 [ + - ]: 3 : if (missing_ok)
1957 : 3 : return NULL;
3385 rhaas@postgresql.org 1958 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for partition key of %u", relid);
1959 : : }
1960 : :
3385 rhaas@postgresql.org 1961 :CBC 807 : form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
1962 : :
1963 [ - + ]: 807 : Assert(form->partrelid == relid);
1964 : :
1965 : : /* Must get partclass and partcollation the hard way */
1086 dgustafsson@postgres 1966 : 807 : datum = SysCacheGetAttrNotNull(PARTRELID, tuple,
1967 : : Anum_pg_partitioned_table_partclass);
3385 rhaas@postgresql.org 1968 : 807 : partclass = (oidvector *) DatumGetPointer(datum);
1969 : :
1086 dgustafsson@postgres 1970 : 807 : datum = SysCacheGetAttrNotNull(PARTRELID, tuple,
1971 : : Anum_pg_partitioned_table_partcollation);
3385 rhaas@postgresql.org 1972 : 807 : partcollation = (oidvector *) DatumGetPointer(datum);
1973 : :
1974 : :
1975 : : /*
1976 : : * Get the expressions, if any. (NOTE: we do not use the relcache
1977 : : * versions of the expressions, because we want to display
1978 : : * non-const-folded expressions.)
1979 : : */
2909 andrew@dunslane.net 1980 [ + + ]: 807 : if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs, NULL))
1981 : : {
1982 : : Datum exprsDatum;
1983 : : char *exprsString;
1984 : :
1086 dgustafsson@postgres 1985 : 73 : exprsDatum = SysCacheGetAttrNotNull(PARTRELID, tuple,
1986 : : Anum_pg_partitioned_table_partexprs);
3385 rhaas@postgresql.org 1987 : 73 : exprsString = TextDatumGetCString(exprsDatum);
1988 : 73 : partexprs = (List *) stringToNode(exprsString);
1989 : :
1990 [ - + ]: 73 : if (!IsA(partexprs, List))
3385 rhaas@postgresql.org 1991 [ # # ]:UBC 0 : elog(ERROR, "unexpected node type found in partexprs: %d",
1992 : : (int) nodeTag(partexprs));
1993 : :
3385 rhaas@postgresql.org 1994 :CBC 73 : pfree(exprsString);
1995 : : }
1996 : : else
1997 : 734 : partexprs = NIL;
1998 : :
1999 : 807 : partexpr_item = list_head(partexprs);
2000 : 807 : context = deparse_context_for(get_relation_name(relid), relid);
2001 : :
2002 : 807 : initStringInfo(&buf);
2003 : :
2004 [ + + + - ]: 807 : switch (form->partstrat)
2005 : : {
3048 2006 : 58 : case PARTITION_STRATEGY_HASH:
2007 [ + - ]: 58 : if (!attrsOnly)
2446 drowley@postgresql.o 2008 : 58 : appendStringInfoString(&buf, "HASH");
3048 rhaas@postgresql.org 2009 : 58 : break;
3385 2010 : 293 : case PARTITION_STRATEGY_LIST:
3299 2011 [ + + ]: 293 : if (!attrsOnly)
3134 peter_e@gmx.net 2012 : 273 : appendStringInfoString(&buf, "LIST");
3385 rhaas@postgresql.org 2013 : 293 : break;
2014 : 456 : case PARTITION_STRATEGY_RANGE:
3299 2015 [ + + ]: 456 : if (!attrsOnly)
3134 peter_e@gmx.net 2016 : 405 : appendStringInfoString(&buf, "RANGE");
3385 rhaas@postgresql.org 2017 : 456 : break;
3385 rhaas@postgresql.org 2018 :UBC 0 : default:
2019 [ # # ]: 0 : elog(ERROR, "unexpected partition strategy: %d",
2020 : : (int) form->partstrat);
2021 : : }
2022 : :
3299 rhaas@postgresql.org 2023 [ + + ]:CBC 807 : if (!attrsOnly)
3134 peter_e@gmx.net 2024 : 736 : appendStringInfoString(&buf, " (");
3385 rhaas@postgresql.org 2025 : 807 : sep = "";
2026 [ + + ]: 1690 : for (keyno = 0; keyno < form->partnatts; keyno++)
2027 : : {
2028 : 883 : AttrNumber attnum = form->partattrs.values[keyno];
2029 : : Oid keycoltype;
2030 : : Oid keycolcollation;
2031 : : Oid partcoll;
2032 : :
2033 : 883 : appendStringInfoString(&buf, sep);
2034 : 883 : sep = ", ";
2035 [ + + ]: 883 : if (attnum != 0)
2036 : : {
2037 : : /* Simple attribute reference */
2038 : : char *attname;
2039 : : int32 keycoltypmod;
2040 : :
2953 alvherre@alvh.no-ip. 2041 : 804 : attname = get_attname(relid, attnum, false);
3385 rhaas@postgresql.org 2042 : 804 : appendStringInfoString(&buf, quote_identifier(attname));
2043 : 804 : get_atttypetypmodcoll(relid, attnum,
2044 : : &keycoltype, &keycoltypmod,
2045 : : &keycolcollation);
2046 : : }
2047 : : else
2048 : : {
2049 : : /* Expression */
2050 : : Node *partkey;
2051 : :
2052 [ - + ]: 79 : if (partexpr_item == NULL)
3385 rhaas@postgresql.org 2053 [ # # ]:UBC 0 : elog(ERROR, "too few entries in partexprs list");
3385 rhaas@postgresql.org 2054 :CBC 79 : partkey = (Node *) lfirst(partexpr_item);
2435 tgl@sss.pgh.pa.us 2055 : 79 : partexpr_item = lnext(partexprs, partexpr_item);
2056 : :
2057 : : /* Deparse */
3385 rhaas@postgresql.org 2058 : 79 : str = deparse_expression_pretty(partkey, context, false, false,
2059 : : prettyFlags, 0);
2060 : : /* Need parens if it's not a bare function call */
3167 tgl@sss.pgh.pa.us 2061 [ + + ]: 79 : if (looks_like_function(partkey))
2062 : 28 : appendStringInfoString(&buf, str);
2063 : : else
2064 : 51 : appendStringInfo(&buf, "(%s)", str);
2065 : :
3385 rhaas@postgresql.org 2066 : 79 : keycoltype = exprType(partkey);
2067 : 79 : keycolcollation = exprCollation(partkey);
2068 : : }
2069 : :
2070 : : /* Add collation, if not default for column */
2071 : 883 : partcoll = partcollation->values[keyno];
3299 2072 [ + + + + : 883 : if (!attrsOnly && OidIsValid(partcoll) && partcoll != keycolcollation)
+ + ]
3385 2073 : 3 : appendStringInfo(&buf, " COLLATE %s",
2074 : : generate_collation_name((partcoll)));
2075 : :
2076 : : /* Add the operator class name, if not default */
3299 2077 [ + + ]: 883 : if (!attrsOnly)
2078 : 785 : get_opclass_name(partclass->values[keyno], keycoltype, &buf);
2079 : : }
2080 : :
2081 [ + + ]: 807 : if (!attrsOnly)
2082 : 736 : appendStringInfoChar(&buf, ')');
2083 : :
2084 : : /* Clean up */
3385 2085 : 807 : ReleaseSysCache(tuple);
2086 : :
2087 : 807 : return buf.data;
2088 : : }
2089 : :
2090 : : /*
2091 : : * pg_get_partition_constraintdef
2092 : : *
2093 : : * Returns partition constraint expression as a string for the input relation
2094 : : */
2095 : : Datum
3228 2096 : 115 : pg_get_partition_constraintdef(PG_FUNCTION_ARGS)
2097 : : {
3224 bruce@momjian.us 2098 : 115 : Oid relationId = PG_GETARG_OID(0);
2099 : : Expr *constr_expr;
2100 : : int prettyFlags;
2101 : : List *context;
2102 : : char *consrc;
2103 : :
3228 rhaas@postgresql.org 2104 : 115 : constr_expr = get_partition_qual_relid(relationId);
2105 : :
2106 : : /* Quick exit if no partition constraint */
2107 [ + + ]: 115 : if (constr_expr == NULL)
2108 : 12 : PG_RETURN_NULL();
2109 : :
2110 : : /*
2111 : : * Deparse and return the constraint expression.
2112 : : */
2113 : 103 : prettyFlags = PRETTYFLAG_INDENT;
2114 : 103 : context = deparse_context_for(get_relation_name(relationId), relationId);
2115 : 103 : consrc = deparse_expression_pretty((Node *) constr_expr, context, false,
2116 : : false, prettyFlags, 0);
2117 : :
2118 : 103 : PG_RETURN_TEXT_P(string_to_text(consrc));
2119 : : }
2120 : :
2121 : : /*
2122 : : * pg_get_partconstrdef_string
2123 : : *
2124 : : * Returns the partition constraint as a C-string for the input relation, with
2125 : : * the given alias. No pretty-printing.
2126 : : */
2127 : : char *
2538 alvherre@alvh.no-ip. 2128 : 55 : pg_get_partconstrdef_string(Oid partitionId, char *aliasname)
2129 : : {
2130 : : Expr *constr_expr;
2131 : : List *context;
2132 : :
2133 : 55 : constr_expr = get_partition_qual_relid(partitionId);
2134 : 55 : context = deparse_context_for(aliasname, partitionId);
2135 : :
2136 : 55 : return deparse_expression((Node *) constr_expr, context, true, false);
2137 : : }
2138 : :
2139 : : /*
2140 : : * pg_get_constraintdef
2141 : : *
2142 : : * Returns the definition for the constraint, ie, everything that needs to
2143 : : * appear after "ALTER TABLE ... ADD CONSTRAINT <constraintname>".
2144 : : */
2145 : : Datum
8612 tgl@sss.pgh.pa.us 2146 : 1093 : pg_get_constraintdef(PG_FUNCTION_ARGS)
2147 : : {
8593 bruce@momjian.us 2148 : 1093 : Oid constraintId = PG_GETARG_OID(0);
2149 : : int prettyFlags;
2150 : : char *res;
2151 : :
4788 tgl@sss.pgh.pa.us 2152 : 1093 : prettyFlags = PRETTYFLAG_INDENT;
2153 : :
3519 rhaas@postgresql.org 2154 : 1093 : res = pg_get_constraintdef_worker(constraintId, false, prettyFlags, true);
2155 : :
2156 [ + + ]: 1093 : if (res == NULL)
2157 : 3 : PG_RETURN_NULL();
2158 : :
2159 : 1090 : PG_RETURN_TEXT_P(string_to_text(res));
2160 : : }
2161 : :
2162 : : Datum
8264 tgl@sss.pgh.pa.us 2163 : 2427 : pg_get_constraintdef_ext(PG_FUNCTION_ARGS)
2164 : : {
2165 : 2427 : Oid constraintId = PG_GETARG_OID(0);
2166 : 2427 : bool pretty = PG_GETARG_BOOL(1);
2167 : : int prettyFlags;
2168 : : char *res;
2169 : :
1448 2170 [ + + ]: 2427 : prettyFlags = GET_PRETTY_FLAGS(pretty);
2171 : :
3519 rhaas@postgresql.org 2172 : 2427 : res = pg_get_constraintdef_worker(constraintId, false, prettyFlags, true);
2173 : :
2174 [ - + ]: 2427 : if (res == NULL)
3519 rhaas@postgresql.org 2175 :UBC 0 : PG_RETURN_NULL();
2176 : :
3519 rhaas@postgresql.org 2177 :CBC 2427 : PG_RETURN_TEXT_P(string_to_text(res));
2178 : : }
2179 : :
2180 : : /*
2181 : : * Internal version that returns a full ALTER TABLE ... ADD CONSTRAINT command
2182 : : */
2183 : : char *
3768 tgl@sss.pgh.pa.us 2184 : 328 : pg_get_constraintdef_command(Oid constraintId)
2185 : : {
3519 rhaas@postgresql.org 2186 : 328 : return pg_get_constraintdef_worker(constraintId, true, 0, false);
2187 : : }
2188 : :
2189 : : /*
2190 : : * As of 9.4, we now use an MVCC snapshot for this.
2191 : : */
2192 : : static char *
7984 tgl@sss.pgh.pa.us 2193 : 3848 : pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
2194 : : int prettyFlags, bool missing_ok)
2195 : : {
2196 : : HeapTuple tup;
2197 : : Form_pg_constraint conForm;
2198 : : StringInfoData buf;
2199 : : SysScanDesc scandesc;
2200 : : ScanKeyData scankey[1];
4331 bruce@momjian.us 2201 : 3848 : Snapshot snapshot = RegisterSnapshot(GetTransactionSnapshot());
2610 andres@anarazel.de 2202 : 3848 : Relation relation = table_open(ConstraintRelationId, AccessShareLock);
2203 : :
4361 simon@2ndQuadrant.co 2204 : 3848 : ScanKeyInit(&scankey[0],
2205 : : Anum_pg_constraint_oid,
2206 : : BTEqualStrategyNumber, F_OIDEQ,
2207 : : ObjectIdGetDatum(constraintId));
2208 : :
2209 : 3848 : scandesc = systable_beginscan(relation,
2210 : : ConstraintOidIndexId,
2211 : : true,
2212 : : snapshot,
2213 : : 1,
2214 : : scankey);
2215 : :
2216 : : /*
2217 : : * We later use the tuple with SysCacheGetAttr() as if we had obtained it
2218 : : * via SearchSysCache, which works fine.
2219 : : */
2220 : 3848 : tup = systable_getnext(scandesc);
2221 : :
2222 : 3848 : UnregisterSnapshot(snapshot);
2223 : :
3519 rhaas@postgresql.org 2224 [ + + ]: 3848 : if (!HeapTupleIsValid(tup))
2225 : : {
2226 [ + - ]: 3 : if (missing_ok)
2227 : : {
2228 : 3 : systable_endscan(scandesc);
2610 andres@anarazel.de 2229 : 3 : table_close(relation, AccessShareLock);
3519 rhaas@postgresql.org 2230 : 3 : return NULL;
2231 : : }
3206 tgl@sss.pgh.pa.us 2232 [ # # ]:UBC 0 : elog(ERROR, "could not find tuple for constraint %u", constraintId);
2233 : : }
2234 : :
8612 tgl@sss.pgh.pa.us 2235 :CBC 3845 : conForm = (Form_pg_constraint) GETSTRUCT(tup);
2236 : :
2237 : 3845 : initStringInfo(&buf);
2238 : :
3768 2239 [ + + ]: 3845 : if (fullCommand)
2240 : : {
3056 2241 [ + + ]: 328 : if (OidIsValid(conForm->conrelid))
2242 : : {
2243 : : /*
2244 : : * Currently, callers want ALTER TABLE (without ONLY) for CHECK
2245 : : * constraints, and other types of constraints don't inherit
2246 : : * anyway so it doesn't matter whether we say ONLY or not. Someday
2247 : : * we might need to let callers specify whether to put ONLY in the
2248 : : * command.
2249 : : */
2250 : 321 : appendStringInfo(&buf, "ALTER TABLE %s ADD CONSTRAINT %s ",
2251 : : generate_qualified_relation_name(conForm->conrelid),
2252 : 321 : quote_identifier(NameStr(conForm->conname)));
2253 : : }
2254 : : else
2255 : : {
2256 : : /* Must be a domain constraint */
2257 [ - + ]: 7 : Assert(OidIsValid(conForm->contypid));
2258 : 7 : appendStringInfo(&buf, "ALTER DOMAIN %s ADD CONSTRAINT %s ",
2259 : : generate_qualified_type_name(conForm->contypid),
2260 : 7 : quote_identifier(NameStr(conForm->conname)));
2261 : : }
2262 : : }
2263 : :
8612 2264 [ + + + + : 3845 : switch (conForm->contype)
- + - ]
2265 : : {
2266 : 386 : case CONSTRAINT_FOREIGN:
2267 : : {
2268 : : Datum val;
2269 : : bool isnull;
2270 : : const char *string;
2271 : :
2272 : : /* Start off the constraint definition */
4518 rhaas@postgresql.org 2273 : 386 : appendStringInfoString(&buf, "FOREIGN KEY (");
2274 : :
2275 : : /* Fetch and build referencing-column list */
1086 dgustafsson@postgres 2276 : 386 : val = SysCacheGetAttrNotNull(CONSTROID, tup,
2277 : : Anum_pg_constraint_conkey);
2278 : :
2279 : : /* If it is a temporal foreign key then it uses PERIOD. */
544 peter@eisentraut.org 2280 : 386 : decompile_column_index_array(val, conForm->conrelid, conForm->conperiod, &buf);
2281 : :
2282 : : /* add foreign relation name */
8593 bruce@momjian.us 2283 : 386 : appendStringInfo(&buf, ") REFERENCES %s(",
2284 : : generate_relation_name(conForm->confrelid,
2285 : : NIL));
2286 : :
2287 : : /* Fetch and build referenced-column list */
1086 dgustafsson@postgres 2288 : 386 : val = SysCacheGetAttrNotNull(CONSTROID, tup,
2289 : : Anum_pg_constraint_confkey);
2290 : :
544 peter@eisentraut.org 2291 : 386 : decompile_column_index_array(val, conForm->confrelid, conForm->conperiod, &buf);
2292 : :
4518 rhaas@postgresql.org 2293 : 386 : appendStringInfoChar(&buf, ')');
2294 : :
2295 : : /* Add match type */
8593 bruce@momjian.us 2296 [ + - + - ]: 386 : switch (conForm->confmatchtype)
2297 : : {
2298 : 17 : case FKCONSTR_MATCH_FULL:
2299 : 17 : string = " MATCH FULL";
2300 : 17 : break;
8593 bruce@momjian.us 2301 :UBC 0 : case FKCONSTR_MATCH_PARTIAL:
2302 : 0 : string = " MATCH PARTIAL";
2303 : 0 : break;
5019 tgl@sss.pgh.pa.us 2304 :CBC 369 : case FKCONSTR_MATCH_SIMPLE:
8593 bruce@momjian.us 2305 : 369 : string = "";
2306 : 369 : break;
8593 bruce@momjian.us 2307 :UBC 0 : default:
8267 tgl@sss.pgh.pa.us 2308 [ # # ]: 0 : elog(ERROR, "unrecognized confmatchtype: %d",
2309 : : conForm->confmatchtype);
2310 : : string = ""; /* keep compiler quiet */
2311 : : break;
2312 : : }
8079 neilc@samurai.com 2313 :CBC 386 : appendStringInfoString(&buf, string);
2314 : :
2315 : : /* Add ON UPDATE and ON DELETE clauses, if needed */
8593 bruce@momjian.us 2316 [ + - + + : 386 : switch (conForm->confupdtype)
- - ]
2317 : : {
2318 : 318 : case FKCONSTR_ACTION_NOACTION:
8259 2319 : 318 : string = NULL; /* suppress default */
8593 2320 : 318 : break;
8593 bruce@momjian.us 2321 :UBC 0 : case FKCONSTR_ACTION_RESTRICT:
2322 : 0 : string = "RESTRICT";
2323 : 0 : break;
8593 bruce@momjian.us 2324 :CBC 54 : case FKCONSTR_ACTION_CASCADE:
2325 : 54 : string = "CASCADE";
2326 : 54 : break;
2327 : 14 : case FKCONSTR_ACTION_SETNULL:
2328 : 14 : string = "SET NULL";
2329 : 14 : break;
8593 bruce@momjian.us 2330 :UBC 0 : case FKCONSTR_ACTION_SETDEFAULT:
2331 : 0 : string = "SET DEFAULT";
2332 : 0 : break;
2333 : 0 : default:
8267 tgl@sss.pgh.pa.us 2334 [ # # ]: 0 : elog(ERROR, "unrecognized confupdtype: %d",
2335 : : conForm->confupdtype);
2336 : : string = NULL; /* keep compiler quiet */
2337 : : break;
2338 : : }
8441 tgl@sss.pgh.pa.us 2339 [ + + ]:CBC 386 : if (string)
bruce@momjian.us 2340 : 68 : appendStringInfo(&buf, " ON UPDATE %s", string);
2341 : :
8593 2342 [ + - + + : 386 : switch (conForm->confdeltype)
+ - ]
2343 : : {
2344 : 320 : case FKCONSTR_ACTION_NOACTION:
8259 2345 : 320 : string = NULL; /* suppress default */
8593 2346 : 320 : break;
8593 bruce@momjian.us 2347 :UBC 0 : case FKCONSTR_ACTION_RESTRICT:
2348 : 0 : string = "RESTRICT";
2349 : 0 : break;
8593 bruce@momjian.us 2350 :CBC 54 : case FKCONSTR_ACTION_CASCADE:
2351 : 54 : string = "CASCADE";
2352 : 54 : break;
2353 : 9 : case FKCONSTR_ACTION_SETNULL:
2354 : 9 : string = "SET NULL";
2355 : 9 : break;
2356 : 3 : case FKCONSTR_ACTION_SETDEFAULT:
2357 : 3 : string = "SET DEFAULT";
2358 : 3 : break;
8593 bruce@momjian.us 2359 :UBC 0 : default:
8267 tgl@sss.pgh.pa.us 2360 [ # # ]: 0 : elog(ERROR, "unrecognized confdeltype: %d",
2361 : : conForm->confdeltype);
2362 : : string = NULL; /* keep compiler quiet */
2363 : : break;
2364 : : }
8441 tgl@sss.pgh.pa.us 2365 [ + + ]:CBC 386 : if (string)
bruce@momjian.us 2366 : 66 : appendStringInfo(&buf, " ON DELETE %s", string);
2367 : :
2368 : : /*
2369 : : * Add columns specified to SET NULL or SET DEFAULT if
2370 : : * provided.
2371 : : */
1558 peter@eisentraut.org 2372 : 386 : val = SysCacheGetAttr(CONSTROID, tup,
2373 : : Anum_pg_constraint_confdelsetcols, &isnull);
2374 [ + + ]: 386 : if (!isnull)
2375 : : {
1286 drowley@postgresql.o 2376 : 6 : appendStringInfoString(&buf, " (");
544 peter@eisentraut.org 2377 : 6 : decompile_column_index_array(val, conForm->conrelid, false, &buf);
1286 drowley@postgresql.o 2378 : 6 : appendStringInfoChar(&buf, ')');
2379 : : }
2380 : :
8593 bruce@momjian.us 2381 : 386 : break;
2382 : : }
8431 2383 : 1991 : case CONSTRAINT_PRIMARY:
2384 : : case CONSTRAINT_UNIQUE:
2385 : : {
2386 : : Datum val;
2387 : : Oid indexId;
2388 : : int keyatts;
2389 : : HeapTuple indtup;
2390 : :
2391 : : /* Start off the constraint definition */
2392 [ + + ]: 1991 : if (conForm->contype == CONSTRAINT_PRIMARY)
1501 peter@eisentraut.org 2393 : 1624 : appendStringInfoString(&buf, "PRIMARY KEY ");
2394 : : else
2395 : 367 : appendStringInfoString(&buf, "UNIQUE ");
2396 : :
2397 : 1991 : indexId = conForm->conindid;
2398 : :
2399 : 1991 : indtup = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexId));
2400 [ - + ]: 1991 : if (!HeapTupleIsValid(indtup))
1501 peter@eisentraut.org 2401 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for index %u", indexId);
1501 peter@eisentraut.org 2402 [ + + ]:CBC 1991 : if (conForm->contype == CONSTRAINT_UNIQUE &&
2403 [ - + ]: 367 : ((Form_pg_index) GETSTRUCT(indtup))->indnullsnotdistinct)
1501 peter@eisentraut.org 2404 :UBC 0 : appendStringInfoString(&buf, "NULLS NOT DISTINCT ");
2405 : :
1286 drowley@postgresql.o 2406 :CBC 1991 : appendStringInfoChar(&buf, '(');
2407 : :
2408 : : /* Fetch and build target column list */
1086 dgustafsson@postgres 2409 : 1991 : val = SysCacheGetAttrNotNull(CONSTROID, tup,
2410 : : Anum_pg_constraint_conkey);
2411 : :
544 peter@eisentraut.org 2412 : 1991 : keyatts = decompile_column_index_array(val, conForm->conrelid, false, &buf);
2413 [ + + ]: 1991 : if (conForm->conperiod)
2414 : 191 : appendStringInfoString(&buf, " WITHOUT OVERLAPS");
2415 : :
4518 rhaas@postgresql.org 2416 : 1991 : appendStringInfoChar(&buf, ')');
2417 : :
2418 : : /* Build including column list (from pg_index.indkeys) */
1086 dgustafsson@postgres 2419 : 1991 : val = SysCacheGetAttrNotNull(INDEXRELID, indtup,
2420 : : Anum_pg_index_indnatts);
2750 alvherre@alvh.no-ip. 2421 [ + + ]: 1991 : if (DatumGetInt32(val) > keyatts)
2422 : : {
2423 : : Datum cols;
2424 : : Datum *keys;
2425 : : int nKeys;
2426 : : int j;
2427 : :
2899 teodor@sigaev.ru 2428 : 41 : appendStringInfoString(&buf, " INCLUDE (");
2429 : :
1086 dgustafsson@postgres 2430 : 41 : cols = SysCacheGetAttrNotNull(INDEXRELID, indtup,
2431 : : Anum_pg_index_indkey);
2432 : :
1353 peter@eisentraut.org 2433 : 41 : deconstruct_array_builtin(DatumGetArrayTypeP(cols), INT2OID,
2434 : : &keys, NULL, &nKeys);
2435 : :
2750 alvherre@alvh.no-ip. 2436 [ + + ]: 123 : for (j = keyatts; j < nKeys; j++)
2437 : : {
2438 : : char *colName;
2439 : :
2440 : 82 : colName = get_attname(conForm->conrelid,
2441 : 82 : DatumGetInt16(keys[j]), false);
2442 [ + + ]: 82 : if (j > keyatts)
2443 : 41 : appendStringInfoString(&buf, ", ");
2444 : 82 : appendStringInfoString(&buf, quote_identifier(colName));
2445 : : }
2446 : :
2899 teodor@sigaev.ru 2447 : 41 : appendStringInfoChar(&buf, ')');
2448 : : }
2750 alvherre@alvh.no-ip. 2449 : 1991 : ReleaseSysCache(indtup);
2450 : :
2451 : : /* XXX why do we only print these bits if fullCommand? */
6728 tgl@sss.pgh.pa.us 2452 [ + + + - ]: 1991 : if (fullCommand && OidIsValid(indexId))
2453 : : {
2454 : 102 : char *options = flatten_reloptions(indexId);
2455 : : Oid tblspc;
2456 : :
7196 bruce@momjian.us 2457 [ - + ]: 102 : if (options)
2458 : : {
7196 bruce@momjian.us 2459 :UBC 0 : appendStringInfo(&buf, " WITH (%s)", options);
2460 : 0 : pfree(options);
2461 : : }
2462 : :
2463 : : /*
2464 : : * Print the tablespace, unless it's the database default.
2465 : : * This is to help ALTER TABLE usage of this facility,
2466 : : * which needs this behavior to recreate exact catalog
2467 : : * state.
2468 : : */
6728 tgl@sss.pgh.pa.us 2469 :CBC 102 : tblspc = get_rel_tablespace(indexId);
2470 [ + + ]: 102 : if (OidIsValid(tblspc))
2471 : 12 : appendStringInfo(&buf, " USING INDEX TABLESPACE %s",
3189 2472 : 12 : quote_identifier(get_tablespace_name(tblspc)));
2473 : : }
2474 : :
8431 bruce@momjian.us 2475 : 1991 : break;
2476 : : }
2477 : 1182 : case CONSTRAINT_CHECK:
2478 : : {
2479 : : Datum val;
2480 : : char *conbin;
2481 : : char *consrc;
2482 : : Node *expr;
2483 : : List *context;
2484 : :
2485 : : /* Fetch constraint expression in parsetree form */
1086 dgustafsson@postgres 2486 : 1182 : val = SysCacheGetAttrNotNull(CONSTROID, tup,
2487 : : Anum_pg_constraint_conbin);
2488 : :
6564 tgl@sss.pgh.pa.us 2489 : 1182 : conbin = TextDatumGetCString(val);
8299 bruce@momjian.us 2490 : 1182 : expr = stringToNode(conbin);
2491 : :
2492 : : /* Set up deparsing context for Var nodes in constraint */
2493 [ + + ]: 1182 : if (conForm->conrelid != InvalidOid)
2494 : : {
2495 : : /* relation constraint */
5612 tgl@sss.pgh.pa.us 2496 : 1056 : context = deparse_context_for(get_relation_name(conForm->conrelid),
2497 : : conForm->conrelid);
2498 : : }
2499 : : else
2500 : : {
2501 : : /* domain constraint --- can't have Vars */
8198 2502 : 126 : context = NIL;
2503 : : }
2504 : :
8264 2505 : 1182 : consrc = deparse_expression_pretty(expr, context, false, false,
2506 : : prettyFlags, 0);
2507 : :
2508 : : /*
2509 : : * Now emit the constraint definition, adding NO INHERIT if
2510 : : * necessary.
2511 : : *
2512 : : * There are cases where the constraint expression will be
2513 : : * fully parenthesized and we don't need the outer parens ...
2514 : : * but there are other cases where we do need 'em. Be
2515 : : * conservative for now.
2516 : : *
2517 : : * Note that simply checking for leading '(' and trailing ')'
2518 : : * would NOT be good enough, consider "(x > 0) AND (y > 0)".
2519 : : */
4982 alvherre@alvh.no-ip. 2520 : 1182 : appendStringInfo(&buf, "CHECK (%s)%s",
2521 : : consrc,
2522 [ + + ]: 1182 : conForm->connoinherit ? " NO INHERIT" : "");
8431 bruce@momjian.us 2523 : 1182 : break;
2524 : : }
492 alvherre@alvh.no-ip. 2525 : 234 : case CONSTRAINT_NOTNULL:
2526 : : {
2527 [ + + ]: 234 : if (conForm->conrelid)
2528 : : {
2529 : : AttrNumber attnum;
2530 : :
2531 : 178 : attnum = extractNotNullColumn(tup);
2532 : :
2533 : 178 : appendStringInfo(&buf, "NOT NULL %s",
2534 : 178 : quote_identifier(get_attname(conForm->conrelid,
2535 : : attnum, false)));
2536 [ - + ]: 178 : if (((Form_pg_constraint) GETSTRUCT(tup))->connoinherit)
492 alvherre@alvh.no-ip. 2537 :UBC 0 : appendStringInfoString(&buf, " NO INHERIT");
2538 : : }
492 alvherre@alvh.no-ip. 2539 [ + - ]:CBC 56 : else if (conForm->contypid)
2540 : : {
2541 : : /* conkey is null for domain not-null constraints */
2542 : 56 : appendStringInfoString(&buf, "NOT NULL");
2543 : : }
2544 : 234 : break;
2545 : : }
2546 : :
5901 tgl@sss.pgh.pa.us 2547 :UBC 0 : case CONSTRAINT_TRIGGER:
2548 : :
2549 : : /*
2550 : : * There isn't an ALTER TABLE syntax for creating a user-defined
2551 : : * constraint trigger, but it seems better to print something than
2552 : : * throw an error; if we throw error then this function couldn't
2553 : : * safely be applied to all rows of pg_constraint.
2554 : : */
4518 rhaas@postgresql.org 2555 : 0 : appendStringInfoString(&buf, "TRIGGER");
5901 tgl@sss.pgh.pa.us 2556 : 0 : break;
5942 tgl@sss.pgh.pa.us 2557 :CBC 52 : case CONSTRAINT_EXCLUSION:
2558 : : {
5861 bruce@momjian.us 2559 : 52 : Oid indexOid = conForm->conindid;
2560 : : Datum val;
2561 : : Datum *elems;
2562 : : int nElems;
2563 : : int i;
2564 : : Oid *operators;
2565 : :
2566 : : /* Extract operator OIDs from the pg_constraint tuple */
1086 dgustafsson@postgres 2567 : 52 : val = SysCacheGetAttrNotNull(CONSTROID, tup,
2568 : : Anum_pg_constraint_conexclop);
2569 : :
1353 peter@eisentraut.org 2570 : 52 : deconstruct_array_builtin(DatumGetArrayTypeP(val), OIDOID,
2571 : : &elems, NULL, &nElems);
2572 : :
5942 tgl@sss.pgh.pa.us 2573 : 52 : operators = (Oid *) palloc(nElems * sizeof(Oid));
2574 [ + + ]: 114 : for (i = 0; i < nElems; i++)
2575 : 62 : operators[i] = DatumGetObjectId(elems[i]);
2576 : :
2577 : : /* pg_get_indexdef_worker does the rest */
2578 : : /* suppress tablespace because pg_dump wants it that way */
2579 : 52 : appendStringInfoString(&buf,
2580 : 52 : pg_get_indexdef_worker(indexOid,
2581 : : 0,
2582 : : operators,
2583 : : false,
2584 : : false,
2585 : : false,
2586 : : false,
2587 : : prettyFlags,
2588 : : false));
2589 : 52 : break;
2590 : : }
8612 tgl@sss.pgh.pa.us 2591 :UBC 0 : default:
8217 peter_e@gmx.net 2592 [ # # ]: 0 : elog(ERROR, "invalid constraint type \"%c\"", conForm->contype);
2593 : : break;
2594 : : }
2595 : :
6073 tgl@sss.pgh.pa.us 2596 [ + + ]:CBC 3845 : if (conForm->condeferrable)
4518 rhaas@postgresql.org 2597 : 60 : appendStringInfoString(&buf, " DEFERRABLE");
6073 tgl@sss.pgh.pa.us 2598 [ + + ]: 3845 : if (conForm->condeferred)
4518 rhaas@postgresql.org 2599 : 24 : appendStringInfoString(&buf, " INITIALLY DEFERRED");
2600 : :
2601 : : /* Validated status is irrelevant when the constraint is NOT ENFORCED. */
428 peter@eisentraut.org 2602 [ + + ]: 3845 : if (!conForm->conenforced)
2603 : 61 : appendStringInfoString(&buf, " NOT ENFORCED");
2604 [ + + ]: 3784 : else if (!conForm->convalidated)
5400 alvherre@alvh.no-ip. 2605 : 139 : appendStringInfoString(&buf, " NOT VALID");
2606 : :
2607 : : /* Cleanup */
4361 simon@2ndQuadrant.co 2608 : 3845 : systable_endscan(scandesc);
2610 andres@anarazel.de 2609 : 3845 : table_close(relation, AccessShareLock);
2610 : :
7984 tgl@sss.pgh.pa.us 2611 : 3845 : return buf.data;
2612 : : }
2613 : :
2614 : :
2615 : : /*
2616 : : * Convert an int16[] Datum into a comma-separated list of column names
2617 : : * for the indicated relation; append the list to buf. Returns the number
2618 : : * of keys.
2619 : : */
2620 : : static int
8612 2621 : 2769 : decompile_column_index_array(Datum column_index_array, Oid relId,
2622 : : bool withPeriod, StringInfo buf)
2623 : : {
2624 : : Datum *keys;
2625 : : int nKeys;
2626 : : int j;
2627 : :
2628 : : /* Extract data from array of int16 */
1353 peter@eisentraut.org 2629 : 2769 : deconstruct_array_builtin(DatumGetArrayTypeP(column_index_array), INT2OID,
2630 : : &keys, NULL, &nKeys);
2631 : :
8612 tgl@sss.pgh.pa.us 2632 [ + + ]: 6676 : for (j = 0; j < nKeys; j++)
2633 : : {
2634 : : char *colName;
2635 : :
2953 alvherre@alvh.no-ip. 2636 : 3907 : colName = get_attname(relId, DatumGetInt16(keys[j]), false);
2637 : :
8612 tgl@sss.pgh.pa.us 2638 [ + + ]: 3907 : if (j == 0)
8079 neilc@samurai.com 2639 : 2769 : appendStringInfoString(buf, quote_identifier(colName));
2640 : : else
544 peter@eisentraut.org 2641 [ + + ]: 1252 : appendStringInfo(buf, ", %s%s",
2642 [ + + ]: 114 : (withPeriod && j == nKeys - 1) ? "PERIOD " : "",
2643 : : quote_identifier(colName));
2644 : : }
2645 : :
2750 alvherre@alvh.no-ip. 2646 : 2769 : return nKeys;
2647 : : }
2648 : :
2649 : :
2650 : : /* ----------
2651 : : * pg_get_expr - Decompile an expression tree
2652 : : *
2653 : : * Input: an expression tree in nodeToString form, and a relation OID
2654 : : *
2655 : : * Output: reverse-listed expression
2656 : : *
2657 : : * Currently, the expression can only refer to a single relation, namely
2658 : : * the one specified by the second parameter. This is sufficient for
2659 : : * partial indexes, column default expressions, etc. We also support
2660 : : * Var-free expressions, for which the OID can be InvalidOid.
2661 : : *
2662 : : * If the OID is nonzero but not actually valid, don't throw an error,
2663 : : * just return NULL. This is a bit questionable, but it's what we've
2664 : : * done historically, and it can help avoid unwanted failures when
2665 : : * examining catalog entries for just-deleted relations.
2666 : : *
2667 : : * We expect this function to work, or throw a reasonably clean error,
2668 : : * for any node tree that can appear in a catalog pg_node_tree column.
2669 : : * Query trees, such as those appearing in pg_rewrite.ev_action, are
2670 : : * not supported. Nor are expressions in more than one relation, which
2671 : : * can appear in places like pg_rewrite.ev_qual.
2672 : : * ----------
2673 : : */
2674 : : Datum
9008 tgl@sss.pgh.pa.us 2675 : 4652 : pg_get_expr(PG_FUNCTION_ARGS)
2676 : : {
3290 noah@leadboat.com 2677 : 4652 : text *expr = PG_GETARG_TEXT_PP(0);
8259 bruce@momjian.us 2678 : 4652 : Oid relid = PG_GETARG_OID(1);
2679 : : text *result;
2680 : : int prettyFlags;
2681 : :
4788 tgl@sss.pgh.pa.us 2682 : 4652 : prettyFlags = PRETTYFLAG_INDENT;
2683 : :
765 2684 : 4652 : result = pg_get_expr_worker(expr, relid, prettyFlags);
2685 [ + - ]: 4652 : if (result)
2686 : 4652 : PG_RETURN_TEXT_P(result);
2687 : : else
765 tgl@sss.pgh.pa.us 2688 :UBC 0 : PG_RETURN_NULL();
2689 : : }
2690 : :
2691 : : Datum
8264 tgl@sss.pgh.pa.us 2692 :CBC 431 : pg_get_expr_ext(PG_FUNCTION_ARGS)
2693 : : {
3290 noah@leadboat.com 2694 : 431 : text *expr = PG_GETARG_TEXT_PP(0);
8259 bruce@momjian.us 2695 : 431 : Oid relid = PG_GETARG_OID(1);
8264 tgl@sss.pgh.pa.us 2696 : 431 : bool pretty = PG_GETARG_BOOL(2);
2697 : : text *result;
2698 : : int prettyFlags;
2699 : :
1448 2700 [ + - ]: 431 : prettyFlags = GET_PRETTY_FLAGS(pretty);
2701 : :
765 2702 : 431 : result = pg_get_expr_worker(expr, relid, prettyFlags);
2703 [ + - ]: 431 : if (result)
2704 : 431 : PG_RETURN_TEXT_P(result);
2705 : : else
765 tgl@sss.pgh.pa.us 2706 :UBC 0 : PG_RETURN_NULL();
2707 : : }
2708 : :
2709 : : static text *
765 tgl@sss.pgh.pa.us 2710 :CBC 5083 : pg_get_expr_worker(text *expr, Oid relid, int prettyFlags)
2711 : : {
2712 : : Node *node;
2713 : : Node *tst;
2714 : : Relids relids;
2715 : : List *context;
2716 : : char *exprstr;
2717 : 5083 : Relation rel = NULL;
2718 : : char *str;
2719 : :
2720 : : /* Convert input pg_node_tree (really TEXT) object to C string */
6564 2721 : 5083 : exprstr = text_to_cstring(expr);
2722 : :
2723 : : /* Convert expression to node tree */
9008 2724 : 5083 : node = (Node *) stringToNode(exprstr);
2725 : :
6137 2726 : 5083 : pfree(exprstr);
2727 : :
2728 : : /*
2729 : : * Throw error if the input is a querytree rather than an expression tree.
2730 : : * While we could support queries here, there seems no very good reason
2731 : : * to. In most such catalog columns, we'll see a List of Query nodes, or
2732 : : * even nested Lists, so drill down to a non-List node before checking.
2733 : : */
1526 2734 : 5083 : tst = node;
2735 [ + - - + ]: 5083 : while (tst && IsA(tst, List))
1526 tgl@sss.pgh.pa.us 2736 :UBC 0 : tst = linitial((List *) tst);
1526 tgl@sss.pgh.pa.us 2737 [ + - - + ]:CBC 5083 : if (tst && IsA(tst, Query))
1526 tgl@sss.pgh.pa.us 2738 [ # # ]:UBC 0 : ereport(ERROR,
2739 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2740 : : errmsg("input is a query, not an expression")));
2741 : :
2742 : : /*
2743 : : * Throw error if the expression contains Vars we won't be able to
2744 : : * deparse.
2745 : : */
1526 tgl@sss.pgh.pa.us 2746 :CBC 5083 : relids = pull_varnos(NULL, node);
2747 [ + + ]: 5083 : if (OidIsValid(relid))
2748 : : {
2749 [ - + ]: 5041 : if (!bms_is_subset(relids, bms_make_singleton(1)))
1526 tgl@sss.pgh.pa.us 2750 [ # # ]:UBC 0 : ereport(ERROR,
2751 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2752 : : errmsg("expression contains variables of more than one relation")));
2753 : : }
2754 : : else
2755 : : {
1526 tgl@sss.pgh.pa.us 2756 [ - + ]:CBC 42 : if (!bms_is_empty(relids))
1526 tgl@sss.pgh.pa.us 2757 [ # # ]:UBC 0 : ereport(ERROR,
2758 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2759 : : errmsg("expression contains variables")));
2760 : : }
2761 : :
2762 : : /*
2763 : : * Prepare deparse context if needed. If we are deparsing with a relid,
2764 : : * we need to transiently open and lock the rel, to make sure it won't go
2765 : : * away underneath us. (set_relation_column_names would lock it anyway,
2766 : : * so this isn't really introducing any new behavior.)
2767 : : */
6137 tgl@sss.pgh.pa.us 2768 [ + + ]:CBC 5083 : if (OidIsValid(relid))
2769 : : {
765 2770 : 5041 : rel = try_relation_open(relid, AccessShareLock);
2771 [ - + ]: 5041 : if (rel == NULL)
765 tgl@sss.pgh.pa.us 2772 :UBC 0 : return NULL;
765 tgl@sss.pgh.pa.us 2773 :CBC 5041 : context = deparse_context_for(RelationGetRelationName(rel), relid);
2774 : : }
2775 : : else
6137 2776 : 42 : context = NIL;
2777 : :
2778 : : /* Deparse */
8264 2779 : 5083 : str = deparse_expression_pretty(node, context, false, false,
2780 : : prettyFlags, 0);
2781 : :
765 2782 [ + + ]: 5083 : if (rel != NULL)
2783 : 5041 : relation_close(rel, AccessShareLock);
2784 : :
6137 2785 : 5083 : return string_to_text(str);
2786 : : }
2787 : :
2788 : :
2789 : : /* ----------
2790 : : * pg_get_userbyid - Get a user name by roleid and
2791 : : * fallback to 'unknown (OID=n)'
2792 : : * ----------
2793 : : */
2794 : : Datum
9406 2795 : 926 : pg_get_userbyid(PG_FUNCTION_ARGS)
2796 : : {
7565 2797 : 926 : Oid roleid = PG_GETARG_OID(0);
2798 : : Name result;
2799 : : HeapTuple roletup;
2800 : : Form_pg_authid role_rec;
2801 : :
2802 : : /*
2803 : : * Allocate space for the result
2804 : : */
9406 2805 : 926 : result = (Name) palloc(NAMEDATALEN);
9625 bruce@momjian.us 2806 : 926 : memset(NameStr(*result), 0, NAMEDATALEN);
2807 : :
2808 : : /*
2809 : : * Get the pg_authid entry and print the result
2810 : : */
5873 rhaas@postgresql.org 2811 : 926 : roletup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
7565 tgl@sss.pgh.pa.us 2812 [ + - ]: 926 : if (HeapTupleIsValid(roletup))
2813 : : {
2814 : 926 : role_rec = (Form_pg_authid) GETSTRUCT(roletup);
2043 peter@eisentraut.org 2815 : 926 : *result = role_rec->rolname;
7565 tgl@sss.pgh.pa.us 2816 : 926 : ReleaseSysCache(roletup);
2817 : : }
2818 : : else
7565 tgl@sss.pgh.pa.us 2819 :UBC 0 : sprintf(NameStr(*result), "unknown (OID=%u)", roleid);
2820 : :
9406 tgl@sss.pgh.pa.us 2821 :CBC 926 : PG_RETURN_NAME(result);
2822 : : }
2823 : :
2824 : :
2825 : : /*
2826 : : * pg_get_serial_sequence
2827 : : * Get the name of the sequence used by an identity or serial column,
2828 : : * formatted suitably for passing to setval, nextval or currval.
2829 : : * First parameter is not treated as double-quoted, second parameter
2830 : : * is --- see documentation for reason.
2831 : : */
2832 : : Datum
7933 2833 : 6 : pg_get_serial_sequence(PG_FUNCTION_ARGS)
2834 : : {
3290 noah@leadboat.com 2835 : 6 : text *tablename = PG_GETARG_TEXT_PP(0);
6564 tgl@sss.pgh.pa.us 2836 : 6 : text *columnname = PG_GETARG_TEXT_PP(1);
2837 : : RangeVar *tablerv;
2838 : : Oid tableOid;
2839 : : char *column;
2840 : : AttrNumber attnum;
7868 bruce@momjian.us 2841 : 6 : Oid sequenceId = InvalidOid;
2842 : : Relation depRel;
2843 : : ScanKeyData key[3];
2844 : : SysScanDesc scan;
2845 : : HeapTuple tup;
2846 : :
2847 : : /* Look up table name. Can't lock it - we might not have privileges. */
7597 neilc@samurai.com 2848 : 6 : tablerv = makeRangeVarFromNameList(textToQualifiedNameList(tablename));
5219 rhaas@postgresql.org 2849 : 6 : tableOid = RangeVarGetRelid(tablerv, NoLock, false);
2850 : :
2851 : : /* Get the number of the column */
6564 tgl@sss.pgh.pa.us 2852 : 6 : column = text_to_cstring(columnname);
2853 : :
7933 2854 : 6 : attnum = get_attnum(tableOid, column);
2855 [ - + ]: 6 : if (attnum == InvalidAttrNumber)
7933 tgl@sss.pgh.pa.us 2856 [ # # ]:UBC 0 : ereport(ERROR,
2857 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
2858 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
2859 : : column, tablerv->relname)));
2860 : :
2861 : : /* Search the dependency table for the dependent sequence */
2610 andres@anarazel.de 2862 :CBC 6 : depRel = table_open(DependRelationId, AccessShareLock);
2863 : :
7933 tgl@sss.pgh.pa.us 2864 : 6 : ScanKeyInit(&key[0],
2865 : : Anum_pg_depend_refclassid,
2866 : : BTEqualStrategyNumber, F_OIDEQ,
2867 : : ObjectIdGetDatum(RelationRelationId));
2868 : 6 : ScanKeyInit(&key[1],
2869 : : Anum_pg_depend_refobjid,
2870 : : BTEqualStrategyNumber, F_OIDEQ,
2871 : : ObjectIdGetDatum(tableOid));
2872 : 6 : ScanKeyInit(&key[2],
2873 : : Anum_pg_depend_refobjsubid,
2874 : : BTEqualStrategyNumber, F_INT4EQ,
2875 : : Int32GetDatum(attnum));
2876 : :
7640 2877 : 6 : scan = systable_beginscan(depRel, DependReferenceIndexId, true,
2878 : : NULL, 3, key);
2879 : :
7933 2880 [ + - ]: 15 : while (HeapTupleIsValid(tup = systable_getnext(scan)))
2881 : : {
7868 bruce@momjian.us 2882 : 15 : Form_pg_depend deprec = (Form_pg_depend) GETSTRUCT(tup);
2883 : :
2884 : : /*
2885 : : * Look for an auto dependency (serial column) or internal dependency
2886 : : * (identity column) of a sequence on a column. (We need the relkind
2887 : : * test because indexes can also have auto dependencies on columns.)
2888 : : */
7640 tgl@sss.pgh.pa.us 2889 [ + + ]: 15 : if (deprec->classid == RelationRelationId &&
7933 2890 [ + - ]: 6 : deprec->objsubid == 0 &&
3103 peter_e@gmx.net 2891 [ + + ]: 6 : (deprec->deptype == DEPENDENCY_AUTO ||
2892 [ + - + - ]: 9 : deprec->deptype == DEPENDENCY_INTERNAL) &&
7065 tgl@sss.pgh.pa.us 2893 : 6 : get_rel_relkind(deprec->objid) == RELKIND_SEQUENCE)
2894 : : {
7933 2895 : 6 : sequenceId = deprec->objid;
2896 : 6 : break;
2897 : : }
2898 : : }
2899 : :
2900 : 6 : systable_endscan(scan);
2610 andres@anarazel.de 2901 : 6 : table_close(depRel, AccessShareLock);
2902 : :
7933 tgl@sss.pgh.pa.us 2903 [ + - ]: 6 : if (OidIsValid(sequenceId))
2904 : : {
2905 : : char *result;
2906 : :
3768 2907 : 6 : result = generate_qualified_relation_name(sequenceId);
2908 : :
7933 2909 : 6 : PG_RETURN_TEXT_P(string_to_text(result));
2910 : : }
2911 : :
7933 tgl@sss.pgh.pa.us 2912 :UBC 0 : PG_RETURN_NULL();
2913 : : }
2914 : :
2915 : :
2916 : : /*
2917 : : * pg_get_functiondef
2918 : : * Returns the complete "CREATE OR REPLACE FUNCTION ..." statement for
2919 : : * the specified function.
2920 : : *
2921 : : * Note: if you change the output format of this function, be careful not
2922 : : * to break psql's rules (in \ef and \sf) for identifying the start of the
2923 : : * function body. To wit: the function body starts on a line that begins with
2924 : : * "AS ", "BEGIN ", or "RETURN ", and no preceding line will look like that.
2925 : : */
2926 : : Datum
6399 tgl@sss.pgh.pa.us 2927 :CBC 86 : pg_get_functiondef(PG_FUNCTION_ARGS)
2928 : : {
2929 : 86 : Oid funcid = PG_GETARG_OID(0);
2930 : : StringInfoData buf;
2931 : : StringInfoData dq;
2932 : : HeapTuple proctup;
2933 : : Form_pg_proc proc;
2934 : : bool isfunction;
2935 : : Datum tmp;
2936 : : bool isnull;
2937 : : const char *prosrc;
2938 : : const char *name;
2939 : : const char *nsp;
2940 : : float4 procost;
2941 : : int oldlen;
2942 : :
2943 : 86 : initStringInfo(&buf);
2944 : :
2945 : : /* Look up the function */
5873 rhaas@postgresql.org 2946 : 86 : proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
6399 tgl@sss.pgh.pa.us 2947 [ + + ]: 86 : if (!HeapTupleIsValid(proctup))
3519 rhaas@postgresql.org 2948 : 3 : PG_RETURN_NULL();
2949 : :
6399 tgl@sss.pgh.pa.us 2950 : 83 : proc = (Form_pg_proc) GETSTRUCT(proctup);
2951 : 83 : name = NameStr(proc->proname);
2952 : :
2935 peter_e@gmx.net 2953 [ - + ]: 83 : if (proc->prokind == PROKIND_AGGREGATE)
6399 tgl@sss.pgh.pa.us 2954 [ # # ]:UBC 0 : ereport(ERROR,
2955 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2956 : : errmsg("\"%s\" is an aggregate function", name)));
2957 : :
2935 peter_e@gmx.net 2958 :CBC 83 : isfunction = (proc->prokind != PROKIND_PROCEDURE);
2959 : :
2960 : : /*
2961 : : * We always qualify the function name, to ensure the right function gets
2962 : : * replaced.
2963 : : */
1692 tgl@sss.pgh.pa.us 2964 : 83 : nsp = get_namespace_name_or_temp(proc->pronamespace);
2952 peter_e@gmx.net 2965 [ + + ]: 83 : appendStringInfo(&buf, "CREATE OR REPLACE %s %s(",
2966 : : isfunction ? "FUNCTION" : "PROCEDURE",
2967 : : quote_qualified_identifier(nsp, name));
6310 2968 : 83 : (void) print_function_arguments(&buf, proctup, false, true);
2952 2969 : 83 : appendStringInfoString(&buf, ")\n");
2970 [ + + ]: 83 : if (isfunction)
2971 : : {
2972 : 73 : appendStringInfoString(&buf, " RETURNS ");
2973 : 73 : print_function_rettype(&buf, proctup);
2974 : 73 : appendStringInfoChar(&buf, '\n');
2975 : : }
2976 : :
3976 2977 : 83 : print_function_trftypes(&buf, proctup);
2978 : :
2952 2979 : 83 : appendStringInfo(&buf, " LANGUAGE %s\n",
3189 tgl@sss.pgh.pa.us 2980 : 83 : quote_identifier(get_language_name(proc->prolang, false)));
2981 : :
2982 : : /* Emit some miscellaneous options on one line */
6399 2983 : 83 : oldlen = buf.len;
2984 : :
2935 peter_e@gmx.net 2985 [ - + ]: 83 : if (proc->prokind == PROKIND_WINDOW)
6283 tgl@sss.pgh.pa.us 2986 :UBC 0 : appendStringInfoString(&buf, " WINDOW");
6399 tgl@sss.pgh.pa.us 2987 [ + + + - ]:CBC 83 : switch (proc->provolatile)
2988 : : {
2989 : 6 : case PROVOLATILE_IMMUTABLE:
2990 : 6 : appendStringInfoString(&buf, " IMMUTABLE");
2991 : 6 : break;
2992 : 15 : case PROVOLATILE_STABLE:
2993 : 15 : appendStringInfoString(&buf, " STABLE");
2994 : 15 : break;
2995 : 62 : case PROVOLATILE_VOLATILE:
2996 : 62 : break;
2997 : : }
2998 : :
3610 rhaas@postgresql.org 2999 [ + - + - ]: 83 : switch (proc->proparallel)
3000 : : {
3001 : 14 : case PROPARALLEL_SAFE:
3002 : 14 : appendStringInfoString(&buf, " PARALLEL SAFE");
3003 : 14 : break;
3610 rhaas@postgresql.org 3004 :UBC 0 : case PROPARALLEL_RESTRICTED:
3005 : 0 : appendStringInfoString(&buf, " PARALLEL RESTRICTED");
3006 : 0 : break;
3610 rhaas@postgresql.org 3007 :CBC 69 : case PROPARALLEL_UNSAFE:
3008 : 69 : break;
3009 : : }
3010 : :
6399 tgl@sss.pgh.pa.us 3011 [ + + ]: 83 : if (proc->proisstrict)
3012 : 25 : appendStringInfoString(&buf, " STRICT");
3013 [ + + ]: 83 : if (proc->prosecdef)
3014 : 3 : appendStringInfoString(&buf, " SECURITY DEFINER");
3944 3015 [ - + ]: 83 : if (proc->proleakproof)
3944 tgl@sss.pgh.pa.us 3016 :UBC 0 : appendStringInfoString(&buf, " LEAKPROOF");
3017 : :
3018 : : /* This code for the default cost and rows should match functioncmds.c */
6399 tgl@sss.pgh.pa.us 3019 [ + - ]:CBC 83 : if (proc->prolang == INTERNALlanguageId ||
3020 [ + + ]: 83 : proc->prolang == ClanguageId)
3021 : 5 : procost = 1;
3022 : : else
3023 : 78 : procost = 100;
3024 [ + + ]: 83 : if (proc->procost != procost)
3025 : 3 : appendStringInfo(&buf, " COST %g", proc->procost);
3026 : :
3027 [ + + - + ]: 83 : if (proc->prorows > 0 && proc->prorows != 1000)
6399 tgl@sss.pgh.pa.us 3028 :UBC 0 : appendStringInfo(&buf, " ROWS %g", proc->prorows);
3029 : :
2591 tgl@sss.pgh.pa.us 3030 [ - + ]:CBC 83 : if (proc->prosupport)
3031 : : {
3032 : : Oid argtypes[1];
3033 : :
3034 : : /*
3035 : : * We should qualify the support function's name if it wouldn't be
3036 : : * resolved by lookup in the current search path.
3037 : : */
2591 tgl@sss.pgh.pa.us 3038 :UBC 0 : argtypes[0] = INTERNALOID;
3039 : 0 : appendStringInfo(&buf, " SUPPORT %s",
3040 : : generate_function_name(proc->prosupport, 1,
3041 : : NIL, argtypes,
3042 : : false, NULL, false));
3043 : : }
3044 : :
6399 tgl@sss.pgh.pa.us 3045 [ + + ]:CBC 83 : if (oldlen != buf.len)
3046 : 32 : appendStringInfoChar(&buf, '\n');
3047 : :
3048 : : /* Emit any proconfig options, one per line */
3049 : 83 : tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_proconfig, &isnull);
3050 [ + + ]: 83 : if (!isnull)
3051 : : {
6121 bruce@momjian.us 3052 : 3 : ArrayType *a = DatumGetArrayTypeP(tmp);
3053 : : int i;
3054 : :
6399 tgl@sss.pgh.pa.us 3055 [ - + ]: 3 : Assert(ARR_ELEMTYPE(a) == TEXTOID);
3056 [ - + ]: 3 : Assert(ARR_NDIM(a) == 1);
3057 [ - + ]: 3 : Assert(ARR_LBOUND(a)[0] == 1);
3058 : :
3059 [ + + ]: 21 : for (i = 1; i <= ARR_DIMS(a)[0]; i++)
3060 : : {
3061 : : Datum d;
3062 : :
3063 : 18 : d = array_ref(a, 1, &i,
3064 : : -1 /* varlenarray */ ,
3065 : : -1 /* TEXT's typlen */ ,
3066 : : false /* TEXT's typbyval */ ,
3067 : : TYPALIGN_INT /* TEXT's typalign */ ,
3068 : : &isnull);
3069 [ + - ]: 18 : if (!isnull)
3070 : : {
3071 : 18 : char *configitem = TextDatumGetCString(d);
3072 : : char *pos;
3073 : :
3074 : 18 : pos = strchr(configitem, '=');
3075 [ - + ]: 18 : if (pos == NULL)
6399 tgl@sss.pgh.pa.us 3076 :UBC 0 : continue;
6399 tgl@sss.pgh.pa.us 3077 :CBC 18 : *pos++ = '\0';
3078 : :
3079 : 18 : appendStringInfo(&buf, " SET %s TO ",
3080 : : quote_identifier(configitem));
3081 : :
3082 : : /*
3083 : : * Variables that are marked GUC_LIST_QUOTE were already fully
3084 : : * quoted by flatten_set_variable_args() before they were put
3085 : : * into the proconfig array. However, because the quoting
3086 : : * rules used there aren't exactly like SQL's, we have to
3087 : : * break the list value apart and then quote the elements as
3088 : : * string literals. (The elements may be double-quoted as-is,
3089 : : * but we can't just feed them to the SQL parser; it would do
3090 : : * the wrong thing with elements that are zero-length or
3091 : : * longer than NAMEDATALEN.) Also, we need a special case for
3092 : : * empty lists.
3093 : : *
3094 : : * Variables that are not so marked should just be emitted as
3095 : : * simple string literals. If the variable is not known to
3096 : : * guc.c, we'll do that; this makes it unsafe to use
3097 : : * GUC_LIST_QUOTE for extension variables.
3098 : : */
2916 3099 [ + + ]: 18 : if (GetConfigOptionFlags(configitem, true) & GUC_LIST_QUOTE)
3100 : : {
3101 : : List *namelist;
3102 : : ListCell *lc;
3103 : :
3104 : : /* Parse string into list of identifiers */
2784 3105 [ - + ]: 9 : if (!SplitGUCList(pos, ',', &namelist))
3106 : : {
3107 : : /* this shouldn't fail really */
2784 tgl@sss.pgh.pa.us 3108 [ # # ]:UBC 0 : elog(ERROR, "invalid list syntax in proconfig item");
3109 : : }
3110 : : /* Special case: represent an empty list as NULL */
131 tgl@sss.pgh.pa.us 3111 [ + + ]:GNC 9 : if (namelist == NIL)
3112 : 3 : appendStringInfoString(&buf, "NULL");
2784 tgl@sss.pgh.pa.us 3113 [ + + + + :CBC 24 : foreach(lc, namelist)
+ + ]
3114 : : {
3115 : 15 : char *curname = (char *) lfirst(lc);
3116 : :
3117 : 15 : simple_quote_literal(&buf, curname);
2435 3118 [ + + ]: 15 : if (lnext(namelist, lc))
2784 3119 : 9 : appendStringInfoString(&buf, ", ");
3120 : : }
3121 : : }
3122 : : else
6399 3123 : 9 : simple_quote_literal(&buf, pos);
3124 : 18 : appendStringInfoChar(&buf, '\n');
3125 : : }
3126 : : }
3127 : : }
3128 : :
3129 : : /* And finally the function definition ... */
1657 3130 : 83 : (void) SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
1803 peter@eisentraut.org 3131 [ + + + + ]: 83 : if (proc->prolang == SQLlanguageId && !isnull)
3132 : : {
3133 : 57 : print_function_sqlbody(&buf, proctup);
3134 : : }
3135 : : else
3136 : : {
1768 tgl@sss.pgh.pa.us 3137 : 26 : appendStringInfoString(&buf, "AS ");
3138 : :
3139 : 26 : tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
3140 [ + + ]: 26 : if (!isnull)
3141 : : {
3142 : 5 : simple_quote_literal(&buf, TextDatumGetCString(tmp));
3143 : 5 : appendStringInfoString(&buf, ", "); /* assume prosrc isn't null */
3144 : : }
3145 : :
1086 dgustafsson@postgres 3146 : 26 : tmp = SysCacheGetAttrNotNull(PROCOID, proctup, Anum_pg_proc_prosrc);
1768 tgl@sss.pgh.pa.us 3147 : 26 : prosrc = TextDatumGetCString(tmp);
3148 : :
3149 : : /*
3150 : : * We always use dollar quoting. Figure out a suitable delimiter.
3151 : : *
3152 : : * Since the user is likely to be editing the function body string, we
3153 : : * shouldn't use a short delimiter that he might easily create a
3154 : : * conflict with. Hence prefer "$function$"/"$procedure$", but extend
3155 : : * if needed.
3156 : : */
3157 : 26 : initStringInfo(&dq);
3158 : 26 : appendStringInfoChar(&dq, '$');
3159 [ + + ]: 26 : appendStringInfoString(&dq, (isfunction ? "function" : "procedure"));
3160 [ - + ]: 26 : while (strstr(prosrc, dq.data) != NULL)
1768 tgl@sss.pgh.pa.us 3161 :UBC 0 : appendStringInfoChar(&dq, 'x');
1768 tgl@sss.pgh.pa.us 3162 :CBC 26 : appendStringInfoChar(&dq, '$');
3163 : :
3164 : 26 : appendBinaryStringInfo(&buf, dq.data, dq.len);
3165 : 26 : appendStringInfoString(&buf, prosrc);
3166 : 26 : appendBinaryStringInfo(&buf, dq.data, dq.len);
3167 : : }
3168 : :
4518 rhaas@postgresql.org 3169 : 83 : appendStringInfoChar(&buf, '\n');
3170 : :
6399 tgl@sss.pgh.pa.us 3171 : 83 : ReleaseSysCache(proctup);
3172 : :
3173 : 83 : PG_RETURN_TEXT_P(string_to_text(buf.data));
3174 : : }
3175 : :
3176 : : /*
3177 : : * pg_get_function_arguments
3178 : : * Get a nicely-formatted list of arguments for a function.
3179 : : * This is everything that would go between the parentheses in
3180 : : * CREATE FUNCTION.
3181 : : */
3182 : : Datum
6449 3183 : 2334 : pg_get_function_arguments(PG_FUNCTION_ARGS)
3184 : : {
3185 : 2334 : Oid funcid = PG_GETARG_OID(0);
3186 : : StringInfoData buf;
3187 : : HeapTuple proctup;
3188 : :
5873 rhaas@postgresql.org 3189 : 2334 : proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
6449 tgl@sss.pgh.pa.us 3190 [ + + ]: 2334 : if (!HeapTupleIsValid(proctup))
3516 rhaas@postgresql.org 3191 : 3 : PG_RETURN_NULL();
3192 : :
3193 : 2331 : initStringInfo(&buf);
3194 : :
6310 peter_e@gmx.net 3195 : 2331 : (void) print_function_arguments(&buf, proctup, false, true);
3196 : :
6449 tgl@sss.pgh.pa.us 3197 : 2331 : ReleaseSysCache(proctup);
3198 : :
3199 : 2331 : PG_RETURN_TEXT_P(string_to_text(buf.data));
3200 : : }
3201 : :
3202 : : /*
3203 : : * pg_get_function_identity_arguments
3204 : : * Get a formatted list of arguments for a function.
3205 : : * This is everything that would go between the parentheses in
3206 : : * ALTER FUNCTION, etc. In particular, don't print defaults.
3207 : : */
3208 : : Datum
6310 peter_e@gmx.net 3209 : 2068 : pg_get_function_identity_arguments(PG_FUNCTION_ARGS)
3210 : : {
3211 : 2068 : Oid funcid = PG_GETARG_OID(0);
3212 : : StringInfoData buf;
3213 : : HeapTuple proctup;
3214 : :
5873 rhaas@postgresql.org 3215 : 2068 : proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
6310 peter_e@gmx.net 3216 [ + + ]: 2068 : if (!HeapTupleIsValid(proctup))
3516 rhaas@postgresql.org 3217 : 3 : PG_RETURN_NULL();
3218 : :
3219 : 2065 : initStringInfo(&buf);
3220 : :
6310 peter_e@gmx.net 3221 : 2065 : (void) print_function_arguments(&buf, proctup, false, false);
3222 : :
3223 : 2065 : ReleaseSysCache(proctup);
3224 : :
3225 : 2065 : PG_RETURN_TEXT_P(string_to_text(buf.data));
3226 : : }
3227 : :
3228 : : /*
3229 : : * pg_get_function_result
3230 : : * Get a nicely-formatted version of the result type of a function.
3231 : : * This is what would appear after RETURNS in CREATE FUNCTION.
3232 : : */
3233 : : Datum
6449 tgl@sss.pgh.pa.us 3234 : 2042 : pg_get_function_result(PG_FUNCTION_ARGS)
3235 : : {
3236 : 2042 : Oid funcid = PG_GETARG_OID(0);
3237 : : StringInfoData buf;
3238 : : HeapTuple proctup;
3239 : :
5873 rhaas@postgresql.org 3240 : 2042 : proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
6449 tgl@sss.pgh.pa.us 3241 [ + + ]: 2042 : if (!HeapTupleIsValid(proctup))
3516 rhaas@postgresql.org 3242 : 3 : PG_RETURN_NULL();
3243 : :
2935 peter_e@gmx.net 3244 [ + + ]: 2039 : if (((Form_pg_proc) GETSTRUCT(proctup))->prokind == PROKIND_PROCEDURE)
3245 : : {
3027 3246 : 119 : ReleaseSysCache(proctup);
3247 : 119 : PG_RETURN_NULL();
3248 : : }
3249 : :
3516 rhaas@postgresql.org 3250 : 1920 : initStringInfo(&buf);
3251 : :
6399 tgl@sss.pgh.pa.us 3252 : 1920 : print_function_rettype(&buf, proctup);
3253 : :
3254 : 1920 : ReleaseSysCache(proctup);
3255 : :
3256 : 1920 : PG_RETURN_TEXT_P(string_to_text(buf.data));
3257 : : }
3258 : :
3259 : : /*
3260 : : * Guts of pg_get_function_result: append the function's return type
3261 : : * to the specified buffer.
3262 : : */
3263 : : static void
3264 : 1993 : print_function_rettype(StringInfo buf, HeapTuple proctup)
3265 : : {
3266 : 1993 : Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(proctup);
3267 : 1993 : int ntabargs = 0;
3268 : : StringInfoData rbuf;
3269 : :
3270 : 1993 : initStringInfo(&rbuf);
3271 : :
3272 [ + + ]: 1993 : if (proc->proretset)
3273 : : {
3274 : : /* It might be a table function; try to print the arguments */
3275 : 202 : appendStringInfoString(&rbuf, "TABLE(");
6296 3276 : 202 : ntabargs = print_function_arguments(&rbuf, proctup, true, false);
6449 3277 [ + + ]: 202 : if (ntabargs > 0)
3961 peter_e@gmx.net 3278 : 38 : appendStringInfoChar(&rbuf, ')');
3279 : : else
6399 tgl@sss.pgh.pa.us 3280 : 164 : resetStringInfo(&rbuf);
3281 : : }
3282 : :
6449 3283 [ + + ]: 1993 : if (ntabargs == 0)
3284 : : {
3285 : : /* Not a table function, so do the normal thing */
6399 3286 [ + + ]: 1955 : if (proc->proretset)
3287 : 164 : appendStringInfoString(&rbuf, "SETOF ");
3288 : 1955 : appendStringInfoString(&rbuf, format_type_be(proc->prorettype));
3289 : : }
3290 : :
2427 drowley@postgresql.o 3291 : 1993 : appendBinaryStringInfo(buf, rbuf.data, rbuf.len);
6449 tgl@sss.pgh.pa.us 3292 : 1993 : }
3293 : :
3294 : : /*
3295 : : * Common code for pg_get_function_arguments and pg_get_function_result:
3296 : : * append the desired subset of arguments to buf. We print only TABLE
3297 : : * arguments when print_table_args is true, and all the others when it's false.
3298 : : * We print argument defaults only if print_defaults is true.
3299 : : * Function return value is the number of arguments printed.
3300 : : */
3301 : : static int
3302 : 4681 : print_function_arguments(StringInfo buf, HeapTuple proctup,
3303 : : bool print_table_args, bool print_defaults)
3304 : : {
6296 3305 : 4681 : Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(proctup);
3306 : : int numargs;
3307 : : Oid *argtypes;
3308 : : char **argnames;
3309 : : char *argmodes;
4465 3310 : 4681 : int insertorderbyat = -1;
3311 : : int argsprinted;
3312 : : int inputargno;
3313 : : int nlackdefaults;
2435 3314 : 4681 : List *argdefaults = NIL;
6296 3315 : 4681 : ListCell *nextargdefault = NULL;
3316 : : int i;
3317 : :
6449 3318 : 4681 : numargs = get_func_arg_info(proctup,
3319 : : &argtypes, &argnames, &argmodes);
3320 : :
6296 3321 : 4681 : nlackdefaults = numargs;
3322 [ + + + + ]: 4681 : if (print_defaults && proc->pronargdefaults > 0)
3323 : : {
3324 : : Datum proargdefaults;
3325 : : bool isnull;
3326 : :
3327 : 19 : proargdefaults = SysCacheGetAttr(PROCOID, proctup,
3328 : : Anum_pg_proc_proargdefaults,
3329 : : &isnull);
3330 [ + - ]: 19 : if (!isnull)
3331 : : {
3332 : : char *str;
3333 : :
3334 : 19 : str = TextDatumGetCString(proargdefaults);
3309 peter_e@gmx.net 3335 : 19 : argdefaults = castNode(List, stringToNode(str));
6296 tgl@sss.pgh.pa.us 3336 : 19 : pfree(str);
3337 : 19 : nextargdefault = list_head(argdefaults);
3338 : : /* nlackdefaults counts only *input* arguments lacking defaults */
3339 : 19 : nlackdefaults = proc->pronargs - list_length(argdefaults);
3340 : : }
3341 : : }
3342 : :
3343 : : /* Check for special treatment of ordered-set aggregates */
2935 peter_e@gmx.net 3344 [ + + ]: 4681 : if (proc->prokind == PROKIND_AGGREGATE)
3345 : : {
3346 : : HeapTuple aggtup;
3347 : : Form_pg_aggregate agg;
3348 : :
969 michael@paquier.xyz 3349 : 585 : aggtup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(proc->oid));
4465 tgl@sss.pgh.pa.us 3350 [ - + ]: 585 : if (!HeapTupleIsValid(aggtup))
4465 tgl@sss.pgh.pa.us 3351 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for aggregate %u",
3352 : : proc->oid);
4465 tgl@sss.pgh.pa.us 3353 :CBC 585 : agg = (Form_pg_aggregate) GETSTRUCT(aggtup);
3354 [ + + ]: 585 : if (AGGKIND_IS_ORDERED_SET(agg->aggkind))
3355 : 26 : insertorderbyat = agg->aggnumdirectargs;
3356 : 585 : ReleaseSysCache(aggtup);
3357 : : }
3358 : :
6449 3359 : 4681 : argsprinted = 0;
6296 3360 : 4681 : inputargno = 0;
6449 3361 [ + + ]: 9426 : for (i = 0; i < numargs; i++)
3362 : : {
6121 bruce@momjian.us 3363 : 4745 : Oid argtype = argtypes[i];
3364 [ + + ]: 4745 : char *argname = argnames ? argnames[i] : NULL;
3365 [ + + ]: 4745 : char argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
3366 : : const char *modename;
3367 : : bool isinput;
3368 : :
6449 tgl@sss.pgh.pa.us 3369 [ + + + + : 4745 : switch (argmode)
+ - ]
3370 : : {
3371 : 3906 : case PROARGMODE_IN:
3372 : :
3373 : : /*
3374 : : * For procedures, explicitly mark all argument modes, so as
3375 : : * to avoid ambiguity with the SQL syntax for DROP PROCEDURE.
3376 : : */
1739 3377 [ + + ]: 3906 : if (proc->prokind == PROKIND_PROCEDURE)
3378 : 266 : modename = "IN ";
3379 : : else
3380 : 3640 : modename = "";
6296 3381 : 3906 : isinput = true;
6449 3382 : 3906 : break;
3383 : 50 : case PROARGMODE_INOUT:
3384 : 50 : modename = "INOUT ";
6296 3385 : 50 : isinput = true;
6449 3386 : 50 : break;
3387 : 478 : case PROARGMODE_OUT:
3388 : 478 : modename = "OUT ";
6296 3389 : 478 : isinput = false;
6449 3390 : 478 : break;
3391 : 89 : case PROARGMODE_VARIADIC:
3392 : 89 : modename = "VARIADIC ";
6296 3393 : 89 : isinput = true;
6449 3394 : 89 : break;
3395 : 222 : case PROARGMODE_TABLE:
3396 : 222 : modename = "";
6296 3397 : 222 : isinput = false;
6449 3398 : 222 : break;
6449 tgl@sss.pgh.pa.us 3399 :UBC 0 : default:
3400 [ # # ]: 0 : elog(ERROR, "invalid parameter mode '%c'", argmode);
3401 : : modename = NULL; /* keep compiler quiet */
3402 : : isinput = false;
3403 : : break;
3404 : : }
6296 tgl@sss.pgh.pa.us 3405 [ + + ]:CBC 4745 : if (isinput)
3406 : 4045 : inputargno++; /* this is a 1-based counter */
3407 : :
3408 [ + + ]: 4745 : if (print_table_args != (argmode == PROARGMODE_TABLE))
3409 : 382 : continue;
3410 : :
4465 3411 [ + + ]: 4363 : if (argsprinted == insertorderbyat)
3412 : : {
3413 [ + - ]: 26 : if (argsprinted)
3414 : 26 : appendStringInfoChar(buf, ' ');
3415 : 26 : appendStringInfoString(buf, "ORDER BY ");
3416 : : }
3417 [ + + ]: 4337 : else if (argsprinted)
6449 3418 : 1408 : appendStringInfoString(buf, ", ");
3419 : :
3420 : 4363 : appendStringInfoString(buf, modename);
6296 3421 [ + + + + ]: 4363 : if (argname && argname[0])
6123 3422 : 1553 : appendStringInfo(buf, "%s ", quote_identifier(argname));
6449 3423 : 4363 : appendStringInfoString(buf, format_type_be(argtype));
6296 3424 [ + + + + : 4363 : if (print_defaults && isinput && inputargno > nlackdefaults)
+ + ]
3425 : : {
3426 : : Node *expr;
3427 : :
3428 [ - + ]: 29 : Assert(nextargdefault != NULL);
3429 : 29 : expr = (Node *) lfirst(nextargdefault);
2435 3430 : 29 : nextargdefault = lnext(argdefaults, nextargdefault);
3431 : :
6296 3432 : 29 : appendStringInfo(buf, " DEFAULT %s",
3433 : : deparse_expression(expr, NIL, false, false));
3434 : : }
6449 3435 : 4363 : argsprinted++;
3436 : :
3437 : : /* nasty hack: print the last arg twice for variadic ordered-set agg */
4465 3438 [ + + + + ]: 4363 : if (argsprinted == insertorderbyat && i == numargs - 1)
3439 : : {
3440 : 13 : i--;
3441 : : /* aggs shouldn't have defaults anyway, but just to be sure ... */
3442 : 13 : print_defaults = false;
3443 : : }
3444 : : }
3445 : :
6449 3446 : 4681 : return argsprinted;
3447 : : }
3448 : :
3449 : : static bool
4492 peter_e@gmx.net 3450 : 48 : is_input_argument(int nth, const char *argmodes)
3451 : : {
3452 : : return (!argmodes
3453 [ + + ]: 21 : || argmodes[nth] == PROARGMODE_IN
3454 [ + - ]: 9 : || argmodes[nth] == PROARGMODE_INOUT
3455 [ + + - + ]: 69 : || argmodes[nth] == PROARGMODE_VARIADIC);
3456 : : }
3457 : :
3458 : : /*
3459 : : * Append used transformed types to specified buffer
3460 : : */
3461 : : static void
3976 3462 : 83 : print_function_trftypes(StringInfo buf, HeapTuple proctup)
3463 : : {
3464 : : Oid *trftypes;
3465 : : int ntypes;
3466 : :
3467 : 83 : ntypes = get_func_trftypes(proctup, &trftypes);
3468 [ + + ]: 83 : if (ntypes > 0)
3469 : : {
3470 : : int i;
3471 : :
1875 tgl@sss.pgh.pa.us 3472 : 3 : appendStringInfoString(buf, " TRANSFORM ");
3976 peter_e@gmx.net 3473 [ + + ]: 8 : for (i = 0; i < ntypes; i++)
3474 : : {
3475 [ + + ]: 5 : if (i != 0)
3476 : 2 : appendStringInfoString(buf, ", ");
3965 magnus@hagander.net 3477 : 5 : appendStringInfo(buf, "FOR TYPE %s", format_type_be(trftypes[i]));
3478 : : }
1875 tgl@sss.pgh.pa.us 3479 : 3 : appendStringInfoChar(buf, '\n');
3480 : : }
3976 peter_e@gmx.net 3481 : 83 : }
3482 : :
3483 : : /*
3484 : : * Get textual representation of a function argument's default value. The
3485 : : * second argument of this function is the argument number among all arguments
3486 : : * (i.e. proallargtypes, *not* proargtypes), starting with 1, because that's
3487 : : * how information_schema.sql uses it.
3488 : : */
3489 : : Datum
4492 3490 : 27 : pg_get_function_arg_default(PG_FUNCTION_ARGS)
3491 : : {
3492 : 27 : Oid funcid = PG_GETARG_OID(0);
3493 : 27 : int32 nth_arg = PG_GETARG_INT32(1);
3494 : : HeapTuple proctup;
3495 : : Form_pg_proc proc;
3496 : : int numargs;
3497 : : Oid *argtypes;
3498 : : char **argnames;
3499 : : char *argmodes;
3500 : : int i;
3501 : : List *argdefaults;
3502 : : Node *node;
3503 : : char *str;
3504 : : int nth_inputarg;
3505 : : Datum proargdefaults;
3506 : : bool isnull;
3507 : : int nth_default;
3508 : :
3509 : 27 : proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
3510 [ + + ]: 27 : if (!HeapTupleIsValid(proctup))
3516 rhaas@postgresql.org 3511 : 6 : PG_RETURN_NULL();
3512 : :
4492 peter_e@gmx.net 3513 : 21 : numargs = get_func_arg_info(proctup, &argtypes, &argnames, &argmodes);
3514 [ + - + - : 21 : if (nth_arg < 1 || nth_arg > numargs || !is_input_argument(nth_arg - 1, argmodes))
+ + ]
3515 : : {
3516 : 6 : ReleaseSysCache(proctup);
3517 : 6 : PG_RETURN_NULL();
3518 : : }
3519 : :
3520 : 15 : nth_inputarg = 0;
3521 [ + + ]: 42 : for (i = 0; i < nth_arg; i++)
3522 [ + + ]: 27 : if (is_input_argument(i, argmodes))
3523 : 24 : nth_inputarg++;
3524 : :
3525 : 15 : proargdefaults = SysCacheGetAttr(PROCOID, proctup,
3526 : : Anum_pg_proc_proargdefaults,
3527 : : &isnull);
3528 [ - + ]: 15 : if (isnull)
3529 : : {
4492 peter_e@gmx.net 3530 :UBC 0 : ReleaseSysCache(proctup);
3531 : 0 : PG_RETURN_NULL();
3532 : : }
3533 : :
4492 peter_e@gmx.net 3534 :CBC 15 : str = TextDatumGetCString(proargdefaults);
3309 3535 : 15 : argdefaults = castNode(List, stringToNode(str));
4492 3536 : 15 : pfree(str);
3537 : :
3538 : 15 : proc = (Form_pg_proc) GETSTRUCT(proctup);
3539 : :
3540 : : /*
3541 : : * Calculate index into proargdefaults: proargdefaults corresponds to the
3542 : : * last N input arguments, where N = pronargdefaults.
3543 : : */
3544 : 15 : nth_default = nth_inputarg - 1 - (proc->pronargs - proc->pronargdefaults);
3545 : :
3546 [ + + - + ]: 15 : if (nth_default < 0 || nth_default >= list_length(argdefaults))
3547 : : {
3548 : 3 : ReleaseSysCache(proctup);
3549 : 3 : PG_RETURN_NULL();
3550 : : }
3551 : 12 : node = list_nth(argdefaults, nth_default);
3552 : 12 : str = deparse_expression(node, NIL, false, false);
3553 : :
3554 : 12 : ReleaseSysCache(proctup);
3555 : :
3556 : 12 : PG_RETURN_TEXT_P(string_to_text(str));
3557 : : }
3558 : :
3559 : : static void
1803 peter@eisentraut.org 3560 : 105 : print_function_sqlbody(StringInfo buf, HeapTuple proctup)
3561 : : {
3562 : : int numargs;
3563 : : Oid *argtypes;
3564 : : char **argnames;
3565 : : char *argmodes;
3566 : 105 : deparse_namespace dpns = {0};
3567 : : Datum tmp;
3568 : : Node *n;
3569 : :
3570 : 105 : dpns.funcname = pstrdup(NameStr(((Form_pg_proc) GETSTRUCT(proctup))->proname));
3571 : 105 : numargs = get_func_arg_info(proctup,
3572 : : &argtypes, &argnames, &argmodes);
3573 : 105 : dpns.numargs = numargs;
3574 : 105 : dpns.argnames = argnames;
3575 : :
1086 dgustafsson@postgres 3576 : 105 : tmp = SysCacheGetAttrNotNull(PROCOID, proctup, Anum_pg_proc_prosqlbody);
1803 peter@eisentraut.org 3577 : 105 : n = stringToNode(TextDatumGetCString(tmp));
3578 : :
3579 [ + + ]: 105 : if (IsA(n, List))
3580 : : {
3581 : : List *stmts;
3582 : : ListCell *lc;
3583 : :
3584 : 82 : stmts = linitial(castNode(List, n));
3585 : :
3586 : 82 : appendStringInfoString(buf, "BEGIN ATOMIC\n");
3587 : :
3588 [ + + + + : 159 : foreach(lc, stmts)
+ + ]
3589 : : {
3590 : 77 : Query *query = lfirst_node(Query, lc);
3591 : :
3592 : : /* It seems advisable to get at least AccessShareLock on rels */
1657 tgl@sss.pgh.pa.us 3593 : 77 : AcquireRewriteLocks(query, false, false);
1394 3594 : 77 : get_query_def(query, buf, list_make1(&dpns), NULL, false,
3595 : : PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
1803 peter@eisentraut.org 3596 : 77 : appendStringInfoChar(buf, ';');
3597 : 77 : appendStringInfoChar(buf, '\n');
3598 : : }
3599 : :
3600 : 82 : appendStringInfoString(buf, "END");
3601 : : }
3602 : : else
3603 : : {
1657 tgl@sss.pgh.pa.us 3604 : 23 : Query *query = castNode(Query, n);
3605 : :
3606 : : /* It seems advisable to get at least AccessShareLock on rels */
3607 : 23 : AcquireRewriteLocks(query, false, false);
1394 3608 : 23 : get_query_def(query, buf, list_make1(&dpns), NULL, false,
3609 : : 0, WRAP_COLUMN_DEFAULT, 0);
3610 : : }
1803 peter@eisentraut.org 3611 : 105 : }
3612 : :
3613 : : Datum
3614 : 1780 : pg_get_function_sqlbody(PG_FUNCTION_ARGS)
3615 : : {
3616 : 1780 : Oid funcid = PG_GETARG_OID(0);
3617 : : StringInfoData buf;
3618 : : HeapTuple proctup;
3619 : : bool isnull;
3620 : :
3621 : 1780 : initStringInfo(&buf);
3622 : :
3623 : : /* Look up the function */
3624 : 1780 : proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
3625 [ - + ]: 1780 : if (!HeapTupleIsValid(proctup))
1803 peter@eisentraut.org 3626 :UBC 0 : PG_RETURN_NULL();
3627 : :
1657 tgl@sss.pgh.pa.us 3628 :CBC 1780 : (void) SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
1803 peter@eisentraut.org 3629 [ + + ]: 1780 : if (isnull)
3630 : : {
3631 : 1732 : ReleaseSysCache(proctup);
3632 : 1732 : PG_RETURN_NULL();
3633 : : }
3634 : :
3635 : 48 : print_function_sqlbody(&buf, proctup);
3636 : :
3637 : 48 : ReleaseSysCache(proctup);
3638 : :
1286 drowley@postgresql.o 3639 : 48 : PG_RETURN_TEXT_P(cstring_to_text_with_len(buf.data, buf.len));
3640 : : }
3641 : :
3642 : :
3643 : : /*
3644 : : * deparse_expression - General utility for deparsing expressions
3645 : : *
3646 : : * calls deparse_expression_pretty with all prettyPrinting disabled
3647 : : */
3648 : : char *
8264 tgl@sss.pgh.pa.us 3649 : 41697 : deparse_expression(Node *expr, List *dpcontext,
3650 : : bool forceprefix, bool showimplicit)
3651 : : {
8259 bruce@momjian.us 3652 : 41697 : return deparse_expression_pretty(expr, dpcontext, forceprefix,
3653 : : showimplicit, 0, 0);
3654 : : }
3655 : :
3656 : : /* ----------
3657 : : * deparse_expression_pretty - General utility for deparsing expressions
3658 : : *
3659 : : * expr is the node tree to be deparsed. It must be a transformed expression
3660 : : * tree (ie, not the raw output of gram.y).
3661 : : *
3662 : : * dpcontext is a list of deparse_namespace nodes representing the context
3663 : : * for interpreting Vars in the node tree. It can be NIL if no Vars are
3664 : : * expected.
3665 : : *
3666 : : * forceprefix is true to force all Vars to be prefixed with their table names.
3667 : : *
3668 : : * showimplicit is true to force all implicit casts to be shown explicitly.
3669 : : *
3670 : : * Tries to pretty up the output according to prettyFlags and startIndent.
3671 : : *
3672 : : * The result is a palloc'd string.
3673 : : * ----------
3674 : : */
3675 : : static char *
8264 tgl@sss.pgh.pa.us 3676 : 48995 : deparse_expression_pretty(Node *expr, List *dpcontext,
3677 : : bool forceprefix, bool showimplicit,
3678 : : int prettyFlags, int startIndent)
3679 : : {
3680 : : StringInfoData buf;
3681 : : deparse_context context;
3682 : :
9660 3683 : 48995 : initStringInfo(&buf);
3684 : 48995 : context.buf = &buf;
9160 3685 : 48995 : context.namespaces = dpcontext;
563 3686 : 48995 : context.resultDesc = NULL;
3687 : 48995 : context.targetList = NIL;
6286 3688 : 48995 : context.windowClause = NIL;
9160 3689 : 48995 : context.varprefix = forceprefix;
8264 3690 : 48995 : context.prettyFlags = prettyFlags;
4829 3691 : 48995 : context.wrapColumn = WRAP_COLUMN_DEFAULT;
8264 3692 : 48995 : context.indentLevel = startIndent;
563 3693 : 48995 : context.colNamesVisible = true;
3694 : 48995 : context.inGroupBy = false;
3695 : 48995 : context.varInOrderBy = false;
2286 3696 : 48995 : context.appendparents = NULL;
3697 : :
8578 3698 : 48995 : get_rule_expr(expr, &context, showimplicit);
3699 : :
9660 3700 : 48995 : return buf.data;
3701 : : }
3702 : :
3703 : : /* ----------
3704 : : * deparse_context_for - Build deparse context for a single relation
3705 : : *
3706 : : * Given the reference name (alias) and OID of a relation, build deparsing
3707 : : * context for an expression referencing only that relation (as varno 1,
3708 : : * varlevelsup 0). This is sufficient for many uses of deparse_expression.
3709 : : * ----------
3710 : : */
3711 : : List *
8759 3712 : 12647 : deparse_context_for(const char *aliasname, Oid relid)
3713 : : {
3714 : : deparse_namespace *dpns;
3715 : : RangeTblEntry *rte;
3716 : :
95 michael@paquier.xyz 3717 :GNC 12647 : dpns = palloc0_object(deparse_namespace);
3718 : :
3719 : : /* Build a minimal RTE for the rel */
9160 tgl@sss.pgh.pa.us 3720 :CBC 12647 : rte = makeNode(RangeTblEntry);
8769 3721 : 12647 : rte->rtekind = RTE_RELATION;
9160 3722 : 12647 : rte->relid = relid;
5500 3723 : 12647 : rte->relkind = RELKIND_RELATION; /* no need for exactness here */
2723 3724 : 12647 : rte->rellockmode = AccessShareLock;
4923 3725 : 12647 : rte->alias = makeAlias(aliasname, NIL);
3726 : 12647 : rte->eref = rte->alias;
4968 3727 : 12647 : rte->lateral = false;
9160 3728 : 12647 : rte->inh = false;
3729 : 12647 : rte->inFromCl = true;
3730 : :
3731 : : /* Build one-element rtable */
7959 neilc@samurai.com 3732 : 12647 : dpns->rtable = list_make1(rte);
2286 tgl@sss.pgh.pa.us 3733 : 12647 : dpns->subplans = NIL;
6369 3734 : 12647 : dpns->ctes = NIL;
2286 3735 : 12647 : dpns->appendrels = NULL;
4923 3736 : 12647 : set_rtable_names(dpns, NIL, NULL);
4822 3737 : 12647 : set_simple_column_names(dpns);
3738 : :
3739 : : /* Return a one-deep namespace stack */
7959 neilc@samurai.com 3740 : 12647 : return list_make1(dpns);
3741 : : }
3742 : :
3743 : : /*
3744 : : * deparse_context_for_plan_tree - Build deparse context for a Plan tree
3745 : : *
3746 : : * When deparsing an expression in a Plan tree, we use the plan's rangetable
3747 : : * to resolve names of simple Vars. The initialization of column names for
3748 : : * this is rather expensive if the rangetable is large, and it'll be the same
3749 : : * for every expression in the Plan tree; so we do it just once and re-use
3750 : : * the result of this function for each expression. (Note that the result
3751 : : * is not usable until set_deparse_context_plan() is applied to it.)
3752 : : *
3753 : : * In addition to the PlannedStmt, pass the per-RTE alias names
3754 : : * assigned by a previous call to select_rtable_names_for_explain.
3755 : : */
3756 : : List *
2286 tgl@sss.pgh.pa.us 3757 : 12639 : deparse_context_for_plan_tree(PlannedStmt *pstmt, List *rtable_names)
3758 : : {
3759 : : deparse_namespace *dpns;
3760 : :
95 michael@paquier.xyz 3761 :GNC 12639 : dpns = palloc0_object(deparse_namespace);
3762 : :
3763 : : /* Initialize fields that stay the same across the whole plan tree */
2286 tgl@sss.pgh.pa.us 3764 :CBC 12639 : dpns->rtable = pstmt->rtable;
4077 3765 : 12639 : dpns->rtable_names = rtable_names;
2286 3766 : 12639 : dpns->subplans = pstmt->subplans;
4077 3767 : 12639 : dpns->ctes = NIL;
2286 3768 [ + + ]: 12639 : if (pstmt->appendRelations)
3769 : : {
3770 : : /* Set up the array, indexed by child relid */
3771 : 1981 : int ntables = list_length(dpns->rtable);
3772 : : ListCell *lc;
3773 : :
3774 : 1981 : dpns->appendrels = (AppendRelInfo **)
3775 : 1981 : palloc0((ntables + 1) * sizeof(AppendRelInfo *));
3776 [ + - + + : 10975 : foreach(lc, pstmt->appendRelations)
+ + ]
3777 : : {
3778 : 8994 : AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
3779 : 8994 : Index crelid = appinfo->child_relid;
3780 : :
3781 [ + - - + ]: 8994 : Assert(crelid > 0 && crelid <= ntables);
3782 [ - + ]: 8994 : Assert(dpns->appendrels[crelid] == NULL);
3783 : 8994 : dpns->appendrels[crelid] = appinfo;
3784 : : }
3785 : : }
3786 : : else
3787 : 10658 : dpns->appendrels = NULL; /* don't need it */
3788 : :
3789 : : /*
3790 : : * Set up column name aliases, ignoring any join RTEs; they don't matter
3791 : : * because plan trees don't contain any join alias Vars.
3792 : : */
4077 3793 : 12639 : set_simple_column_names(dpns);
3794 : :
3795 : : /* Return a one-deep namespace stack */
3796 : 12639 : return list_make1(dpns);
3797 : : }
3798 : :
3799 : : /*
3800 : : * set_deparse_context_plan - Specify Plan node containing expression
3801 : : *
3802 : : * When deparsing an expression in a Plan tree, we might have to resolve
3803 : : * OUTER_VAR, INNER_VAR, or INDEX_VAR references. To do this, the caller must
3804 : : * provide the parent Plan node. Then OUTER_VAR and INNER_VAR references
3805 : : * can be resolved by drilling down into the left and right child plans.
3806 : : * Similarly, INDEX_VAR references can be resolved by reference to the
3807 : : * indextlist given in a parent IndexOnlyScan node, or to the scan tlist in
3808 : : * ForeignScan and CustomScan nodes. (Note that we don't currently support
3809 : : * deparsing of indexquals in regular IndexScan or BitmapIndexScan nodes;
3810 : : * for those, we can only deparse the indexqualorig fields, which won't
3811 : : * contain INDEX_VAR Vars.)
3812 : : *
3813 : : * The ancestors list is a list of the Plan's parent Plan and SubPlan nodes,
3814 : : * the most-closely-nested first. This is needed to resolve PARAM_EXEC
3815 : : * Params. Note we assume that all the Plan nodes share the same rtable.
3816 : : *
3817 : : * For a ModifyTable plan, we might also need to resolve references to OLD/NEW
3818 : : * variables in the RETURNING list, so we copy the alias names of the OLD and
3819 : : * NEW rows from the ModifyTable plan node.
3820 : : *
3821 : : * Once this function has been called, deparse_expression() can be called on
3822 : : * subsidiary expression(s) of the specified Plan node. To deparse
3823 : : * expressions of a different Plan node in the same Plan tree, re-call this
3824 : : * function to identify the new parent Plan node.
3825 : : *
3826 : : * The result is the same List passed in; this is a notational convenience.
3827 : : */
3828 : : List *
2286 3829 : 29836 : set_deparse_context_plan(List *dpcontext, Plan *plan, List *ancestors)
3830 : : {
3831 : : deparse_namespace *dpns;
3832 : :
3833 : : /* Should always have one-entry namespace list for Plan deparsing */
4077 3834 [ - + ]: 29836 : Assert(list_length(dpcontext) == 1);
3835 : 29836 : dpns = (deparse_namespace *) linitial(dpcontext);
3836 : :
3837 : : /* Set our attention on the specific plan node passed in */
5724 3838 : 29836 : dpns->ancestors = ancestors;
1616 3839 : 29836 : set_deparse_plan(dpns, plan);
3840 : :
3841 : : /* For ModifyTable, set aliases for OLD and NEW in RETURNING */
423 dean.a.rasheed@gmail 3842 [ + + ]: 29836 : if (IsA(plan, ModifyTable))
3843 : : {
3844 : 111 : dpns->ret_old_alias = ((ModifyTable *) plan)->returningOldAlias;
3845 : 111 : dpns->ret_new_alias = ((ModifyTable *) plan)->returningNewAlias;
3846 : : }
3847 : :
4077 tgl@sss.pgh.pa.us 3848 : 29836 : return dpcontext;
3849 : : }
3850 : :
3851 : : /*
3852 : : * select_rtable_names_for_explain - Select RTE aliases for EXPLAIN
3853 : : *
3854 : : * Determine the relation aliases we'll use during an EXPLAIN operation.
3855 : : * This is just a frontend to set_rtable_names. We have to expose the aliases
3856 : : * to EXPLAIN because EXPLAIN needs to know the right alias names to print.
3857 : : */
3858 : : List *
4923 3859 : 12639 : select_rtable_names_for_explain(List *rtable, Bitmapset *rels_used)
3860 : : {
3861 : : deparse_namespace dpns;
3862 : :
3863 : 12639 : memset(&dpns, 0, sizeof(dpns));
3864 : 12639 : dpns.rtable = rtable;
2286 3865 : 12639 : dpns.subplans = NIL;
4923 3866 : 12639 : dpns.ctes = NIL;
2286 3867 : 12639 : dpns.appendrels = NULL;
4923 3868 : 12639 : set_rtable_names(&dpns, NIL, rels_used);
3869 : : /* We needn't bother computing column aliases yet */
3870 : :
3871 : 12639 : return dpns.rtable_names;
3872 : : }
3873 : :
3874 : : /*
3875 : : * set_rtable_names: select RTE aliases to be used in printing a query
3876 : : *
3877 : : * We fill in dpns->rtable_names with a list of names that is one-for-one with
3878 : : * the already-filled dpns->rtable list. Each RTE name is unique among those
3879 : : * in the new namespace plus any ancestor namespaces listed in
3880 : : * parent_namespaces.
3881 : : *
3882 : : * If rels_used isn't NULL, only RTE indexes listed in it are given aliases.
3883 : : *
3884 : : * Note that this function is only concerned with relation names, not column
3885 : : * names.
3886 : : */
3887 : : static void
3888 : 28311 : set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
3889 : : Bitmapset *rels_used)
3890 : : {
3891 : : HASHCTL hash_ctl;
3892 : : HTAB *names_hash;
3893 : : NameHashEntry *hentry;
3894 : : bool found;
3895 : : int rtindex;
3896 : : ListCell *lc;
3897 : :
3898 : 28311 : dpns->rtable_names = NIL;
3899 : : /* nothing more to do if empty rtable */
3772 3900 [ + + ]: 28311 : if (dpns->rtable == NIL)
3901 : 287 : return;
3902 : :
3903 : : /*
3904 : : * We use a hash table to hold known names, so that this process is O(N)
3905 : : * not O(N^2) for N names.
3906 : : */
3907 : 28024 : hash_ctl.keysize = NAMEDATALEN;
3908 : 28024 : hash_ctl.entrysize = sizeof(NameHashEntry);
3909 : 28024 : hash_ctl.hcxt = CurrentMemoryContext;
3910 : 28024 : names_hash = hash_create("set_rtable_names names",
3911 : 28024 : list_length(dpns->rtable),
3912 : : &hash_ctl,
3913 : : HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);
3914 : :
3915 : : /* Preload the hash table with names appearing in parent_namespaces */
3916 [ + + + + : 28892 : foreach(lc, parent_namespaces)
+ + ]
3917 : : {
3918 : 868 : deparse_namespace *olddpns = (deparse_namespace *) lfirst(lc);
3919 : : ListCell *lc2;
3920 : :
3921 [ + + + + : 3149 : foreach(lc2, olddpns->rtable_names)
+ + ]
3922 : : {
3923 : 2281 : char *oldname = (char *) lfirst(lc2);
3924 : :
3925 [ + + ]: 2281 : if (oldname == NULL)
3926 : 168 : continue;
3927 : 2113 : hentry = (NameHashEntry *) hash_search(names_hash,
3928 : : oldname,
3929 : : HASH_ENTER,
3930 : : &found);
3931 : : /* we do not complain about duplicate names in parent namespaces */
3932 : 2113 : hentry->counter = 0;
3933 : : }
3934 : : }
3935 : :
3936 : : /* Now we can scan the rtable */
3937 : 28024 : rtindex = 1;
4923 3938 [ + - + + : 80928 : foreach(lc, dpns->rtable)
+ + ]
3939 : : {
3940 : 52904 : RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
3941 : : char *refname;
3942 : :
3943 : : /* Just in case this takes an unreasonable amount of time ... */
3772 3944 [ + + ]: 52904 : CHECK_FOR_INTERRUPTS();
3945 : :
4923 3946 [ + + + + ]: 52904 : if (rels_used && !bms_is_member(rtindex, rels_used))
3947 : : {
3948 : : /* Ignore unreferenced RTE */
3949 : 9239 : refname = NULL;
3950 : : }
3951 [ + + ]: 43665 : else if (rte->alias)
3952 : : {
3953 : : /* If RTE has a user-defined alias, prefer that */
3954 : 28553 : refname = rte->alias->aliasname;
3955 : : }
3956 [ + + ]: 15112 : else if (rte->rtekind == RTE_RELATION)
3957 : : {
3958 : : /* Use the current actual name of the relation */
3959 : 11533 : refname = get_rel_name(rte->relid);
3960 : : }
3961 [ + + ]: 3579 : else if (rte->rtekind == RTE_JOIN)
3962 : : {
3963 : : /* Unnamed join has no refname */
3964 : 900 : refname = NULL;
3965 : : }
3966 : : else
3967 : : {
3968 : : /* Otherwise use whatever the parser assigned */
3969 : 2679 : refname = rte->eref->aliasname;
3970 : : }
3971 : :
3972 : : /*
3973 : : * If the selected name isn't unique, append digits to make it so, and
3974 : : * make a new hash entry for it once we've got a unique name. For a
3975 : : * very long input name, we might have to truncate to stay within
3976 : : * NAMEDATALEN.
3977 : : */
3772 3978 [ + + ]: 52904 : if (refname)
3979 : : {
3980 : 42765 : hentry = (NameHashEntry *) hash_search(names_hash,
3981 : : refname,
3982 : : HASH_ENTER,
3983 : : &found);
3984 [ + + ]: 42765 : if (found)
3985 : : {
3986 : : /* Name already in use, must choose a new one */
3987 : 7716 : int refnamelen = strlen(refname);
3988 : 7716 : char *modname = (char *) palloc(refnamelen + 16);
3989 : : NameHashEntry *hentry2;
3990 : :
3991 : : do
3992 : : {
3993 : 7719 : hentry->counter++;
3994 : : for (;;)
3995 : : {
3996 : 7725 : memcpy(modname, refname, refnamelen);
3997 : 7725 : sprintf(modname + refnamelen, "_%d", hentry->counter);
3998 [ + + ]: 7725 : if (strlen(modname) < NAMEDATALEN)
3999 : 7719 : break;
4000 : : /* drop chars from refname to keep all the digits */
4001 : 6 : refnamelen = pg_mbcliplen(refname, refnamelen,
4002 : : refnamelen - 1);
4003 : : }
4004 : 7719 : hentry2 = (NameHashEntry *) hash_search(names_hash,
4005 : : modname,
4006 : : HASH_ENTER,
4007 : : &found);
4008 [ + + ]: 7719 : } while (found);
4009 : 7716 : hentry2->counter = 0; /* init new hash entry */
4010 : 7716 : refname = modname;
4011 : : }
4012 : : else
4013 : : {
4014 : : /* Name not previously used, need only initialize hentry */
4015 : 35049 : hentry->counter = 0;
4016 : : }
4017 : : }
4018 : :
4923 4019 : 52904 : dpns->rtable_names = lappend(dpns->rtable_names, refname);
4020 : 52904 : rtindex++;
4021 : : }
4022 : :
3772 4023 : 28024 : hash_destroy(names_hash);
4024 : : }
4025 : :
4026 : : /*
4027 : : * set_deparse_for_query: set up deparse_namespace for deparsing a Query tree
4028 : : *
4029 : : * For convenience, this is defined to initialize the deparse_namespace struct
4030 : : * from scratch.
4031 : : */
4032 : : static void
4822 4033 : 2949 : set_deparse_for_query(deparse_namespace *dpns, Query *query,
4034 : : List *parent_namespaces)
4035 : : {
4036 : : ListCell *lc;
4037 : : ListCell *lc2;
4038 : :
4039 : : /* Initialize *dpns and fill rtable/ctes links */
4040 : 2949 : memset(dpns, 0, sizeof(deparse_namespace));
4041 : 2949 : dpns->rtable = query->rtable;
2286 4042 : 2949 : dpns->subplans = NIL;
4822 4043 : 2949 : dpns->ctes = query->cteList;
2286 4044 : 2949 : dpns->appendrels = NULL;
423 dean.a.rasheed@gmail 4045 : 2949 : dpns->ret_old_alias = query->returningOldAlias;
4046 : 2949 : dpns->ret_new_alias = query->returningNewAlias;
4047 : :
4048 : : /* Assign a unique relation alias to each RTE */
4822 tgl@sss.pgh.pa.us 4049 : 2949 : set_rtable_names(dpns, parent_namespaces, NULL);
4050 : :
4051 : : /* Initialize dpns->rtable_columns to contain zeroed structs */
4052 : 2949 : dpns->rtable_columns = NIL;
4053 [ + + ]: 8262 : while (list_length(dpns->rtable_columns) < list_length(dpns->rtable))
4054 : 5313 : dpns->rtable_columns = lappend(dpns->rtable_columns,
4055 : : palloc0(sizeof(deparse_columns)));
4056 : :
4057 : : /* If it's a utility query, it won't have a jointree */
4686 4058 [ + + ]: 2949 : if (query->jointree)
4059 : : {
4060 : : /* Detect whether global uniqueness of USING names is needed */
4061 : 2941 : dpns->unique_using =
4618 4062 : 2941 : has_dangerous_join_using(dpns, (Node *) query->jointree);
4063 : :
4064 : : /*
4065 : : * Select names for columns merged by USING, via a recursive pass over
4066 : : * the query jointree.
4067 : : */
4336 4068 : 2941 : set_using_names(dpns, (Node *) query->jointree, NIL);
4069 : : }
4070 : :
4071 : : /*
4072 : : * Now assign remaining column aliases for each RTE. We do this in a
4073 : : * linear scan of the rtable, so as to process RTEs whether or not they
4074 : : * are in the jointree (we mustn't miss NEW.*, INSERT target relations,
4075 : : * etc). JOIN RTEs must be processed after their children, but this is
4076 : : * okay because they appear later in the rtable list than their children
4077 : : * (cf Asserts in identify_join_columns()).
4078 : : */
4822 4079 [ + + + + : 8262 : forboth(lc, dpns->rtable, lc2, dpns->rtable_columns)
+ + + + +
+ + - +
+ ]
4080 : : {
4081 : 5313 : RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
4082 : 5313 : deparse_columns *colinfo = (deparse_columns *) lfirst(lc2);
4083 : :
4084 [ + + ]: 5313 : if (rte->rtekind == RTE_JOIN)
4085 : 758 : set_join_column_names(dpns, rte, colinfo);
4086 : : else
4087 : 4555 : set_relation_column_names(dpns, rte, colinfo);
4088 : : }
4089 : 2949 : }
4090 : :
4091 : : /*
4092 : : * set_simple_column_names: fill in column aliases for non-query situations
4093 : : *
4094 : : * This handles EXPLAIN and cases where we only have relation RTEs. Without
4095 : : * a join tree, we can't do anything smart about join RTEs, but we don't
4096 : : * need to, because EXPLAIN should never see join alias Vars anyway.
4097 : : * If we find a join RTE we'll just skip it, leaving its deparse_columns
4098 : : * struct all-zero. If somehow we try to deparse a join alias Var, we'll
4099 : : * error out cleanly because the struct's num_cols will be zero.
4100 : : */
4101 : : static void
4102 : 25362 : set_simple_column_names(deparse_namespace *dpns)
4103 : : {
4104 : : ListCell *lc;
4105 : : ListCell *lc2;
4106 : :
4107 : : /* Initialize dpns->rtable_columns to contain zeroed structs */
4108 : 25362 : dpns->rtable_columns = NIL;
4109 [ + + ]: 72953 : while (list_length(dpns->rtable_columns) < list_length(dpns->rtable))
4110 : 47591 : dpns->rtable_columns = lappend(dpns->rtable_columns,
4111 : : palloc0(sizeof(deparse_columns)));
4112 : :
4113 : : /* Assign unique column aliases within each non-join RTE */
4114 [ + - + + : 72953 : forboth(lc, dpns->rtable, lc2, dpns->rtable_columns)
+ - + + +
+ + - +
+ ]
4115 : : {
4116 : 47591 : RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
4117 : 47591 : deparse_columns *colinfo = (deparse_columns *) lfirst(lc2);
4118 : :
453 4119 [ + + ]: 47591 : if (rte->rtekind != RTE_JOIN)
4120 : 44463 : set_relation_column_names(dpns, rte, colinfo);
4121 : : }
4822 4122 : 25362 : }
4123 : :
4124 : : /*
4125 : : * has_dangerous_join_using: search jointree for unnamed JOIN USING
4126 : : *
4127 : : * Merged columns of a JOIN USING may act differently from either of the input
4128 : : * columns, either because they are merged with COALESCE (in a FULL JOIN) or
4129 : : * because an implicit coercion of the underlying input column is required.
4130 : : * In such a case the column must be referenced as a column of the JOIN not as
4131 : : * a column of either input. And this is problematic if the join is unnamed
4132 : : * (alias-less): we cannot qualify the column's name with an RTE name, since
4133 : : * there is none. (Forcibly assigning an alias to the join is not a solution,
4134 : : * since that will prevent legal references to tables below the join.)
4135 : : * To ensure that every column in the query is unambiguously referenceable,
4136 : : * we must assign such merged columns names that are globally unique across
4137 : : * the whole query, aliasing other columns out of the way as necessary.
4138 : : *
4139 : : * Because the ensuing re-aliasing is fairly damaging to the readability of
4140 : : * the query, we don't do this unless we have to. So, we must pre-scan
4141 : : * the join tree to see if we have to, before starting set_using_names().
4142 : : */
4143 : : static bool
4618 4144 : 6969 : has_dangerous_join_using(deparse_namespace *dpns, Node *jtnode)
4145 : : {
4822 4146 [ + + ]: 6969 : if (IsA(jtnode, RangeTblRef))
4147 : : {
4148 : : /* nothing to do here */
4149 : : }
4150 [ + + ]: 3666 : else if (IsA(jtnode, FromExpr))
4151 : : {
4152 : 2941 : FromExpr *f = (FromExpr *) jtnode;
4153 : : ListCell *lc;
4154 : :
4155 [ + + + + : 5558 : foreach(lc, f->fromlist)
+ + ]
4156 : : {
4618 4157 [ + + ]: 2656 : if (has_dangerous_join_using(dpns, (Node *) lfirst(lc)))
4822 4158 : 39 : return true;
4159 : : }
4160 : : }
4161 [ + - ]: 725 : else if (IsA(jtnode, JoinExpr))
4162 : : {
4163 : 725 : JoinExpr *j = (JoinExpr *) jtnode;
4164 : :
4165 : : /* Is it an unnamed JOIN with USING? */
4618 4166 [ + + + + ]: 725 : if (j->alias == NULL && j->usingClause)
4167 : : {
4168 : : /*
4169 : : * Yes, so check each join alias var to see if any of them are not
4170 : : * simple references to underlying columns. If so, we have a
4171 : : * dangerous situation and must pick unique aliases.
4172 : : */
4173 : 146 : RangeTblEntry *jrte = rt_fetch(j->rtindex, dpns->rtable);
4174 : :
4175 : : /* We need only examine the merged columns */
2257 4176 [ + + ]: 301 : for (int i = 0; i < jrte->joinmergedcols; i++)
4177 : : {
4178 : 194 : Node *aliasvar = list_nth(jrte->joinaliasvars, i);
4179 : :
4180 [ + + ]: 194 : if (!IsA(aliasvar, Var))
4618 4181 : 39 : return true;
4182 : : }
4183 : : }
4184 : :
4185 : : /* Nope, but inspect children */
4186 [ - + ]: 686 : if (has_dangerous_join_using(dpns, j->larg))
4822 tgl@sss.pgh.pa.us 4187 :UBC 0 : return true;
4618 tgl@sss.pgh.pa.us 4188 [ - + ]:CBC 686 : if (has_dangerous_join_using(dpns, j->rarg))
4822 tgl@sss.pgh.pa.us 4189 :UBC 0 : return true;
4190 : : }
4191 : : else
4192 [ # # ]: 0 : elog(ERROR, "unrecognized node type: %d",
4193 : : (int) nodeTag(jtnode));
4822 tgl@sss.pgh.pa.us 4194 :CBC 6891 : return false;
4195 : : }
4196 : :
4197 : : /*
4198 : : * set_using_names: select column aliases to be used for merged USING columns
4199 : : *
4200 : : * We do this during a recursive descent of the query jointree.
4201 : : * dpns->unique_using must already be set to determine the global strategy.
4202 : : *
4203 : : * Column alias info is saved in the dpns->rtable_columns list, which is
4204 : : * assumed to be filled with pre-zeroed deparse_columns structs.
4205 : : *
4206 : : * parentUsing is a list of all USING aliases assigned in parent joins of
4207 : : * the current jointree node. (The passed-in list must not be modified.)
4208 : : *
4209 : : * Note that we do not use per-deparse_columns hash tables in this function.
4210 : : * The number of names that need to be assigned should be small enough that
4211 : : * we don't need to trouble with that.
4212 : : */
4213 : : static void
4336 4214 : 7134 : set_using_names(deparse_namespace *dpns, Node *jtnode, List *parentUsing)
4215 : : {
4822 4216 [ + + ]: 7134 : if (IsA(jtnode, RangeTblRef))
4217 : : {
4218 : : /* nothing to do now */
4219 : : }
4220 [ + + ]: 3699 : else if (IsA(jtnode, FromExpr))
4221 : : {
4222 : 2941 : FromExpr *f = (FromExpr *) jtnode;
4223 : : ListCell *lc;
4224 : :
4225 [ + + + + : 5618 : foreach(lc, f->fromlist)
+ + ]
4336 4226 : 2677 : set_using_names(dpns, (Node *) lfirst(lc), parentUsing);
4227 : : }
4822 4228 [ + - ]: 758 : else if (IsA(jtnode, JoinExpr))
4229 : : {
4230 : 758 : JoinExpr *j = (JoinExpr *) jtnode;
4231 : 758 : RangeTblEntry *rte = rt_fetch(j->rtindex, dpns->rtable);
4232 : 758 : deparse_columns *colinfo = deparse_columns_fetch(j->rtindex, dpns);
4233 : : int *leftattnos;
4234 : : int *rightattnos;
4235 : : deparse_columns *leftcolinfo;
4236 : : deparse_columns *rightcolinfo;
4237 : : int i;
4238 : : ListCell *lc;
4239 : :
4240 : : /* Get info about the shape of the join */
4241 : 758 : identify_join_columns(j, rte, colinfo);
4242 : 758 : leftattnos = colinfo->leftattnos;
4243 : 758 : rightattnos = colinfo->rightattnos;
4244 : :
4245 : : /* Look up the not-yet-filled-in child deparse_columns structs */
4246 : 758 : leftcolinfo = deparse_columns_fetch(colinfo->leftrti, dpns);
4247 : 758 : rightcolinfo = deparse_columns_fetch(colinfo->rightrti, dpns);
4248 : :
4249 : : /*
4250 : : * If this join is unnamed, then we cannot substitute new aliases at
4251 : : * this level, so any name requirements pushed down to here must be
4252 : : * pushed down again to the children.
4253 : : */
4254 [ + + ]: 758 : if (rte->alias == NULL)
4255 : : {
4256 [ + + ]: 773 : for (i = 0; i < colinfo->num_cols; i++)
4257 : : {
4258 : 69 : char *colname = colinfo->colnames[i];
4259 : :
4260 [ + + ]: 69 : if (colname == NULL)
4261 : 12 : continue;
4262 : :
4263 : : /* Push down to left column, unless it's a system column */
4264 [ + + ]: 57 : if (leftattnos[i] > 0)
4265 : : {
4266 : 51 : expand_colnames_array_to(leftcolinfo, leftattnos[i]);
4267 : 51 : leftcolinfo->colnames[leftattnos[i] - 1] = colname;
4268 : : }
4269 : :
4270 : : /* Same on the righthand side */
4271 [ + - ]: 57 : if (rightattnos[i] > 0)
4272 : : {
4273 : 57 : expand_colnames_array_to(rightcolinfo, rightattnos[i]);
4274 : 57 : rightcolinfo->colnames[rightattnos[i] - 1] = colname;
4275 : : }
4276 : : }
4277 : : }
4278 : :
4279 : : /*
4280 : : * If there's a USING clause, select the USING column names and push
4281 : : * those names down to the children. We have two strategies:
4282 : : *
4283 : : * If dpns->unique_using is true, we force all USING names to be
4284 : : * unique across the whole query level. In principle we'd only need
4285 : : * the names of dangerous USING columns to be globally unique, but to
4286 : : * safely assign all USING names in a single pass, we have to enforce
4287 : : * the same uniqueness rule for all of them. However, if a USING
4288 : : * column's name has been pushed down from the parent, we should use
4289 : : * it as-is rather than making a uniqueness adjustment. This is
4290 : : * necessary when we're at an unnamed join, and it creates no risk of
4291 : : * ambiguity. Also, if there's a user-written output alias for a
4292 : : * merged column, we prefer to use that rather than the input name;
4293 : : * this simplifies the logic and seems likely to lead to less aliasing
4294 : : * overall.
4295 : : *
4296 : : * If dpns->unique_using is false, we only need USING names to be
4297 : : * unique within their own join RTE. We still need to honor
4298 : : * pushed-down names, though.
4299 : : *
4300 : : * Though significantly different in results, these two strategies are
4301 : : * implemented by the same code, with only the difference of whether
4302 : : * to put assigned names into dpns->using_names.
4303 : : */
4304 [ + + ]: 758 : if (j->usingClause)
4305 : : {
4306 : : /* Copy the input parentUsing list so we don't modify it */
4336 4307 : 215 : parentUsing = list_copy(parentUsing);
4308 : :
4309 : : /* USING names must correspond to the first join output columns */
4822 4310 : 215 : expand_colnames_array_to(colinfo, list_length(j->usingClause));
4311 : 215 : i = 0;
4312 [ + - + + : 508 : foreach(lc, j->usingClause)
+ + ]
4313 : : {
4314 : 293 : char *colname = strVal(lfirst(lc));
4315 : :
4316 : : /* Assert it's a merged column */
4317 [ + - - + ]: 293 : Assert(leftattnos[i] != 0 && rightattnos[i] != 0);
4318 : :
4319 : : /* Adopt passed-down name if any, else select unique name */
4320 [ + + ]: 293 : if (colinfo->colnames[i] != NULL)
4321 : 51 : colname = colinfo->colnames[i];
4322 : : else
4323 : : {
4324 : : /* Prefer user-written output alias if any */
4325 [ + + - + ]: 242 : if (rte->alias && i < list_length(rte->alias->colnames))
4822 tgl@sss.pgh.pa.us 4326 :UBC 0 : colname = strVal(list_nth(rte->alias->colnames, i));
4327 : : /* Make it appropriately unique */
4822 tgl@sss.pgh.pa.us 4328 :CBC 242 : colname = make_colname_unique(colname, dpns, colinfo);
4329 [ + + ]: 242 : if (dpns->unique_using)
4330 : 66 : dpns->using_names = lappend(dpns->using_names,
4331 : : colname);
4332 : : /* Save it as output column name, too */
4333 : 242 : colinfo->colnames[i] = colname;
4334 : : }
4335 : :
4336 : : /* Remember selected names for use later */
4337 : 293 : colinfo->usingNames = lappend(colinfo->usingNames, colname);
4336 4338 : 293 : parentUsing = lappend(parentUsing, colname);
4339 : :
4340 : : /* Push down to left column, unless it's a system column */
4822 4341 [ + - ]: 293 : if (leftattnos[i] > 0)
4342 : : {
4343 : 293 : expand_colnames_array_to(leftcolinfo, leftattnos[i]);
4344 : 293 : leftcolinfo->colnames[leftattnos[i] - 1] = colname;
4345 : : }
4346 : :
4347 : : /* Same on the righthand side */
4348 [ + - ]: 293 : if (rightattnos[i] > 0)
4349 : : {
4350 : 293 : expand_colnames_array_to(rightcolinfo, rightattnos[i]);
4351 : 293 : rightcolinfo->colnames[rightattnos[i] - 1] = colname;
4352 : : }
4353 : :
4354 : 293 : i++;
4355 : : }
4356 : : }
4357 : :
4358 : : /* Mark child deparse_columns structs with correct parentUsing info */
4336 4359 : 758 : leftcolinfo->parentUsing = parentUsing;
4360 : 758 : rightcolinfo->parentUsing = parentUsing;
4361 : :
4362 : : /* Now recursively assign USING column names in children */
4363 : 758 : set_using_names(dpns, j->larg, parentUsing);
4364 : 758 : set_using_names(dpns, j->rarg, parentUsing);
4365 : : }
4366 : : else
4822 tgl@sss.pgh.pa.us 4367 [ # # ]:UBC 0 : elog(ERROR, "unrecognized node type: %d",
4368 : : (int) nodeTag(jtnode));
4822 tgl@sss.pgh.pa.us 4369 :CBC 7134 : }
4370 : :
4371 : : /*
4372 : : * set_relation_column_names: select column aliases for a non-join RTE
4373 : : *
4374 : : * Column alias info is saved in *colinfo, which is assumed to be pre-zeroed.
4375 : : * If any colnames entries are already filled in, those override local
4376 : : * choices.
4377 : : */
4378 : : static void
4379 : 49018 : set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
4380 : : deparse_columns *colinfo)
4381 : : {
4382 : : int ncolumns;
4383 : : char **real_colnames;
4384 : : bool changed_any;
4385 : : int noldcolumns;
4386 : : int i;
4387 : : int j;
4388 : :
4389 : : /*
4390 : : * Construct an array of the current "real" column names of the RTE.
4391 : : * real_colnames[] will be indexed by physical column number, with NULL
4392 : : * entries for dropped columns.
4393 : : */
4394 [ + + ]: 49018 : if (rte->rtekind == RTE_RELATION)
4395 : : {
4396 : : /* Relation --- look to the system catalogs for up-to-date info */
4397 : : Relation rel;
4398 : : TupleDesc tupdesc;
4399 : :
4400 : 41655 : rel = relation_open(rte->relid, AccessShareLock);
4401 : 41655 : tupdesc = RelationGetDescr(rel);
4402 : :
4403 : 41655 : ncolumns = tupdesc->natts;
4404 : 41655 : real_colnames = (char **) palloc(ncolumns * sizeof(char *));
4405 : :
4406 [ + + ]: 261791 : for (i = 0; i < ncolumns; i++)
4407 : : {
3129 andres@anarazel.de 4408 : 220136 : Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
4409 : :
4410 [ + + ]: 220136 : if (attr->attisdropped)
4822 tgl@sss.pgh.pa.us 4411 : 1616 : real_colnames[i] = NULL;
4412 : : else
3129 andres@anarazel.de 4413 : 218520 : real_colnames[i] = pstrdup(NameStr(attr->attname));
4414 : : }
4822 tgl@sss.pgh.pa.us 4415 : 41655 : relation_close(rel, AccessShareLock);
4416 : : }
4417 : : else
4418 : : {
4419 : : /* Otherwise get the column names from eref or expandRTE() */
4420 : : List *colnames;
4421 : : ListCell *lc;
4422 : :
4423 : : /*
4424 : : * Functions returning composites have the annoying property that some
4425 : : * of the composite type's columns might have been dropped since the
4426 : : * query was parsed. If possible, use expandRTE() to handle that
4427 : : * case, since it has the tedious logic needed to find out about
4428 : : * dropped columns. However, if we're explaining a plan, then we
4429 : : * don't have rte->functions because the planner thinks that won't be
4430 : : * needed later, and that breaks expandRTE(). So in that case we have
4431 : : * to rely on rte->eref, which may lead us to report a dropped
4432 : : * column's old name; that seems close enough for EXPLAIN's purposes.
4433 : : *
4434 : : * For non-RELATION, non-FUNCTION RTEs, we can just look at rte->eref,
4435 : : * which should be sufficiently up-to-date: no other RTE types can
4436 : : * have columns get dropped from under them after parsing.
4437 : : */
1333 4438 [ + + + + ]: 7363 : if (rte->rtekind == RTE_FUNCTION && rte->functions != NIL)
4439 : : {
4440 : : /* Since we're not creating Vars, rtindex etc. don't matter */
423 dean.a.rasheed@gmail 4441 : 435 : expandRTE(rte, 1, 0, VAR_RETURNING_DEFAULT, -1,
4442 : : true /* include dropped */ , &colnames, NULL);
4443 : : }
4444 : : else
1333 tgl@sss.pgh.pa.us 4445 : 6928 : colnames = rte->eref->colnames;
4446 : :
4447 : 7363 : ncolumns = list_length(colnames);
4822 4448 : 7363 : real_colnames = (char **) palloc(ncolumns * sizeof(char *));
4449 : :
4450 : 7363 : i = 0;
1333 4451 [ + + + + : 23964 : foreach(lc, colnames)
+ + ]
4452 : : {
4453 : : /*
4454 : : * If the column name we find here is an empty string, then it's a
4455 : : * dropped column, so change to NULL.
4456 : : */
4257 4457 : 16601 : char *cname = strVal(lfirst(lc));
4458 : :
4459 [ + + ]: 16601 : if (cname[0] == '\0')
4460 : 27 : cname = NULL;
4461 : 16601 : real_colnames[i] = cname;
4822 4462 : 16601 : i++;
4463 : : }
4464 : : }
4465 : :
4466 : : /*
4467 : : * Ensure colinfo->colnames has a slot for each column. (It could be long
4468 : : * enough already, if we pushed down a name for the last column.) Note:
4469 : : * it's possible that there are now more columns than there were when the
4470 : : * query was parsed, ie colnames could be longer than rte->eref->colnames.
4471 : : * We must assign unique aliases to the new columns too, else there could
4472 : : * be unresolved conflicts when the view/rule is reloaded.
4473 : : */
4474 : 49018 : expand_colnames_array_to(colinfo, ncolumns);
4475 [ - + ]: 49018 : Assert(colinfo->num_cols == ncolumns);
4476 : :
4477 : : /*
4478 : : * Make sufficiently large new_colnames and is_new_col arrays, too.
4479 : : *
4480 : : * Note: because we leave colinfo->num_new_cols zero until after the loop,
4481 : : * colname_is_unique will not consult that array, which is fine because it
4482 : : * would only be duplicate effort.
4483 : : */
4484 : 49018 : colinfo->new_colnames = (char **) palloc(ncolumns * sizeof(char *));
4485 : 49018 : colinfo->is_new_col = (bool *) palloc(ncolumns * sizeof(bool));
4486 : :
4487 : : /* If the RTE is wide enough, use a hash table to avoid O(N^2) costs */
551 4488 : 49018 : build_colinfo_names_hash(colinfo);
4489 : :
4490 : : /*
4491 : : * Scan the columns, select a unique alias for each one, and store it in
4492 : : * colinfo->colnames and colinfo->new_colnames. The former array has NULL
4493 : : * entries for dropped columns, the latter omits them. Also mark
4494 : : * new_colnames entries as to whether they are new since parse time; this
4495 : : * is the case for entries beyond the length of rte->eref->colnames.
4496 : : */
4822 4497 : 49018 : noldcolumns = list_length(rte->eref->colnames);
4498 : 49018 : changed_any = false;
4499 : 49018 : j = 0;
4500 [ + + ]: 285755 : for (i = 0; i < ncolumns; i++)
4501 : : {
4502 : 236737 : char *real_colname = real_colnames[i];
4503 : 236737 : char *colname = colinfo->colnames[i];
4504 : :
4505 : : /* Skip dropped columns */
4506 [ + + ]: 236737 : if (real_colname == NULL)
4507 : : {
4508 [ - + ]: 1643 : Assert(colname == NULL); /* colnames[i] is already NULL */
4509 : 1643 : continue;
4510 : : }
4511 : :
4512 : : /* If alias already assigned, that's what to use */
4513 [ + + ]: 235094 : if (colname == NULL)
4514 : : {
4515 : : /* If user wrote an alias, prefer that over real column name */
4516 [ + + + + ]: 234559 : if (rte->alias && i < list_length(rte->alias->colnames))
4517 : 22613 : colname = strVal(list_nth(rte->alias->colnames, i));
4518 : : else
4519 : 211946 : colname = real_colname;
4520 : :
4521 : : /* Unique-ify and insert into colinfo */
4522 : 234559 : colname = make_colname_unique(colname, dpns, colinfo);
4523 : :
4524 : 234559 : colinfo->colnames[i] = colname;
551 4525 : 234559 : add_to_names_hash(colinfo, colname);
4526 : : }
4527 : :
4528 : : /* Put names of non-dropped columns in new_colnames[] too */
4822 4529 : 235094 : colinfo->new_colnames[j] = colname;
4530 : : /* And mark them as new or not */
4531 : 235094 : colinfo->is_new_col[j] = (i >= noldcolumns);
4532 : 235094 : j++;
4533 : :
4534 : : /* Remember if any assigned aliases differ from "real" name */
4535 [ + + + + ]: 235094 : if (!changed_any && strcmp(colname, real_colname) != 0)
4536 : 599 : changed_any = true;
4537 : : }
4538 : :
4539 : : /* We're now done needing the colinfo's names_hash */
551 4540 : 49018 : destroy_colinfo_names_hash(colinfo);
4541 : :
4542 : : /*
4543 : : * Set correct length for new_colnames[] array. (Note: if columns have
4544 : : * been added, colinfo->num_cols includes them, which is not really quite
4545 : : * right but is harmless, since any new columns must be at the end where
4546 : : * they won't affect varattnos of pre-existing columns.)
4547 : : */
4822 4548 : 49018 : colinfo->num_new_cols = j;
4549 : :
4550 : : /*
4551 : : * For a relation RTE, we need only print the alias column names if any
4552 : : * are different from the underlying "real" names. For a function RTE,
4553 : : * always emit a complete column alias list; this is to protect against
4554 : : * possible instability of the default column names (eg, from altering
4555 : : * parameter names). For tablefunc RTEs, we never print aliases, because
4556 : : * the column names are part of the clause itself. For other RTE types,
4557 : : * print if we changed anything OR if there were user-written column
4558 : : * aliases (since the latter would be part of the underlying "reality").
4559 : : */
4560 [ + + ]: 49018 : if (rte->rtekind == RTE_RELATION)
4561 : 41655 : colinfo->printaliases = changed_any;
4562 [ + + ]: 7363 : else if (rte->rtekind == RTE_FUNCTION)
4563 : 745 : colinfo->printaliases = true;
3294 alvherre@alvh.no-ip. 4564 [ + + ]: 6618 : else if (rte->rtekind == RTE_TABLEFUNC)
4565 : 88 : colinfo->printaliases = false;
4822 tgl@sss.pgh.pa.us 4566 [ + + + + ]: 6530 : else if (rte->alias && rte->alias->colnames != NIL)
4567 : 375 : colinfo->printaliases = true;
4568 : : else
4569 : 6155 : colinfo->printaliases = changed_any;
4570 : 49018 : }
4571 : :
4572 : : /*
4573 : : * set_join_column_names: select column aliases for a join RTE
4574 : : *
4575 : : * Column alias info is saved in *colinfo, which is assumed to be pre-zeroed.
4576 : : * If any colnames entries are already filled in, those override local
4577 : : * choices. Also, names for USING columns were already chosen by
4578 : : * set_using_names(). We further expect that column alias selection has been
4579 : : * completed for both input RTEs.
4580 : : */
4581 : : static void
4582 : 758 : set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
4583 : : deparse_columns *colinfo)
4584 : : {
4585 : : deparse_columns *leftcolinfo;
4586 : : deparse_columns *rightcolinfo;
4587 : : bool changed_any;
4588 : : int noldcolumns;
4589 : : int nnewcolumns;
4590 : 758 : Bitmapset *leftmerged = NULL;
4591 : 758 : Bitmapset *rightmerged = NULL;
4592 : : int i;
4593 : : int j;
4594 : : int ic;
4595 : : int jc;
4596 : :
4597 : : /* Look up the previously-filled-in child deparse_columns structs */
4598 : 758 : leftcolinfo = deparse_columns_fetch(colinfo->leftrti, dpns);
4599 : 758 : rightcolinfo = deparse_columns_fetch(colinfo->rightrti, dpns);
4600 : :
4601 : : /*
4602 : : * Ensure colinfo->colnames has a slot for each column. (It could be long
4603 : : * enough already, if we pushed down a name for the last column.) Note:
4604 : : * it's possible that one or both inputs now have more columns than there
4605 : : * were when the query was parsed, but we'll deal with that below. We
4606 : : * only need entries in colnames for pre-existing columns.
4607 : : */
4608 : 758 : noldcolumns = list_length(rte->eref->colnames);
4609 : 758 : expand_colnames_array_to(colinfo, noldcolumns);
4610 [ - + ]: 758 : Assert(colinfo->num_cols == noldcolumns);
4611 : :
4612 : : /* If the RTE is wide enough, use a hash table to avoid O(N^2) costs */
551 4613 : 758 : build_colinfo_names_hash(colinfo);
4614 : :
4615 : : /*
4616 : : * Scan the join output columns, select an alias for each one, and store
4617 : : * it in colinfo->colnames. If there are USING columns, set_using_names()
4618 : : * already selected their names, so we can start the loop at the first
4619 : : * non-merged column.
4620 : : */
4822 4621 : 758 : changed_any = false;
4622 [ + + ]: 25211 : for (i = list_length(colinfo->usingNames); i < noldcolumns; i++)
4623 : : {
4624 : 24453 : char *colname = colinfo->colnames[i];
4625 : : char *real_colname;
4626 : :
4627 : : /* Join column must refer to at least one input column */
2257 4628 [ + + - + ]: 24453 : Assert(colinfo->leftattnos[i] != 0 || colinfo->rightattnos[i] != 0);
4629 : :
4630 : : /* Get the child column name */
4822 4631 [ + + ]: 24453 : if (colinfo->leftattnos[i] > 0)
4632 : 17258 : real_colname = leftcolinfo->colnames[colinfo->leftattnos[i] - 1];
4633 [ + - ]: 7195 : else if (colinfo->rightattnos[i] > 0)
4634 : 7195 : real_colname = rightcolinfo->colnames[colinfo->rightattnos[i] - 1];
4635 : : else
4636 : : {
4637 : : /* We're joining system columns --- use eref name */
4618 tgl@sss.pgh.pa.us 4638 :UBC 0 : real_colname = strVal(list_nth(rte->eref->colnames, i));
4639 : : }
4640 : :
4641 : : /* If child col has been dropped, no need to assign a join colname */
2257 tgl@sss.pgh.pa.us 4642 [ + + ]:CBC 24453 : if (real_colname == NULL)
4643 : : {
4644 : 3 : colinfo->colnames[i] = NULL;
4645 : 3 : continue;
4646 : : }
4647 : :
4648 : : /* In an unnamed join, just report child column names as-is */
4822 4649 [ + + ]: 24450 : if (rte->alias == NULL)
4650 : : {
4651 : 24261 : colinfo->colnames[i] = real_colname;
551 4652 : 24261 : add_to_names_hash(colinfo, real_colname);
4822 4653 : 24261 : continue;
4654 : : }
4655 : :
4656 : : /* If alias already assigned, that's what to use */
4657 [ + - ]: 189 : if (colname == NULL)
4658 : : {
4659 : : /* If user wrote an alias, prefer that over real column name */
4660 [ + - + + ]: 189 : if (rte->alias && i < list_length(rte->alias->colnames))
4661 : 48 : colname = strVal(list_nth(rte->alias->colnames, i));
4662 : : else
4663 : 141 : colname = real_colname;
4664 : :
4665 : : /* Unique-ify and insert into colinfo */
4666 : 189 : colname = make_colname_unique(colname, dpns, colinfo);
4667 : :
4668 : 189 : colinfo->colnames[i] = colname;
551 4669 : 189 : add_to_names_hash(colinfo, colname);
4670 : : }
4671 : :
4672 : : /* Remember if any assigned aliases differ from "real" name */
4822 4673 [ + + + + ]: 189 : if (!changed_any && strcmp(colname, real_colname) != 0)
4674 : 12 : changed_any = true;
4675 : : }
4676 : :
4677 : : /*
4678 : : * Calculate number of columns the join would have if it were re-parsed
4679 : : * now, and create storage for the new_colnames and is_new_col arrays.
4680 : : *
4681 : : * Note: colname_is_unique will be consulting new_colnames[] during the
4682 : : * loops below, so its not-yet-filled entries must be zeroes.
4683 : : */
4684 : 1516 : nnewcolumns = leftcolinfo->num_new_cols + rightcolinfo->num_new_cols -
4685 : 758 : list_length(colinfo->usingNames);
4686 : 758 : colinfo->num_new_cols = nnewcolumns;
4687 : 758 : colinfo->new_colnames = (char **) palloc0(nnewcolumns * sizeof(char *));
4688 : 758 : colinfo->is_new_col = (bool *) palloc0(nnewcolumns * sizeof(bool));
4689 : :
4690 : : /*
4691 : : * Generating the new_colnames array is a bit tricky since any new columns
4692 : : * added since parse time must be inserted in the right places. This code
4693 : : * must match the parser, which will order a join's columns as merged
4694 : : * columns first (in USING-clause order), then non-merged columns from the
4695 : : * left input (in attnum order), then non-merged columns from the right
4696 : : * input (ditto). If one of the inputs is itself a join, its columns will
4697 : : * be ordered according to the same rule, which means newly-added columns
4698 : : * might not be at the end. We can figure out what's what by consulting
4699 : : * the leftattnos and rightattnos arrays plus the input is_new_col arrays.
4700 : : *
4701 : : * In these loops, i indexes leftattnos/rightattnos (so it's join varattno
4702 : : * less one), j indexes new_colnames/is_new_col, and ic/jc have similar
4703 : : * meanings for the current child RTE.
4704 : : */
4705 : :
4706 : : /* Handle merged columns; they are first and can't be new */
4707 : 758 : i = j = 0;
4708 : 758 : while (i < noldcolumns &&
4709 [ + - + - ]: 1051 : colinfo->leftattnos[i] != 0 &&
4710 [ + + ]: 1051 : colinfo->rightattnos[i] != 0)
4711 : : {
4712 : : /* column name is already determined and known unique */
4713 : 293 : colinfo->new_colnames[j] = colinfo->colnames[i];
4714 : 293 : colinfo->is_new_col[j] = false;
4715 : :
4716 : : /* build bitmapsets of child attnums of merged columns */
4717 [ + - ]: 293 : if (colinfo->leftattnos[i] > 0)
4718 : 293 : leftmerged = bms_add_member(leftmerged, colinfo->leftattnos[i]);
4719 [ + - ]: 293 : if (colinfo->rightattnos[i] > 0)
4720 : 293 : rightmerged = bms_add_member(rightmerged, colinfo->rightattnos[i]);
4721 : :
4722 : 293 : i++, j++;
4723 : : }
4724 : :
4725 : : /* Handle non-merged left-child columns */
4726 : 758 : ic = 0;
4727 [ + + ]: 18552 : for (jc = 0; jc < leftcolinfo->num_new_cols; jc++)
4728 : : {
4729 : 17794 : char *child_colname = leftcolinfo->new_colnames[jc];
4730 : :
4731 [ + + ]: 17794 : if (!leftcolinfo->is_new_col[jc])
4732 : : {
4733 : : /* Advance ic to next non-dropped old column of left child */
4734 [ + - ]: 17590 : while (ic < leftcolinfo->num_cols &&
4735 [ + + ]: 17590 : leftcolinfo->colnames[ic] == NULL)
4736 : 42 : ic++;
4737 [ - + ]: 17548 : Assert(ic < leftcolinfo->num_cols);
4738 : 17548 : ic++;
4739 : : /* If it is a merged column, we already processed it */
4740 [ + + ]: 17548 : if (bms_is_member(ic, leftmerged))
4741 : 293 : continue;
4742 : : /* Else, advance i to the corresponding existing join column */
4743 [ + - ]: 17258 : while (i < colinfo->num_cols &&
4744 [ + + ]: 17258 : colinfo->colnames[i] == NULL)
4745 : 3 : i++;
4746 [ - + ]: 17255 : Assert(i < colinfo->num_cols);
4747 [ - + ]: 17255 : Assert(ic == colinfo->leftattnos[i]);
4748 : : /* Use the already-assigned name of this column */
4749 : 17255 : colinfo->new_colnames[j] = colinfo->colnames[i];
4750 : 17255 : i++;
4751 : : }
4752 : : else
4753 : : {
4754 : : /*
4755 : : * Unique-ify the new child column name and assign, unless we're
4756 : : * in an unnamed join, in which case just copy
4757 : : */
4758 [ + + ]: 246 : if (rte->alias != NULL)
4759 : : {
4760 : 132 : colinfo->new_colnames[j] =
4761 : 66 : make_colname_unique(child_colname, dpns, colinfo);
4762 [ + + ]: 66 : if (!changed_any &&
4763 [ + + ]: 54 : strcmp(colinfo->new_colnames[j], child_colname) != 0)
4764 : 6 : changed_any = true;
4765 : : }
4766 : : else
4767 : 180 : colinfo->new_colnames[j] = child_colname;
551 4768 : 246 : add_to_names_hash(colinfo, colinfo->new_colnames[j]);
4769 : : }
4770 : :
4822 4771 : 17501 : colinfo->is_new_col[j] = leftcolinfo->is_new_col[jc];
4772 : 17501 : j++;
4773 : : }
4774 : :
4775 : : /* Handle non-merged right-child columns in exactly the same way */
4776 : 758 : ic = 0;
4777 [ + + ]: 8330 : for (jc = 0; jc < rightcolinfo->num_new_cols; jc++)
4778 : : {
4779 : 7572 : char *child_colname = rightcolinfo->new_colnames[jc];
4780 : :
4781 [ + + ]: 7572 : if (!rightcolinfo->is_new_col[jc])
4782 : : {
4783 : : /* Advance ic to next non-dropped old column of right child */
4784 [ + - ]: 7488 : while (ic < rightcolinfo->num_cols &&
4785 [ - + ]: 7488 : rightcolinfo->colnames[ic] == NULL)
4822 tgl@sss.pgh.pa.us 4786 :UBC 0 : ic++;
4822 tgl@sss.pgh.pa.us 4787 [ - + ]:CBC 7488 : Assert(ic < rightcolinfo->num_cols);
4788 : 7488 : ic++;
4789 : : /* If it is a merged column, we already processed it */
4790 [ + + ]: 7488 : if (bms_is_member(ic, rightmerged))
4791 : 293 : continue;
4792 : : /* Else, advance i to the corresponding existing join column */
4793 [ + - ]: 7195 : while (i < colinfo->num_cols &&
4794 [ - + ]: 7195 : colinfo->colnames[i] == NULL)
4822 tgl@sss.pgh.pa.us 4795 :UBC 0 : i++;
4822 tgl@sss.pgh.pa.us 4796 [ - + ]:CBC 7195 : Assert(i < colinfo->num_cols);
4797 [ - + ]: 7195 : Assert(ic == colinfo->rightattnos[i]);
4798 : : /* Use the already-assigned name of this column */
4799 : 7195 : colinfo->new_colnames[j] = colinfo->colnames[i];
4800 : 7195 : i++;
4801 : : }
4802 : : else
4803 : : {
4804 : : /*
4805 : : * Unique-ify the new child column name and assign, unless we're
4806 : : * in an unnamed join, in which case just copy
4807 : : */
4808 [ + + ]: 84 : if (rte->alias != NULL)
4809 : : {
4810 : 24 : colinfo->new_colnames[j] =
4811 : 12 : make_colname_unique(child_colname, dpns, colinfo);
4812 [ + - ]: 12 : if (!changed_any &&
4813 [ + + ]: 12 : strcmp(colinfo->new_colnames[j], child_colname) != 0)
4814 : 6 : changed_any = true;
4815 : : }
4816 : : else
4817 : 72 : colinfo->new_colnames[j] = child_colname;
551 4818 : 84 : add_to_names_hash(colinfo, colinfo->new_colnames[j]);
4819 : : }
4820 : :
4822 4821 : 7279 : colinfo->is_new_col[j] = rightcolinfo->is_new_col[jc];
4822 : 7279 : j++;
4823 : : }
4824 : :
4825 : : /* Assert we processed the right number of columns */
4826 : : #ifdef USE_ASSERT_CHECKING
4827 [ - + - - ]: 758 : while (i < colinfo->num_cols && colinfo->colnames[i] == NULL)
4822 tgl@sss.pgh.pa.us 4828 :UBC 0 : i++;
4822 tgl@sss.pgh.pa.us 4829 [ - + ]:CBC 758 : Assert(i == colinfo->num_cols);
4830 [ - + ]: 758 : Assert(j == nnewcolumns);
4831 : : #endif
4832 : :
4833 : : /* We're now done needing the colinfo's names_hash */
551 4834 : 758 : destroy_colinfo_names_hash(colinfo);
4835 : :
4836 : : /*
4837 : : * For a named join, print column aliases if we changed any from the child
4838 : : * names. Unnamed joins cannot print aliases.
4839 : : */
4822 4840 [ + + ]: 758 : if (rte->alias != NULL)
4841 : 54 : colinfo->printaliases = changed_any;
4842 : : else
4843 : 704 : colinfo->printaliases = false;
4844 : 758 : }
4845 : :
4846 : : /*
4847 : : * colname_is_unique: is colname distinct from already-chosen column names?
4848 : : *
4849 : : * dpns is query-wide info, colinfo is for the column's RTE
4850 : : */
4851 : : static bool
3057 peter_e@gmx.net 4852 : 236252 : colname_is_unique(const char *colname, deparse_namespace *dpns,
4853 : : deparse_columns *colinfo)
4854 : : {
4855 : : int i;
4856 : : ListCell *lc;
4857 : :
4858 : : /*
4859 : : * If we have a hash table, consult that instead of linearly scanning the
4860 : : * colinfo's strings.
4861 : : */
551 tgl@sss.pgh.pa.us 4862 [ + + ]: 236252 : if (colinfo->names_hash)
4863 : : {
4864 [ - + ]: 9001 : if (hash_search(colinfo->names_hash,
4865 : : colname,
4866 : : HASH_FIND,
4867 : : NULL) != NULL)
4822 tgl@sss.pgh.pa.us 4868 :UBC 0 : return false;
4869 : : }
4870 : : else
4871 : : {
4872 : : /* Check against already-assigned column aliases within RTE */
551 tgl@sss.pgh.pa.us 4873 [ + + ]:CBC 3095694 : for (i = 0; i < colinfo->num_cols; i++)
4874 : : {
4875 : 2869591 : char *oldname = colinfo->colnames[i];
4876 : :
4877 [ + + + + ]: 2869591 : if (oldname && strcmp(oldname, colname) == 0)
4878 : 1148 : return false;
4879 : : }
4880 : :
4881 : : /*
4882 : : * If we're building a new_colnames array, check that too (this will
4883 : : * be partially but not completely redundant with the previous checks)
4884 : : */
4885 [ + + ]: 226739 : for (i = 0; i < colinfo->num_new_cols; i++)
4886 : : {
4887 : 648 : char *oldname = colinfo->new_colnames[i];
4888 : :
4889 [ + + + + ]: 648 : if (oldname && strcmp(oldname, colname) == 0)
4890 : 12 : return false;
4891 : : }
4892 : :
4893 : : /*
4894 : : * Also check against names already assigned for parent-join USING
4895 : : * cols
4896 : : */
4897 [ + + + + : 227393 : foreach(lc, colinfo->parentUsing)
+ + ]
4898 : : {
4899 : 1305 : char *oldname = (char *) lfirst(lc);
4900 : :
4901 [ + + ]: 1305 : if (strcmp(oldname, colname) == 0)
4902 : 3 : return false;
4903 : : }
4904 : : }
4905 : :
4906 : : /*
4907 : : * Also check against USING-column names that must be globally unique.
4908 : : * These are not hashed, but there should be few of them.
4909 : : */
4910 [ + + + + : 235518 : foreach(lc, dpns->using_names)
+ + ]
4911 : : {
4336 4912 : 450 : char *oldname = (char *) lfirst(lc);
4913 : :
4914 [ + + ]: 450 : if (strcmp(oldname, colname) == 0)
4915 : 21 : return false;
4916 : : }
4917 : :
4822 4918 : 235068 : return true;
4919 : : }
4920 : :
4921 : : /*
4922 : : * make_colname_unique: modify colname if necessary to make it unique
4923 : : *
4924 : : * dpns is query-wide info, colinfo is for the column's RTE
4925 : : */
4926 : : static char *
4927 : 235068 : make_colname_unique(char *colname, deparse_namespace *dpns,
4928 : : deparse_columns *colinfo)
4929 : : {
4930 : : /*
4931 : : * If the selected name isn't unique, append digits to make it so. For a
4932 : : * very long input name, we might have to truncate to stay within
4933 : : * NAMEDATALEN.
4934 : : */
4935 [ + + ]: 235068 : if (!colname_is_unique(colname, dpns, colinfo))
4936 : : {
3772 4937 : 822 : int colnamelen = strlen(colname);
4938 : 822 : char *modname = (char *) palloc(colnamelen + 16);
4822 4939 : 822 : int i = 0;
4940 : :
4941 : : do
4942 : : {
3772 4943 : 1184 : i++;
4944 : : for (;;)
4945 : : {
4946 : 1184 : memcpy(modname, colname, colnamelen);
4947 : 1184 : sprintf(modname + colnamelen, "_%d", i);
4948 [ + - ]: 1184 : if (strlen(modname) < NAMEDATALEN)
4949 : 1184 : break;
4950 : : /* drop chars from colname to keep all the digits */
3772 tgl@sss.pgh.pa.us 4951 :UBC 0 : colnamelen = pg_mbcliplen(colname, colnamelen,
4952 : : colnamelen - 1);
4953 : : }
4822 tgl@sss.pgh.pa.us 4954 [ + + ]:CBC 1184 : } while (!colname_is_unique(modname, dpns, colinfo));
4955 : 822 : colname = modname;
4956 : : }
4957 : 235068 : return colname;
4958 : : }
4959 : :
4960 : : /*
4961 : : * expand_colnames_array_to: make colinfo->colnames at least n items long
4962 : : *
4963 : : * Any added array entries are initialized to zero.
4964 : : */
4965 : : static void
4966 : 50685 : expand_colnames_array_to(deparse_columns *colinfo, int n)
4967 : : {
4968 [ + + ]: 50685 : if (n > colinfo->num_cols)
4969 : : {
4970 [ + + ]: 49309 : if (colinfo->colnames == NULL)
1219 peter@eisentraut.org 4971 : 48592 : colinfo->colnames = palloc0_array(char *, n);
4972 : : else
4973 : 717 : colinfo->colnames = repalloc0_array(colinfo->colnames, char *, colinfo->num_cols, n);
4822 tgl@sss.pgh.pa.us 4974 : 49309 : colinfo->num_cols = n;
4975 : : }
4976 : 50685 : }
4977 : :
4978 : : /*
4979 : : * build_colinfo_names_hash: optionally construct a hash table for colinfo
4980 : : */
4981 : : static void
551 4982 : 49776 : build_colinfo_names_hash(deparse_columns *colinfo)
4983 : : {
4984 : : HASHCTL hash_ctl;
4985 : : int i;
4986 : : ListCell *lc;
4987 : :
4988 : : /*
4989 : : * Use a hash table only for RTEs with at least 32 columns. (The cutoff
4990 : : * is somewhat arbitrary, but let's choose it so that this code does get
4991 : : * exercised in the regression tests.)
4992 : : */
4993 [ + + ]: 49776 : if (colinfo->num_cols < 32)
4994 : 49100 : return;
4995 : :
4996 : : /*
4997 : : * Set up the hash table. The entries are just strings with no other
4998 : : * payload.
4999 : : */
5000 : 676 : hash_ctl.keysize = NAMEDATALEN;
5001 : 676 : hash_ctl.entrysize = NAMEDATALEN;
5002 : 676 : hash_ctl.hcxt = CurrentMemoryContext;
5003 : 1352 : colinfo->names_hash = hash_create("deparse_columns names",
5004 : 676 : colinfo->num_cols + colinfo->num_new_cols,
5005 : : &hash_ctl,
5006 : : HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);
5007 : :
5008 : : /*
5009 : : * Preload the hash table with any names already present (these would have
5010 : : * come from set_using_names).
5011 : : */
5012 [ + + ]: 31703 : for (i = 0; i < colinfo->num_cols; i++)
5013 : : {
5014 : 31027 : char *oldname = colinfo->colnames[i];
5015 : :
5016 [ - + ]: 31027 : if (oldname)
551 tgl@sss.pgh.pa.us 5017 :UBC 0 : add_to_names_hash(colinfo, oldname);
5018 : : }
5019 : :
551 tgl@sss.pgh.pa.us 5020 [ - + ]:CBC 676 : for (i = 0; i < colinfo->num_new_cols; i++)
5021 : : {
551 tgl@sss.pgh.pa.us 5022 :UBC 0 : char *oldname = colinfo->new_colnames[i];
5023 : :
5024 [ # # ]: 0 : if (oldname)
5025 : 0 : add_to_names_hash(colinfo, oldname);
5026 : : }
5027 : :
551 tgl@sss.pgh.pa.us 5028 [ - + - - :CBC 676 : foreach(lc, colinfo->parentUsing)
- + ]
5029 : : {
551 tgl@sss.pgh.pa.us 5030 :UBC 0 : char *oldname = (char *) lfirst(lc);
5031 : :
5032 : 0 : add_to_names_hash(colinfo, oldname);
5033 : : }
5034 : : }
5035 : :
5036 : : /*
5037 : : * add_to_names_hash: add a string to the names_hash, if we're using one
5038 : : */
5039 : : static void
551 tgl@sss.pgh.pa.us 5040 :CBC 259339 : add_to_names_hash(deparse_columns *colinfo, const char *name)
5041 : : {
5042 [ + + ]: 259339 : if (colinfo->names_hash)
5043 : 31027 : (void) hash_search(colinfo->names_hash,
5044 : : name,
5045 : : HASH_ENTER,
5046 : : NULL);
5047 : 259339 : }
5048 : :
5049 : : /*
5050 : : * destroy_colinfo_names_hash: destroy hash table when done with it
5051 : : */
5052 : : static void
5053 : 49776 : destroy_colinfo_names_hash(deparse_columns *colinfo)
5054 : : {
5055 [ + + ]: 49776 : if (colinfo->names_hash)
5056 : : {
5057 : 676 : hash_destroy(colinfo->names_hash);
5058 : 676 : colinfo->names_hash = NULL;
5059 : : }
5060 : 49776 : }
5061 : :
5062 : : /*
5063 : : * identify_join_columns: figure out where columns of a join come from
5064 : : *
5065 : : * Fills the join-specific fields of the colinfo struct, except for
5066 : : * usingNames which is filled later.
5067 : : */
5068 : : static void
4822 5069 : 758 : identify_join_columns(JoinExpr *j, RangeTblEntry *jrte,
5070 : : deparse_columns *colinfo)
5071 : : {
5072 : : int numjoincols;
5073 : : int jcolno;
5074 : : int rcolno;
5075 : : ListCell *lc;
5076 : :
5077 : : /* Extract left/right child RT indexes */
5078 [ + + ]: 758 : if (IsA(j->larg, RangeTblRef))
5079 : 485 : colinfo->leftrti = ((RangeTblRef *) j->larg)->rtindex;
5080 [ + - ]: 273 : else if (IsA(j->larg, JoinExpr))
5081 : 273 : colinfo->leftrti = ((JoinExpr *) j->larg)->rtindex;
5082 : : else
4822 tgl@sss.pgh.pa.us 5083 [ # # ]:UBC 0 : elog(ERROR, "unrecognized node type in jointree: %d",
5084 : : (int) nodeTag(j->larg));
4822 tgl@sss.pgh.pa.us 5085 [ + - ]:CBC 758 : if (IsA(j->rarg, RangeTblRef))
5086 : 758 : colinfo->rightrti = ((RangeTblRef *) j->rarg)->rtindex;
4822 tgl@sss.pgh.pa.us 5087 [ # # ]:UBC 0 : else if (IsA(j->rarg, JoinExpr))
5088 : 0 : colinfo->rightrti = ((JoinExpr *) j->rarg)->rtindex;
5089 : : else
5090 [ # # ]: 0 : elog(ERROR, "unrecognized node type in jointree: %d",
5091 : : (int) nodeTag(j->rarg));
5092 : :
5093 : : /* Assert children will be processed earlier than join in second pass */
4822 tgl@sss.pgh.pa.us 5094 [ - + ]:CBC 758 : Assert(colinfo->leftrti < j->rtindex);
5095 [ - + ]: 758 : Assert(colinfo->rightrti < j->rtindex);
5096 : :
5097 : : /* Initialize result arrays with zeroes */
5098 : 758 : numjoincols = list_length(jrte->joinaliasvars);
5099 [ - + ]: 758 : Assert(numjoincols == list_length(jrte->eref->colnames));
5100 : 758 : colinfo->leftattnos = (int *) palloc0(numjoincols * sizeof(int));
5101 : 758 : colinfo->rightattnos = (int *) palloc0(numjoincols * sizeof(int));
5102 : :
5103 : : /*
5104 : : * Deconstruct RTE's joinleftcols/joinrightcols into desired format.
5105 : : * Recall that the column(s) merged due to USING are the first column(s)
5106 : : * of the join output. We need not do anything special while scanning
5107 : : * joinleftcols, but while scanning joinrightcols we must distinguish
5108 : : * merged from unmerged columns.
5109 : : */
2257 5110 : 758 : jcolno = 0;
5111 [ + - + + : 18309 : foreach(lc, jrte->joinleftcols)
+ + ]
5112 : : {
5113 : 17551 : int leftattno = lfirst_int(lc);
5114 : :
5115 : 17551 : colinfo->leftattnos[jcolno++] = leftattno;
5116 : : }
5117 : 758 : rcolno = 0;
5118 [ + - + + : 8246 : foreach(lc, jrte->joinrightcols)
+ + ]
5119 : : {
5120 : 7488 : int rightattno = lfirst_int(lc);
5121 : :
5122 [ + + ]: 7488 : if (rcolno < jrte->joinmergedcols) /* merged column? */
5123 : 293 : colinfo->rightattnos[rcolno] = rightattno;
5124 : : else
5125 : 7195 : colinfo->rightattnos[jcolno++] = rightattno;
5126 : 7488 : rcolno++;
5127 : : }
5128 [ - + ]: 758 : Assert(jcolno == numjoincols);
4822 5129 : 758 : }
5130 : :
5131 : : /*
5132 : : * get_rtable_name: convenience function to get a previously assigned RTE alias
5133 : : *
5134 : : * The RTE must belong to the topmost namespace level in "context".
5135 : : */
5136 : : static char *
4923 5137 : 3361 : get_rtable_name(int rtindex, deparse_context *context)
5138 : : {
5139 : 3361 : deparse_namespace *dpns = (deparse_namespace *) linitial(context->namespaces);
5140 : :
5141 [ + - - + ]: 3361 : Assert(rtindex > 0 && rtindex <= list_length(dpns->rtable_names));
5142 : 3361 : return (char *) list_nth(dpns->rtable_names, rtindex - 1);
5143 : : }
5144 : :
5145 : : /*
5146 : : * set_deparse_plan: set up deparse_namespace to parse subexpressions
5147 : : * of a given Plan node
5148 : : *
5149 : : * This sets the plan, outer_plan, inner_plan, outer_tlist, inner_tlist,
5150 : : * and index_tlist fields. Caller must already have adjusted the ancestors
5151 : : * list if necessary. Note that the rtable, subplans, and ctes fields do
5152 : : * not need to change when shifting attention to different plan nodes in a
5153 : : * single plan tree.
5154 : : */
5155 : : static void
2286 5156 : 76913 : set_deparse_plan(deparse_namespace *dpns, Plan *plan)
5157 : : {
5158 : 76913 : dpns->plan = plan;
5159 : :
5160 : : /*
5161 : : * We special-case Append and MergeAppend to pretend that the first child
5162 : : * plan is the OUTER referent; we have to interpret OUTER Vars in their
5163 : : * tlists according to one of the children, and the first one is the most
5164 : : * natural choice.
5165 : : */
5166 [ + + ]: 76913 : if (IsA(plan, Append))
5167 : 2259 : dpns->outer_plan = linitial(((Append *) plan)->appendplans);
5168 [ + + ]: 74654 : else if (IsA(plan, MergeAppend))
5169 : 276 : dpns->outer_plan = linitial(((MergeAppend *) plan)->mergeplans);
5170 : : else
5171 : 74378 : dpns->outer_plan = outerPlan(plan);
5172 : :
5173 [ + + ]: 76913 : if (dpns->outer_plan)
5174 : 37164 : dpns->outer_tlist = dpns->outer_plan->targetlist;
5175 : : else
5269 5176 : 39749 : dpns->outer_tlist = NIL;
5177 : :
5178 : : /*
5179 : : * For a SubqueryScan, pretend the subplan is INNER referent. (We don't
5180 : : * use OUTER because that could someday conflict with the normal meaning.)
5181 : : * Likewise, for a CteScan, pretend the subquery's plan is INNER referent.
5182 : : * For a WorkTableScan, locate the parent RecursiveUnion plan node and use
5183 : : * that as INNER referent.
5184 : : *
5185 : : * For MERGE, pretend the ModifyTable's source plan (its outer plan) is
5186 : : * INNER referent. This is the join from the target relation to the data
5187 : : * source, and all INNER_VAR Vars in other parts of the query refer to its
5188 : : * targetlist.
5189 : : *
5190 : : * For ON CONFLICT DO SELECT/UPDATE we just need the inner tlist to point
5191 : : * to the excluded expression's tlist. (Similar to the SubqueryScan we
5192 : : * don't want to reuse OUTER, it's used for RETURNING in some modify table
5193 : : * cases, although not INSERT .. CONFLICT).
5194 : : */
2286 5195 [ + + ]: 76913 : if (IsA(plan, SubqueryScan))
5196 : 343 : dpns->inner_plan = ((SubqueryScan *) plan)->subplan;
5197 [ + + ]: 76570 : else if (IsA(plan, CteScan))
5198 : 286 : dpns->inner_plan = list_nth(dpns->subplans,
5199 : 286 : ((CteScan *) plan)->ctePlanId - 1);
1641 5200 [ + + ]: 76284 : else if (IsA(plan, WorkTableScan))
5201 : 87 : dpns->inner_plan = find_recursive_union(dpns,
5202 : : (WorkTableScan *) plan);
2286 5203 [ + + ]: 76197 : else if (IsA(plan, ModifyTable))
5204 : : {
1448 alvherre@alvh.no-ip. 5205 [ + + ]: 222 : if (((ModifyTable *) plan)->operation == CMD_MERGE)
728 dean.a.rasheed@gmail 5206 : 30 : dpns->inner_plan = outerPlan(plan);
5207 : : else
5208 : 192 : dpns->inner_plan = plan;
5209 : : }
5210 : : else
5211 : 75975 : dpns->inner_plan = innerPlan(plan);
5212 : :
5213 [ + + + + ]: 76913 : if (IsA(plan, ModifyTable) && ((ModifyTable *) plan)->operation == CMD_INSERT)
5214 : 106 : dpns->inner_tlist = ((ModifyTable *) plan)->exclRelTlist;
2286 tgl@sss.pgh.pa.us 5215 [ + + ]: 76807 : else if (dpns->inner_plan)
5216 : 13398 : dpns->inner_tlist = dpns->inner_plan->targetlist;
5217 : : else
5269 5218 : 63409 : dpns->inner_tlist = NIL;
5219 : :
5220 : : /* Set up referent for INDEX_VAR Vars, if needed */
2286 5221 [ + + ]: 76913 : if (IsA(plan, IndexOnlyScan))
5222 : 1749 : dpns->index_tlist = ((IndexOnlyScan *) plan)->indextlist;
5223 [ + + ]: 75164 : else if (IsA(plan, ForeignScan))
5224 : 1558 : dpns->index_tlist = ((ForeignScan *) plan)->fdw_scan_tlist;
5225 [ - + ]: 73606 : else if (IsA(plan, CustomScan))
2286 tgl@sss.pgh.pa.us 5226 :UBC 0 : dpns->index_tlist = ((CustomScan *) plan)->custom_scan_tlist;
5227 : : else
5269 tgl@sss.pgh.pa.us 5228 :CBC 73606 : dpns->index_tlist = NIL;
5724 5229 : 76913 : }
5230 : :
5231 : : /*
5232 : : * Locate the ancestor plan node that is the RecursiveUnion generating
5233 : : * the WorkTableScan's work table. We can match on wtParam, since that
5234 : : * should be unique within the plan tree.
5235 : : */
5236 : : static Plan *
1641 5237 : 87 : find_recursive_union(deparse_namespace *dpns, WorkTableScan *wtscan)
5238 : : {
5239 : : ListCell *lc;
5240 : :
5241 [ + - + - : 219 : foreach(lc, dpns->ancestors)
+ - ]
5242 : : {
5243 : 219 : Plan *ancestor = (Plan *) lfirst(lc);
5244 : :
5245 [ + + ]: 219 : if (IsA(ancestor, RecursiveUnion) &&
5246 [ + - ]: 87 : ((RecursiveUnion *) ancestor)->wtParam == wtscan->wtParam)
5247 : 87 : return ancestor;
5248 : : }
1641 tgl@sss.pgh.pa.us 5249 [ # # ]:UBC 0 : elog(ERROR, "could not find RecursiveUnion for WorkTableScan with wtParam %d",
5250 : : wtscan->wtParam);
5251 : : return NULL;
5252 : : }
5253 : :
5254 : : /*
5255 : : * push_child_plan: temporarily transfer deparsing attention to a child plan
5256 : : *
5257 : : * When expanding an OUTER_VAR or INNER_VAR reference, we must adjust the
5258 : : * deparse context in case the referenced expression itself uses
5259 : : * OUTER_VAR/INNER_VAR. We modify the top stack entry in-place to avoid
5260 : : * affecting levelsup issues (although in a Plan tree there really shouldn't
5261 : : * be any).
5262 : : *
5263 : : * Caller must provide a local deparse_namespace variable to save the
5264 : : * previous state for pop_child_plan.
5265 : : */
5266 : : static void
2286 tgl@sss.pgh.pa.us 5267 :CBC 44716 : push_child_plan(deparse_namespace *dpns, Plan *plan,
5268 : : deparse_namespace *save_dpns)
5269 : : {
5270 : : /* Save state for restoration later */
5724 5271 : 44716 : *save_dpns = *dpns;
5272 : :
5273 : : /* Link current plan node into ancestors list */
2286 5274 : 44716 : dpns->ancestors = lcons(dpns->plan, dpns->ancestors);
5275 : :
5276 : : /* Set attention on selected child */
5277 : 44716 : set_deparse_plan(dpns, plan);
9160 5278 : 44716 : }
5279 : :
5280 : : /*
5281 : : * pop_child_plan: undo the effects of push_child_plan
5282 : : */
5283 : : static void
5724 5284 : 44716 : pop_child_plan(deparse_namespace *dpns, deparse_namespace *save_dpns)
5285 : : {
5286 : : List *ancestors;
5287 : :
5288 : : /* Get rid of ancestors list cell added by push_child_plan */
4945 5289 : 44716 : ancestors = list_delete_first(dpns->ancestors);
5290 : :
5291 : : /* Restore fields changed by push_child_plan */
5724 5292 : 44716 : *dpns = *save_dpns;
5293 : :
5294 : : /* Make sure dpns->ancestors is right (may be unnecessary) */
4945 5295 : 44716 : dpns->ancestors = ancestors;
5724 5296 : 44716 : }
5297 : :
5298 : : /*
5299 : : * push_ancestor_plan: temporarily transfer deparsing attention to an
5300 : : * ancestor plan
5301 : : *
5302 : : * When expanding a Param reference, we must adjust the deparse context
5303 : : * to match the plan node that contains the expression being printed;
5304 : : * otherwise we'd fail if that expression itself contains a Param or
5305 : : * OUTER_VAR/INNER_VAR/INDEX_VAR variable.
5306 : : *
5307 : : * The target ancestor is conveniently identified by the ListCell holding it
5308 : : * in dpns->ancestors.
5309 : : *
5310 : : * Caller must provide a local deparse_namespace variable to save the
5311 : : * previous state for pop_ancestor_plan.
5312 : : */
5313 : : static void
5314 : 2361 : push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell,
5315 : : deparse_namespace *save_dpns)
5316 : : {
2286 5317 : 2361 : Plan *plan = (Plan *) lfirst(ancestor_cell);
5318 : :
5319 : : /* Save state for restoration later */
5724 5320 : 2361 : *save_dpns = *dpns;
5321 : :
5322 : : /* Build a new ancestor list with just this node's ancestors */
2435 5323 : 2361 : dpns->ancestors =
5324 : 2361 : list_copy_tail(dpns->ancestors,
5325 : 2361 : list_cell_number(dpns->ancestors, ancestor_cell) + 1);
5326 : :
5327 : : /* Set attention on selected ancestor */
2286 5328 : 2361 : set_deparse_plan(dpns, plan);
5724 5329 : 2361 : }
5330 : :
5331 : : /*
5332 : : * pop_ancestor_plan: undo the effects of push_ancestor_plan
5333 : : */
5334 : : static void
5335 : 2361 : pop_ancestor_plan(deparse_namespace *dpns, deparse_namespace *save_dpns)
5336 : : {
5337 : : /* Free the ancestor list made in push_ancestor_plan */
5338 : 2361 : list_free(dpns->ancestors);
5339 : :
5340 : : /* Restore fields changed by push_ancestor_plan */
5341 : 2361 : *dpns = *save_dpns;
5342 : 2361 : }
5343 : :
5344 : :
5345 : : /* ----------
5346 : : * make_ruledef - reconstruct the CREATE RULE command
5347 : : * for a given pg_rewrite tuple
5348 : : * ----------
5349 : : */
5350 : : static void
8264 5351 : 282 : make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
5352 : : int prettyFlags)
5353 : : {
5354 : : char *rulename;
5355 : : char ev_type;
5356 : : Oid ev_class;
5357 : : bool is_instead;
5358 : : char *ev_qual;
5359 : : char *ev_action;
5360 : : List *actions;
5361 : : Relation ev_relation;
3156 5362 : 282 : TupleDesc viewResultDesc = NULL;
5363 : : int fno;
5364 : : Datum dat;
5365 : : bool isnull;
5366 : :
5367 : : /*
5368 : : * Get the attribute values from the rules tuple
5369 : : */
8732 5370 : 282 : fno = SPI_fnumber(rulettc, "rulename");
5371 : 282 : dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
5372 [ - + ]: 282 : Assert(!isnull);
5373 : 282 : rulename = NameStr(*(DatumGetName(dat)));
5374 : :
10057 bruce@momjian.us 5375 : 282 : fno = SPI_fnumber(rulettc, "ev_type");
8732 tgl@sss.pgh.pa.us 5376 : 282 : dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
5377 [ - + ]: 282 : Assert(!isnull);
5378 : 282 : ev_type = DatumGetChar(dat);
5379 : :
10057 bruce@momjian.us 5380 : 282 : fno = SPI_fnumber(rulettc, "ev_class");
8732 tgl@sss.pgh.pa.us 5381 : 282 : dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
5382 [ - + ]: 282 : Assert(!isnull);
5383 : 282 : ev_class = DatumGetObjectId(dat);
5384 : :
10057 bruce@momjian.us 5385 : 282 : fno = SPI_fnumber(rulettc, "is_instead");
8732 tgl@sss.pgh.pa.us 5386 : 282 : dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
5387 [ - + ]: 282 : Assert(!isnull);
5388 : 282 : is_instead = DatumGetBool(dat);
5389 : :
10057 bruce@momjian.us 5390 : 282 : fno = SPI_fnumber(rulettc, "ev_qual");
5391 : 282 : ev_qual = SPI_getvalue(ruletup, rulettc, fno);
1959 tgl@sss.pgh.pa.us 5392 [ - + ]: 282 : Assert(ev_qual != NULL);
5393 : :
10057 bruce@momjian.us 5394 : 282 : fno = SPI_fnumber(rulettc, "ev_action");
5395 : 282 : ev_action = SPI_getvalue(ruletup, rulettc, fno);
1959 tgl@sss.pgh.pa.us 5396 [ - + ]: 282 : Assert(ev_action != NULL);
5397 : 282 : actions = (List *) stringToNode(ev_action);
5398 [ - + ]: 282 : if (actions == NIL)
1959 tgl@sss.pgh.pa.us 5399 [ # # ]:UBC 0 : elog(ERROR, "invalid empty ev_action list");
5400 : :
2610 andres@anarazel.de 5401 :CBC 282 : ev_relation = table_open(ev_class, AccessShareLock);
5402 : :
5403 : : /*
5404 : : * Build the rules definition text
5405 : : */
8264 tgl@sss.pgh.pa.us 5406 : 282 : appendStringInfo(buf, "CREATE RULE %s AS",
5407 : : quote_identifier(rulename));
5408 : :
5409 [ + - ]: 282 : if (prettyFlags & PRETTYFLAG_INDENT)
8259 bruce@momjian.us 5410 : 282 : appendStringInfoString(buf, "\n ON ");
5411 : : else
8259 bruce@momjian.us 5412 :UBC 0 : appendStringInfoString(buf, " ON ");
5413 : :
5414 : : /* The event the rule is fired for */
10057 bruce@momjian.us 5415 [ + + + + :CBC 282 : switch (ev_type)
- ]
5416 : : {
5417 : 3 : case '1':
4518 rhaas@postgresql.org 5418 : 3 : appendStringInfoString(buf, "SELECT");
3156 tgl@sss.pgh.pa.us 5419 : 3 : viewResultDesc = RelationGetDescr(ev_relation);
10065 bruce@momjian.us 5420 : 3 : break;
5421 : :
10057 5422 : 77 : case '2':
4518 rhaas@postgresql.org 5423 : 77 : appendStringInfoString(buf, "UPDATE");
10065 bruce@momjian.us 5424 : 77 : break;
5425 : :
10057 5426 : 150 : case '3':
4518 rhaas@postgresql.org 5427 : 150 : appendStringInfoString(buf, "INSERT");
10065 bruce@momjian.us 5428 : 150 : break;
5429 : :
10057 5430 : 52 : case '4':
4518 rhaas@postgresql.org 5431 : 52 : appendStringInfoString(buf, "DELETE");
10065 bruce@momjian.us 5432 : 52 : break;
5433 : :
10057 bruce@momjian.us 5434 :UBC 0 : default:
8267 tgl@sss.pgh.pa.us 5435 [ # # ]: 0 : ereport(ERROR,
5436 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5437 : : errmsg("rule \"%s\" has unsupported event type %d",
5438 : : rulename, ev_type)));
5439 : : break;
5440 : : }
5441 : :
5442 : : /* The relation the rule is fired on */
2939 tgl@sss.pgh.pa.us 5443 :CBC 282 : appendStringInfo(buf, " TO %s",
5444 [ + + ]: 282 : (prettyFlags & PRETTYFLAG_SCHEMA) ?
5445 : 57 : generate_relation_name(ev_class, NIL) :
5446 : 225 : generate_qualified_relation_name(ev_class));
5447 : :
5448 : : /* If the rule has an event qualification, add it */
1959 5449 [ + + ]: 282 : if (strcmp(ev_qual, "<>") != 0)
5450 : : {
5451 : : Node *qual;
5452 : : Query *query;
5453 : : deparse_context context;
5454 : : deparse_namespace dpns;
5455 : :
8264 5456 [ + - ]: 61 : if (prettyFlags & PRETTYFLAG_INDENT)
8259 bruce@momjian.us 5457 : 61 : appendStringInfoString(buf, "\n ");
4518 rhaas@postgresql.org 5458 : 61 : appendStringInfoString(buf, " WHERE ");
5459 : :
10057 bruce@momjian.us 5460 : 61 : qual = stringToNode(ev_qual);
5461 : :
5462 : : /*
5463 : : * We need to make a context for recognizing any Vars in the qual
5464 : : * (which can only be references to OLD and NEW). Use the rtable of
5465 : : * the first query in the action list for this purpose.
5466 : : */
7963 neilc@samurai.com 5467 : 61 : query = (Query *) linitial(actions);
5468 : :
5469 : : /*
5470 : : * If the action is INSERT...SELECT, OLD/NEW have been pushed down
5471 : : * into the SELECT, and that's what we need to look at. (Ugly kluge
5472 : : * ... try to fix this when we redesign querytrees.)
5473 : : */
8875 tgl@sss.pgh.pa.us 5474 : 61 : query = getInsertSelectQuery(query, NULL);
5475 : :
5476 : : /* Must acquire locks right away; see notes in get_query_def() */
4392 5477 : 61 : AcquireRewriteLocks(query, false, false);
5478 : :
9660 5479 : 61 : context.buf = buf;
7959 neilc@samurai.com 5480 : 61 : context.namespaces = list_make1(&dpns);
563 tgl@sss.pgh.pa.us 5481 : 61 : context.resultDesc = NULL;
5482 : 61 : context.targetList = NIL;
6286 5483 : 61 : context.windowClause = NIL;
7959 neilc@samurai.com 5484 : 61 : context.varprefix = (list_length(query->rtable) != 1);
8264 tgl@sss.pgh.pa.us 5485 : 61 : context.prettyFlags = prettyFlags;
4829 5486 : 61 : context.wrapColumn = WRAP_COLUMN_DEFAULT;
8264 5487 : 61 : context.indentLevel = PRETTYINDENT_STD;
563 5488 : 61 : context.colNamesVisible = true;
5489 : 61 : context.inGroupBy = false;
5490 : 61 : context.varInOrderBy = false;
2286 5491 : 61 : context.appendparents = NULL;
5492 : :
4822 5493 : 61 : set_deparse_for_query(&dpns, query, NIL);
5494 : :
8578 5495 : 61 : get_rule_expr(qual, &context, false);
5496 : : }
5497 : :
4518 rhaas@postgresql.org 5498 : 282 : appendStringInfoString(buf, " DO ");
5499 : :
5500 : : /* The INSTEAD keyword (if so) */
10057 bruce@momjian.us 5501 [ + + ]: 282 : if (is_instead)
4518 rhaas@postgresql.org 5502 : 168 : appendStringInfoString(buf, "INSTEAD ");
5503 : :
5504 : : /* Finally the rules actions */
7959 neilc@samurai.com 5505 [ + + ]: 282 : if (list_length(actions) > 1)
5506 : : {
5507 : : ListCell *action;
5508 : : Query *query;
5509 : :
4518 rhaas@postgresql.org 5510 : 10 : appendStringInfoChar(buf, '(');
10057 bruce@momjian.us 5511 [ + - + + : 30 : foreach(action, actions)
+ + ]
5512 : : {
5513 : 20 : query = (Query *) lfirst(action);
1394 tgl@sss.pgh.pa.us 5514 : 20 : get_query_def(query, buf, NIL, viewResultDesc, true,
5515 : : prettyFlags, WRAP_COLUMN_DEFAULT, 0);
8264 5516 [ + - ]: 20 : if (prettyFlags)
4518 rhaas@postgresql.org 5517 : 20 : appendStringInfoString(buf, ";\n");
5518 : : else
4518 rhaas@postgresql.org 5519 :UBC 0 : appendStringInfoString(buf, "; ");
5520 : : }
4518 rhaas@postgresql.org 5521 :CBC 10 : appendStringInfoString(buf, ");");
5522 : : }
5523 : : else
5524 : : {
5525 : : Query *query;
5526 : :
7963 neilc@samurai.com 5527 : 272 : query = (Query *) linitial(actions);
1394 tgl@sss.pgh.pa.us 5528 : 272 : get_query_def(query, buf, NIL, viewResultDesc, true,
5529 : : prettyFlags, WRAP_COLUMN_DEFAULT, 0);
4518 rhaas@postgresql.org 5530 : 272 : appendStringInfoChar(buf, ';');
5531 : : }
5532 : :
2610 andres@anarazel.de 5533 : 282 : table_close(ev_relation, AccessShareLock);
10065 bruce@momjian.us 5534 : 282 : }
5535 : :
5536 : :
5537 : : /* ----------
5538 : : * make_viewdef - reconstruct the SELECT part of a
5539 : : * view rewrite rule
5540 : : * ----------
5541 : : */
5542 : : static void
8264 tgl@sss.pgh.pa.us 5543 : 1774 : make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
5544 : : int prettyFlags, int wrapColumn)
5545 : : {
5546 : : Query *query;
5547 : : char ev_type;
5548 : : Oid ev_class;
5549 : : bool is_instead;
5550 : : char *ev_qual;
5551 : : char *ev_action;
5552 : : List *actions;
5553 : : Relation ev_relation;
5554 : : int fno;
5555 : : Datum dat;
5556 : : bool isnull;
5557 : :
5558 : : /*
5559 : : * Get the attribute values from the rules tuple
5560 : : */
10057 bruce@momjian.us 5561 : 1774 : fno = SPI_fnumber(rulettc, "ev_type");
3156 tgl@sss.pgh.pa.us 5562 : 1774 : dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
5563 [ - + ]: 1774 : Assert(!isnull);
5564 : 1774 : ev_type = DatumGetChar(dat);
5565 : :
10057 bruce@momjian.us 5566 : 1774 : fno = SPI_fnumber(rulettc, "ev_class");
3156 tgl@sss.pgh.pa.us 5567 : 1774 : dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
5568 [ - + ]: 1774 : Assert(!isnull);
5569 : 1774 : ev_class = DatumGetObjectId(dat);
5570 : :
10057 bruce@momjian.us 5571 : 1774 : fno = SPI_fnumber(rulettc, "is_instead");
3156 tgl@sss.pgh.pa.us 5572 : 1774 : dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
5573 [ - + ]: 1774 : Assert(!isnull);
5574 : 1774 : is_instead = DatumGetBool(dat);
5575 : :
10057 bruce@momjian.us 5576 : 1774 : fno = SPI_fnumber(rulettc, "ev_qual");
5577 : 1774 : ev_qual = SPI_getvalue(ruletup, rulettc, fno);
1959 tgl@sss.pgh.pa.us 5578 [ - + ]: 1774 : Assert(ev_qual != NULL);
5579 : :
10057 bruce@momjian.us 5580 : 1774 : fno = SPI_fnumber(rulettc, "ev_action");
5581 : 1774 : ev_action = SPI_getvalue(ruletup, rulettc, fno);
1959 tgl@sss.pgh.pa.us 5582 [ - + ]: 1774 : Assert(ev_action != NULL);
5583 : 1774 : actions = (List *) stringToNode(ev_action);
5584 : :
7959 neilc@samurai.com 5585 [ - + ]: 1774 : if (list_length(actions) != 1)
5586 : : {
5587 : : /* keep output buffer empty and leave */
9661 tgl@sss.pgh.pa.us 5588 :UBC 0 : return;
5589 : : }
5590 : :
7963 neilc@samurai.com 5591 :CBC 1774 : query = (Query *) linitial(actions);
5592 : :
4574 kgrittn@postgresql.o 5593 [ + - + - ]: 1774 : if (ev_type != '1' || !is_instead ||
8620 tgl@sss.pgh.pa.us 5594 [ + - - + ]: 1774 : strcmp(ev_qual, "<>") != 0 || query->commandType != CMD_SELECT)
5595 : : {
5596 : : /* keep output buffer empty and leave */
9661 tgl@sss.pgh.pa.us 5597 :UBC 0 : return;
5598 : : }
5599 : :
2610 andres@anarazel.de 5600 :CBC 1774 : ev_relation = table_open(ev_class, AccessShareLock);
5601 : :
1394 tgl@sss.pgh.pa.us 5602 : 1774 : get_query_def(query, buf, NIL, RelationGetDescr(ev_relation), true,
5603 : : prettyFlags, wrapColumn, 0);
4518 rhaas@postgresql.org 5604 : 1774 : appendStringInfoChar(buf, ';');
5605 : :
2610 andres@anarazel.de 5606 : 1774 : table_close(ev_relation, AccessShareLock);
5607 : : }
5608 : :
5609 : :
5610 : : /* ----------
5611 : : * get_query_def - Parse back one query parsetree
5612 : : *
5613 : : * query: parsetree to be displayed
5614 : : * buf: output text is appended to buf
5615 : : * parentnamespace: list (initially empty) of outer-level deparse_namespace's
5616 : : * resultDesc: if not NULL, the output tuple descriptor for the view
5617 : : * represented by a SELECT query. We use the column names from it
5618 : : * to label SELECT output columns, in preference to names in the query
5619 : : * colNamesVisible: true if the surrounding context cares about the output
5620 : : * column names at all (as, for example, an EXISTS() context does not);
5621 : : * when false, we can suppress dummy column labels such as "?column?"
5622 : : * prettyFlags: bitmask of PRETTYFLAG_XXX options
5623 : : * wrapColumn: maximum line length, or -1 to disable wrapping
5624 : : * startIndent: initial indentation amount
5625 : : * ----------
5626 : : */
5627 : : static void
8620 tgl@sss.pgh.pa.us 5628 : 2870 : get_query_def(Query *query, StringInfo buf, List *parentnamespace,
5629 : : TupleDesc resultDesc, bool colNamesVisible,
5630 : : int prettyFlags, int wrapColumn, int startIndent)
5631 : : {
5632 : : deparse_context context;
5633 : : deparse_namespace dpns;
5634 : : int rtable_size;
5635 : :
5636 : : /* Guard against excessively long or deeply-nested queries */
4337 5637 [ - + ]: 2870 : CHECK_FOR_INTERRUPTS();
5638 : 2870 : check_stack_depth();
5639 : :
551 rguo@postgresql.org 5640 : 5740 : rtable_size = query->hasGroupRTE ?
5641 [ + + ]: 2870 : list_length(query->rtable) - 1 :
5642 : 2757 : list_length(query->rtable);
5643 : :
5644 : : /*
5645 : : * Replace any Vars in the query's targetlist and havingQual that
5646 : : * reference GROUP outputs with the underlying grouping expressions.
5647 : : *
5648 : : * We can safely pass NULL for the root here. Preserving varnullingrels
5649 : : * makes no difference to the deparsed source text.
5650 : : */
5651 [ + + ]: 2870 : if (query->hasGroupRTE)
5652 : : {
5653 : 113 : query->targetList = (List *)
5654 : 113 : flatten_group_exprs(NULL, query, (Node *) query->targetList);
5655 : 113 : query->havingQual =
5656 : 113 : flatten_group_exprs(NULL, query, query->havingQual);
5657 : : }
5658 : :
5659 : : /*
5660 : : * Before we begin to examine the query, acquire locks on referenced
5661 : : * relations, and fix up deleted columns in JOIN RTEs. This ensures
5662 : : * consistent results. Note we assume it's OK to scribble on the passed
5663 : : * querytree!
5664 : : *
5665 : : * We are only deparsing the query (we are not about to execute it), so we
5666 : : * only need AccessShareLock on the relations it mentions.
5667 : : */
4392 tgl@sss.pgh.pa.us 5668 : 2870 : AcquireRewriteLocks(query, false, false);
5669 : :
9660 5670 : 2870 : context.buf = buf;
7963 neilc@samurai.com 5671 : 2870 : context.namespaces = lcons(&dpns, list_copy(parentnamespace));
563 tgl@sss.pgh.pa.us 5672 : 2870 : context.resultDesc = NULL;
5673 : 2870 : context.targetList = NIL;
6286 5674 : 2870 : context.windowClause = NIL;
9160 5675 [ + + + + ]: 2870 : context.varprefix = (parentnamespace != NIL ||
5676 : : rtable_size != 1);
8264 5677 : 2870 : context.prettyFlags = prettyFlags;
4829 5678 : 2870 : context.wrapColumn = wrapColumn;
8264 5679 : 2870 : context.indentLevel = startIndent;
563 5680 : 2870 : context.colNamesVisible = colNamesVisible;
5681 : 2870 : context.inGroupBy = false;
5682 : 2870 : context.varInOrderBy = false;
2286 5683 : 2870 : context.appendparents = NULL;
5684 : :
4822 5685 : 2870 : set_deparse_for_query(&dpns, query, parentnamespace);
5686 : :
10057 bruce@momjian.us 5687 [ + + + + : 2870 : switch (query->commandType)
+ + + - ]
5688 : : {
9791 5689 : 2547 : case CMD_SELECT:
5690 : : /* We set context.resultDesc only if it's a SELECT */
563 tgl@sss.pgh.pa.us 5691 : 2547 : context.resultDesc = resultDesc;
5692 : 2547 : get_select_query_def(query, &context);
10057 bruce@momjian.us 5693 : 2547 : break;
5694 : :
5695 : 77 : case CMD_UPDATE:
563 tgl@sss.pgh.pa.us 5696 : 77 : get_update_query_def(query, &context);
10057 bruce@momjian.us 5697 : 77 : break;
5698 : :
5699 : 173 : case CMD_INSERT:
563 tgl@sss.pgh.pa.us 5700 : 173 : get_insert_query_def(query, &context);
10057 bruce@momjian.us 5701 : 173 : break;
5702 : :
5703 : 38 : case CMD_DELETE:
563 tgl@sss.pgh.pa.us 5704 : 38 : get_delete_query_def(query, &context);
10057 bruce@momjian.us 5705 : 38 : break;
5706 : :
1043 tgl@sss.pgh.pa.us 5707 : 6 : case CMD_MERGE:
563 5708 : 6 : get_merge_query_def(query, &context);
1043 5709 : 6 : break;
5710 : :
10057 bruce@momjian.us 5711 : 21 : case CMD_NOTHING:
4518 rhaas@postgresql.org 5712 : 21 : appendStringInfoString(buf, "NOTHING");
10057 bruce@momjian.us 5713 : 21 : break;
5714 : :
9202 tgl@sss.pgh.pa.us 5715 : 8 : case CMD_UTILITY:
5716 : 8 : get_utility_query_def(query, &context);
5717 : 8 : break;
5718 : :
10057 bruce@momjian.us 5719 :UBC 0 : default:
8267 tgl@sss.pgh.pa.us 5720 [ # # ]: 0 : elog(ERROR, "unrecognized query command type: %d",
5721 : : query->commandType);
5722 : : break;
5723 : : }
10065 bruce@momjian.us 5724 :CBC 2870 : }
5725 : :
5726 : : /* ----------
5727 : : * get_values_def - Parse back a VALUES list
5728 : : * ----------
5729 : : */
5730 : : static void
7165 mail@joeconway.com 5731 : 136 : get_values_def(List *values_lists, deparse_context *context)
5732 : : {
5733 : 136 : StringInfo buf = context->buf;
5734 : 136 : bool first_list = true;
5735 : : ListCell *vtl;
5736 : :
5737 : 136 : appendStringInfoString(buf, "VALUES ");
5738 : :
5739 [ + - + + : 389 : foreach(vtl, values_lists)
+ + ]
5740 : : {
5741 : 253 : List *sublist = (List *) lfirst(vtl);
5742 : 253 : bool first_col = true;
5743 : : ListCell *lc;
5744 : :
5745 [ + + ]: 253 : if (first_list)
5746 : 136 : first_list = false;
5747 : : else
5748 : 117 : appendStringInfoString(buf, ", ");
5749 : :
5750 : 253 : appendStringInfoChar(buf, '(');
5751 [ + - + + : 979 : foreach(lc, sublist)
+ + ]
5752 : : {
7102 bruce@momjian.us 5753 : 726 : Node *col = (Node *) lfirst(lc);
5754 : :
7165 mail@joeconway.com 5755 [ + + ]: 726 : if (first_col)
5756 : 253 : first_col = false;
5757 : : else
5758 : 473 : appendStringInfoChar(buf, ',');
5759 : :
5760 : : /*
5761 : : * Print the value. Whole-row Vars need special treatment.
5762 : : */
3511 tgl@sss.pgh.pa.us 5763 : 726 : get_rule_expr_toplevel(col, context, false);
5764 : : }
7165 mail@joeconway.com 5765 : 253 : appendStringInfoChar(buf, ')');
5766 : : }
5767 : 136 : }
5768 : :
5769 : : /* ----------
5770 : : * get_with_clause - Parse back a WITH clause
5771 : : * ----------
5772 : : */
5773 : : static void
6371 tgl@sss.pgh.pa.us 5774 : 2841 : get_with_clause(Query *query, deparse_context *context)
5775 : : {
5776 : 2841 : StringInfo buf = context->buf;
5777 : : const char *sep;
5778 : : ListCell *l;
5779 : :
5780 [ + + ]: 2841 : if (query->cteList == NIL)
5781 : 2793 : return;
5782 : :
5783 [ + - ]: 48 : if (PRETTY_INDENT(context))
5784 : : {
5785 : 48 : context->indentLevel += PRETTYINDENT_STD;
5786 : 48 : appendStringInfoChar(buf, ' ');
5787 : : }
5788 : :
5789 [ + + ]: 48 : if (query->hasRecursive)
5790 : 28 : sep = "WITH RECURSIVE ";
5791 : : else
5792 : 20 : sep = "WITH ";
5793 [ + - + + : 121 : foreach(l, query->cteList)
+ + ]
5794 : : {
5795 : 73 : CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
5796 : :
5797 : 73 : appendStringInfoString(buf, sep);
5798 : 73 : appendStringInfoString(buf, quote_identifier(cte->ctename));
5799 [ + + ]: 73 : if (cte->aliascolnames)
5800 : : {
5801 : 28 : bool first = true;
5802 : : ListCell *col;
5803 : :
5804 : 28 : appendStringInfoChar(buf, '(');
5805 [ + - + + : 74 : foreach(col, cte->aliascolnames)
+ + ]
5806 : : {
5807 [ + + ]: 46 : if (first)
5808 : 28 : first = false;
5809 : : else
5810 : 18 : appendStringInfoString(buf, ", ");
5811 : 46 : appendStringInfoString(buf,
5812 : 46 : quote_identifier(strVal(lfirst(col))));
5813 : : }
5814 : 28 : appendStringInfoChar(buf, ')');
5815 : : }
2584 5816 : 73 : appendStringInfoString(buf, " AS ");
5817 [ + + - - ]: 73 : switch (cte->ctematerialized)
5818 : : {
5819 : 64 : case CTEMaterializeDefault:
5820 : 64 : break;
5821 : 9 : case CTEMaterializeAlways:
5822 : 9 : appendStringInfoString(buf, "MATERIALIZED ");
5823 : 9 : break;
2584 tgl@sss.pgh.pa.us 5824 :UBC 0 : case CTEMaterializeNever:
5825 : 0 : appendStringInfoString(buf, "NOT MATERIALIZED ");
5826 : 0 : break;
5827 : : }
2584 tgl@sss.pgh.pa.us 5828 :CBC 73 : appendStringInfoChar(buf, '(');
6371 5829 [ + - ]: 73 : if (PRETTY_INDENT(context))
5830 : 73 : appendContextKeyword(context, "", 0, 0, 0);
5831 : 73 : get_query_def((Query *) cte->ctequery, buf, context->namespaces, NULL,
5832 : : true,
5833 : : context->prettyFlags, context->wrapColumn,
5834 : : context->indentLevel);
5835 [ + - ]: 73 : if (PRETTY_INDENT(context))
5836 : 73 : appendContextKeyword(context, "", 0, 0, 0);
5837 : 73 : appendStringInfoChar(buf, ')');
5838 : :
1868 peter@eisentraut.org 5839 [ + + ]: 73 : if (cte->search_clause)
5840 : : {
5841 : 3 : bool first = true;
5842 : : ListCell *lc;
5843 : :
5844 : 3 : appendStringInfo(buf, " SEARCH %s FIRST BY ",
5845 [ - + ]: 3 : cte->search_clause->search_breadth_first ? "BREADTH" : "DEPTH");
5846 : :
5847 [ + - + + : 9 : foreach(lc, cte->search_clause->search_col_list)
+ + ]
5848 : : {
5849 [ + + ]: 6 : if (first)
5850 : 3 : first = false;
5851 : : else
5852 : 3 : appendStringInfoString(buf, ", ");
5853 : 6 : appendStringInfoString(buf,
5854 : 6 : quote_identifier(strVal(lfirst(lc))));
5855 : : }
5856 : :
5857 : 3 : appendStringInfo(buf, " SET %s", quote_identifier(cte->search_clause->search_seq_column));
5858 : : }
5859 : :
5860 [ + + ]: 73 : if (cte->cycle_clause)
5861 : : {
5862 : 6 : bool first = true;
5863 : : ListCell *lc;
5864 : :
5865 : 6 : appendStringInfoString(buf, " CYCLE ");
5866 : :
5867 [ + - + + : 18 : foreach(lc, cte->cycle_clause->cycle_col_list)
+ + ]
5868 : : {
5869 [ + + ]: 12 : if (first)
5870 : 6 : first = false;
5871 : : else
5872 : 6 : appendStringInfoString(buf, ", ");
5873 : 12 : appendStringInfoString(buf,
5874 : 12 : quote_identifier(strVal(lfirst(lc))));
5875 : : }
5876 : :
5877 : 6 : appendStringInfo(buf, " SET %s", quote_identifier(cte->cycle_clause->cycle_mark_column));
5878 : :
5879 : : {
1842 5880 : 6 : Const *cmv = castNode(Const, cte->cycle_clause->cycle_mark_value);
5881 : 6 : Const *cmd = castNode(Const, cte->cycle_clause->cycle_mark_default);
5882 : :
5883 [ + + + - : 9 : if (!(cmv->consttype == BOOLOID && !cmv->constisnull && DatumGetBool(cmv->constvalue) == true &&
+ - - + ]
5884 [ + - + - ]: 3 : cmd->consttype == BOOLOID && !cmd->constisnull && DatumGetBool(cmd->constvalue) == false))
5885 : : {
5886 : 3 : appendStringInfoString(buf, " TO ");
5887 : 3 : get_rule_expr(cte->cycle_clause->cycle_mark_value, context, false);
5888 : 3 : appendStringInfoString(buf, " DEFAULT ");
5889 : 3 : get_rule_expr(cte->cycle_clause->cycle_mark_default, context, false);
5890 : : }
5891 : : }
5892 : :
1868 5893 : 6 : appendStringInfo(buf, " USING %s", quote_identifier(cte->cycle_clause->cycle_path_column));
5894 : : }
5895 : :
6371 tgl@sss.pgh.pa.us 5896 : 73 : sep = ", ";
5897 : : }
5898 : :
5899 [ + - ]: 48 : if (PRETTY_INDENT(context))
5900 : : {
5901 : 48 : context->indentLevel -= PRETTYINDENT_STD;
5902 : 48 : appendContextKeyword(context, "", 0, 0, 0);
5903 : : }
5904 : : else
6371 tgl@sss.pgh.pa.us 5905 :UBC 0 : appendStringInfoChar(buf, ' ');
5906 : : }
5907 : :
5908 : : /* ----------
5909 : : * get_select_query_def - Parse back a SELECT parsetree
5910 : : * ----------
5911 : : */
5912 : : static void
563 tgl@sss.pgh.pa.us 5913 :CBC 2547 : get_select_query_def(Query *query, deparse_context *context)
5914 : : {
9292 5915 : 2547 : StringInfo buf = context->buf;
5916 : : bool force_colno;
5917 : : ListCell *l;
5918 : :
5919 : : /* Insert the WITH clause if given */
6371 5920 : 2547 : get_with_clause(query, context);
5921 : :
5922 : : /* Subroutines may need to consult the SELECT targetlist and windowClause */
563 5923 : 2547 : context->targetList = query->targetList;
6286 5924 : 2547 : context->windowClause = query->windowClause;
5925 : :
5926 : : /*
5927 : : * If the Query node has a setOperations tree, then it's the top level of
5928 : : * a UNION/INTERSECT/EXCEPT query; only the WITH, ORDER BY and LIMIT
5929 : : * fields are interesting in the top query itself.
5930 : : */
9292 5931 [ + + ]: 2547 : if (query->setOperations)
5932 : : {
563 5933 : 82 : get_setop_query(query->setOperations, query, context);
5934 : : /* ORDER BY clauses must be simple in this case */
9100 5935 : 82 : force_colno = true;
5936 : : }
5937 : : else
5938 : : {
563 5939 : 2465 : get_basic_select_query(query, context);
9100 5940 : 2465 : force_colno = false;
5941 : : }
5942 : :
5943 : : /* Add the ORDER BY clause if given */
9292 5944 [ + + ]: 2547 : if (query->sortClause != NIL)
5945 : : {
8259 bruce@momjian.us 5946 : 90 : appendContextKeyword(context, " ORDER BY ",
5947 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
6286 tgl@sss.pgh.pa.us 5948 : 90 : get_rule_orderby(query->sortClause, query->targetList,
5949 : : force_colno, context);
5950 : : }
5951 : :
5952 : : /*
5953 : : * Add the LIMIT/OFFSET clauses if given. If non-default options, use the
5954 : : * standard spelling of LIMIT.
5955 : : */
9271 5956 [ + + ]: 2547 : if (query->limitOffset != NULL)
5957 : : {
8264 5958 : 16 : appendContextKeyword(context, " OFFSET ",
5959 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
8578 5960 : 16 : get_rule_expr(query->limitOffset, context, false);
5961 : : }
9271 5962 [ + + ]: 2547 : if (query->limitCount != NULL)
5963 : : {
2168 alvherre@alvh.no-ip. 5964 [ + + ]: 43 : if (query->limitOption == LIMIT_OPTION_WITH_TIES)
5965 : : {
5966 : : /*
5967 : : * The limitCount arg is a c_expr, so it needs parens. Simple
5968 : : * literals and function expressions would not need parens, but
5969 : : * unfortunately it's hard to tell if the expression will be
5970 : : * printed as a simple literal like 123 or as a typecast
5971 : : * expression, like '-123'::int4. The grammar accepts the former
5972 : : * without quoting, but not the latter.
5973 : : */
5974 : 24 : appendContextKeyword(context, " FETCH FIRST ",
5975 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
300 heikki.linnakangas@i 5976 : 24 : appendStringInfoChar(buf, '(');
8578 tgl@sss.pgh.pa.us 5977 : 24 : get_rule_expr(query->limitCount, context, false);
300 heikki.linnakangas@i 5978 : 24 : appendStringInfoChar(buf, ')');
1977 drowley@postgresql.o 5979 : 24 : appendStringInfoString(buf, " ROWS WITH TIES");
5980 : : }
5981 : : else
5982 : : {
2168 alvherre@alvh.no-ip. 5983 : 19 : appendContextKeyword(context, " LIMIT ",
5984 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
5985 [ + + ]: 19 : if (IsA(query->limitCount, Const) &&
5986 [ + - ]: 8 : ((Const *) query->limitCount)->constisnull)
5987 : 8 : appendStringInfoString(buf, "ALL");
5988 : : else
5989 : 11 : get_rule_expr(query->limitCount, context, false);
5990 : : }
5991 : : }
5992 : :
5993 : : /* Add FOR [KEY] UPDATE/SHARE clauses if present */
5982 tgl@sss.pgh.pa.us 5994 [ + + ]: 2547 : if (query->hasForUpdate)
5995 : : {
5996 [ + - + + : 6 : foreach(l, query->rowMarks)
+ + ]
5997 : : {
5998 : 3 : RowMarkClause *rc = (RowMarkClause *) lfirst(l);
5999 : :
6000 : : /* don't print implicit clauses */
6001 [ - + ]: 3 : if (rc->pushedDown)
5982 tgl@sss.pgh.pa.us 6002 :UBC 0 : continue;
6003 : :
31 dean.a.rasheed@gmail 6004 :GNC 3 : appendContextKeyword(context,
6005 : 3 : get_lock_clause_strength(rc->strength),
6006 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
6007 : :
5982 tgl@sss.pgh.pa.us 6008 :CBC 3 : appendStringInfo(buf, " OF %s",
4923 6009 : 3 : quote_identifier(get_rtable_name(rc->rti,
6010 : : context)));
4177 alvherre@alvh.no-ip. 6011 [ - + ]: 3 : if (rc->waitPolicy == LockWaitError)
4518 rhaas@postgresql.org 6012 :UBC 0 : appendStringInfoString(buf, " NOWAIT");
4177 alvherre@alvh.no-ip. 6013 [ - + ]:CBC 3 : else if (rc->waitPolicy == LockWaitSkip)
4177 alvherre@alvh.no-ip. 6014 :UBC 0 : appendStringInfoString(buf, " SKIP LOCKED");
6015 : : }
6016 : : }
9292 tgl@sss.pgh.pa.us 6017 :CBC 2547 : }
6018 : :
6019 : : static char *
31 dean.a.rasheed@gmail 6020 :GNC 6 : get_lock_clause_strength(LockClauseStrength strength)
6021 : : {
6022 [ - - - - : 6 : switch (strength)
+ - ]
6023 : : {
31 dean.a.rasheed@gmail 6024 :UNC 0 : case LCS_NONE:
6025 : : /* we intentionally throw an error for LCS_NONE */
6026 [ # # ]: 0 : elog(ERROR, "unrecognized LockClauseStrength %d",
6027 : : (int) strength);
6028 : : break;
6029 : 0 : case LCS_FORKEYSHARE:
6030 : 0 : return " FOR KEY SHARE";
6031 : 0 : case LCS_FORSHARE:
6032 : 0 : return " FOR SHARE";
6033 : 0 : case LCS_FORNOKEYUPDATE:
6034 : 0 : return " FOR NO KEY UPDATE";
31 dean.a.rasheed@gmail 6035 :GNC 6 : case LCS_FORUPDATE:
6036 : 6 : return " FOR UPDATE";
6037 : : }
31 dean.a.rasheed@gmail 6038 :UNC 0 : return NULL; /* keep compiler quiet */
6039 : : }
6040 : :
6041 : : /*
6042 : : * Detect whether query looks like SELECT ... FROM VALUES(),
6043 : : * with no need to rename the output columns of the VALUES RTE.
6044 : : * If so, return the VALUES RTE. Otherwise return NULL.
6045 : : */
6046 : : static RangeTblEntry *
2311 tgl@sss.pgh.pa.us 6047 :CBC 2465 : get_simple_values_rte(Query *query, TupleDesc resultDesc)
6048 : : {
4956 6049 : 2465 : RangeTblEntry *result = NULL;
6050 : : ListCell *lc;
6051 : :
6052 : : /*
6053 : : * We want to detect a match even if the Query also contains OLD or NEW
6054 : : * rule RTEs. So the idea is to scan the rtable and see if there is only
6055 : : * one inFromCl RTE that is a VALUES RTE.
6056 : : */
6057 [ + + + + : 2651 : foreach(lc, query->rtable)
+ + ]
6058 : : {
6059 : 2241 : RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
6060 : :
6061 [ + + + - ]: 2241 : if (rte->rtekind == RTE_VALUES && rte->inFromCl)
6062 : : {
6063 [ - + ]: 114 : if (result)
6064 : 2055 : return NULL; /* multiple VALUES (probably not possible) */
6065 : 114 : result = rte;
6066 : : }
6067 [ + + + + ]: 2127 : else if (rte->rtekind == RTE_RELATION && !rte->inFromCl)
6068 : 72 : continue; /* ignore rule entries */
6069 : : else
6070 : 2055 : return NULL; /* something else -> not simple VALUES */
6071 : : }
6072 : :
6073 : : /*
6074 : : * We don't need to check the targetlist in any great detail, because
6075 : : * parser/analyze.c will never generate a "bare" VALUES RTE --- they only
6076 : : * appear inside auto-generated sub-queries with very restricted
6077 : : * structure. However, DefineView might have modified the tlist by
6078 : : * injecting new column aliases, or we might have some other column
6079 : : * aliases forced by a resultDesc. We can only simplify if the RTE's
6080 : : * column names match the names that get_target_list() would select.
6081 : : */
4036 6082 [ + + ]: 410 : if (result)
6083 : : {
6084 : : ListCell *lcn;
6085 : : int colno;
6086 : :
6087 [ - + ]: 114 : if (list_length(query->targetList) != list_length(result->eref->colnames))
4036 tgl@sss.pgh.pa.us 6088 :UBC 0 : return NULL; /* this probably cannot happen */
2311 tgl@sss.pgh.pa.us 6089 :CBC 114 : colno = 0;
4036 6090 [ + - + + : 421 : forboth(lc, query->targetList, lcn, result->eref->colnames)
+ - + + +
+ + - +
+ ]
6091 : : {
6092 : 313 : TargetEntry *tle = (TargetEntry *) lfirst(lc);
6093 : 313 : char *cname = strVal(lfirst(lcn));
6094 : : char *colname;
6095 : :
6096 [ - + ]: 313 : if (tle->resjunk)
6097 : 6 : return NULL; /* this probably cannot happen */
6098 : :
6099 : : /* compute name that get_target_list would use for column */
2311 6100 : 313 : colno++;
6101 [ + + + - ]: 313 : if (resultDesc && colno <= resultDesc->natts)
6102 : 15 : colname = NameStr(TupleDescAttr(resultDesc, colno - 1)->attname);
6103 : : else
6104 : 298 : colname = tle->resname;
6105 : :
6106 : : /* does it match the VALUES RTE? */
6107 [ + - + + ]: 313 : if (colname == NULL || strcmp(colname, cname) != 0)
4036 6108 : 6 : return NULL; /* column name has been changed */
6109 : : }
6110 : : }
6111 : :
4956 6112 : 404 : return result;
6113 : : }
6114 : :
6115 : : static void
563 6116 : 2465 : get_basic_select_query(Query *query, deparse_context *context)
6117 : : {
9660 6118 : 2465 : StringInfo buf = context->buf;
6119 : : RangeTblEntry *values_rte;
6120 : : char *sep;
6121 : : ListCell *l;
6122 : :
8264 6123 [ + + ]: 2465 : if (PRETTY_INDENT(context))
6124 : : {
8259 bruce@momjian.us 6125 : 2442 : context->indentLevel += PRETTYINDENT_STD;
6126 : 2442 : appendStringInfoChar(buf, ' ');
6127 : : }
6128 : :
6129 : : /*
6130 : : * If the query looks like SELECT * FROM (VALUES ...), then print just the
6131 : : * VALUES part. This reverses what transformValuesClause() did at parse
6132 : : * time.
6133 : : */
563 tgl@sss.pgh.pa.us 6134 : 2465 : values_rte = get_simple_values_rte(query, context->resultDesc);
4956 6135 [ + + ]: 2465 : if (values_rte)
6136 : : {
6137 : 108 : get_values_def(values_rte->values_lists, context);
6138 : 108 : return;
6139 : : }
6140 : :
6141 : : /*
6142 : : * Build up the query string - first we say SELECT
6143 : : */
1803 peter@eisentraut.org 6144 [ + + ]: 2357 : if (query->isReturn)
6145 : 26 : appendStringInfoString(buf, "RETURN");
6146 : : else
6147 : 2331 : appendStringInfoString(buf, "SELECT");
6148 : :
6149 : : /* Add the DISTINCT clause if given */
9292 tgl@sss.pgh.pa.us 6150 [ - + ]: 2357 : if (query->distinctClause != NIL)
6151 : : {
6434 tgl@sss.pgh.pa.us 6152 [ # # ]:UBC 0 : if (query->hasDistinctOn)
6153 : : {
4518 rhaas@postgresql.org 6154 : 0 : appendStringInfoString(buf, " DISTINCT ON (");
9292 tgl@sss.pgh.pa.us 6155 : 0 : sep = "";
6156 [ # # # # : 0 : foreach(l, query->distinctClause)
# # ]
6157 : : {
6434 6158 : 0 : SortGroupClause *srt = (SortGroupClause *) lfirst(l);
6159 : :
7624 neilc@samurai.com 6160 : 0 : appendStringInfoString(buf, sep);
3956 andres@anarazel.de 6161 : 0 : get_rule_sortgroupclause(srt->tleSortGroupRef, query->targetList,
6162 : : false, context);
9292 tgl@sss.pgh.pa.us 6163 : 0 : sep = ", ";
6164 : : }
4518 rhaas@postgresql.org 6165 : 0 : appendStringInfoChar(buf, ')');
6166 : : }
6167 : : else
6168 : 0 : appendStringInfoString(buf, " DISTINCT");
6169 : : }
6170 : :
6171 : : /* Then we tell what to select (the targetlist) */
563 tgl@sss.pgh.pa.us 6172 :CBC 2357 : get_target_list(query->targetList, context);
6173 : :
6174 : : /* Add the FROM clause if needed */
7155 6175 : 2357 : get_from_clause(query, " FROM ", context);
6176 : :
6177 : : /* Add the WHERE clause if given */
6178 [ + + ]: 2357 : if (query->jointree->quals != NULL)
6179 : : {
6180 : 747 : appendContextKeyword(context, " WHERE ",
6181 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
6182 : 747 : get_rule_expr(query->jointree->quals, context, false);
6183 : : }
6184 : :
6185 : : /* Add the GROUP BY clause if given */
3956 andres@anarazel.de 6186 [ + + - + ]: 2357 : if (query->groupClause != NULL || query->groupingSets != NULL)
6187 : : {
6188 : : bool save_ingroupby;
6189 : :
7155 tgl@sss.pgh.pa.us 6190 : 113 : appendContextKeyword(context, " GROUP BY ",
6191 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
1823 tomas.vondra@postgre 6192 [ - + ]: 113 : if (query->groupDistinct)
1823 tomas.vondra@postgre 6193 :UBC 0 : appendStringInfoString(buf, "DISTINCT ");
6194 : :
563 tgl@sss.pgh.pa.us 6195 :CBC 113 : save_ingroupby = context->inGroupBy;
6196 : 113 : context->inGroupBy = true;
6197 : :
167 tgl@sss.pgh.pa.us 6198 [ + + ]:GNC 113 : if (query->groupByAll)
6199 : 3 : appendStringInfoString(buf, "ALL");
6200 [ + + ]: 110 : else if (query->groupingSets == NIL)
6201 : : {
3956 andres@anarazel.de 6202 :CBC 107 : sep = "";
6203 [ + - + + : 243 : foreach(l, query->groupClause)
+ + ]
6204 : : {
6205 : 136 : SortGroupClause *grp = (SortGroupClause *) lfirst(l);
6206 : :
6207 : 136 : appendStringInfoString(buf, sep);
6208 : 136 : get_rule_sortgroupclause(grp->tleSortGroupRef, query->targetList,
6209 : : false, context);
6210 : 136 : sep = ", ";
6211 : : }
6212 : : }
6213 : : else
6214 : : {
6215 : 3 : sep = "";
6216 [ + - + + : 6 : foreach(l, query->groupingSets)
+ + ]
6217 : : {
6218 : 3 : GroupingSet *grp = lfirst(l);
6219 : :
6220 : 3 : appendStringInfoString(buf, sep);
6221 : 3 : get_rule_groupingset(grp, query->targetList, true, context);
6222 : 3 : sep = ", ";
6223 : : }
6224 : : }
6225 : :
563 tgl@sss.pgh.pa.us 6226 : 113 : context->inGroupBy = save_ingroupby;
6227 : : }
6228 : :
6229 : : /* Add the HAVING clause if given */
7155 6230 [ + + ]: 2357 : if (query->havingQual != NULL)
6231 : : {
6232 : 5 : appendContextKeyword(context, " HAVING ",
6233 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
6234 : 5 : get_rule_expr(query->havingQual, context, false);
6235 : : }
6236 : :
6237 : : /* Add the WINDOW clause if needed */
6286 6238 [ + + ]: 2357 : if (query->windowClause != NIL)
6239 : 24 : get_rule_windowclause(query, context);
6240 : : }
6241 : :
6242 : : /* ----------
6243 : : * get_target_list - Parse back a SELECT target list
6244 : : *
6245 : : * This is also used for RETURNING lists in INSERT/UPDATE/DELETE/MERGE.
6246 : : * ----------
6247 : : */
6248 : : static void
563 6249 : 2433 : get_target_list(List *targetList, deparse_context *context)
6250 : : {
7155 6251 : 2433 : StringInfo buf = context->buf;
6252 : : StringInfoData targetbuf;
4829 6253 : 2433 : bool last_was_multiline = false;
6254 : : char *sep;
6255 : : int colno;
6256 : : ListCell *l;
6257 : :
6258 : : /* we use targetbuf to hold each TLE's text temporarily */
6259 : 2433 : initStringInfo(&targetbuf);
6260 : :
10065 bruce@momjian.us 6261 : 2433 : sep = " ";
8620 tgl@sss.pgh.pa.us 6262 : 2433 : colno = 0;
7155 6263 [ + - + + : 12822 : foreach(l, targetList)
+ + ]
6264 : : {
9315 6265 : 10389 : TargetEntry *tle = (TargetEntry *) lfirst(l);
6266 : : char *colname;
6267 : : char *attname;
6268 : :
7648 6269 [ + + ]: 10389 : if (tle->resjunk)
9298 6270 : 20 : continue; /* ignore junk entries */
6271 : :
7624 neilc@samurai.com 6272 : 10369 : appendStringInfoString(buf, sep);
10057 bruce@momjian.us 6273 : 10369 : sep = ", ";
8620 tgl@sss.pgh.pa.us 6274 : 10369 : colno++;
6275 : :
6276 : : /*
6277 : : * Put the new field text into targetbuf so we can decide after we've
6278 : : * got it whether or not it needs to go on a new line.
6279 : : */
4829 6280 : 10369 : resetStringInfo(&targetbuf);
5138 andrew@dunslane.net 6281 : 10369 : context->buf = &targetbuf;
6282 : :
6283 : : /*
6284 : : * We special-case Var nodes rather than using get_rule_expr. This is
6285 : : * needed because get_rule_expr will display a whole-row Var as
6286 : : * "foo.*", which is the preferred notation in most contexts, but at
6287 : : * the top level of a SELECT list it's not right (the parser will
6288 : : * expand that notation into multiple columns, yielding behavior
6289 : : * different from a whole-row Var). We need to call get_variable
6290 : : * directly so that we can tell it to do the right thing, and so that
6291 : : * we can get the attribute name which is the default AS label.
6292 : : */
3956 andres@anarazel.de 6293 [ + - + + ]: 10369 : if (tle->expr && (IsA(tle->expr, Var)))
6294 : : {
5070 tgl@sss.pgh.pa.us 6295 : 7984 : attname = get_variable((Var *) tle->expr, 0, true, context);
6296 : : }
6297 : : else
6298 : : {
7353 6299 : 2385 : get_rule_expr((Node *) tle->expr, context, true);
6300 : :
6301 : : /*
6302 : : * When colNamesVisible is true, we should always show the
6303 : : * assigned column name explicitly. Otherwise, show it only if
6304 : : * it's not FigureColname's fallback.
6305 : : */
563 6306 [ + + ]: 2385 : attname = context->colNamesVisible ? NULL : "?column?";
6307 : : }
6308 : :
6309 : : /*
6310 : : * Figure out what the result column should be called. In the context
6311 : : * of a view, use the view's tuple descriptor (so as to pick up the
6312 : : * effects of any column RENAME that's been done on the view).
6313 : : * Otherwise, just use what we can find in the TLE.
6314 : : */
6315 [ + + + - ]: 10369 : if (context->resultDesc && colno <= context->resultDesc->natts)
6316 : 9457 : colname = NameStr(TupleDescAttr(context->resultDesc,
6317 : : colno - 1)->attname);
6318 : : else
7648 6319 : 912 : colname = tle->resname;
6320 : :
6321 : : /* Show AS unless the column's name is correct as-is */
8252 6322 [ + + ]: 10369 : if (colname) /* resname could be NULL */
6323 : : {
7353 6324 [ + + + + ]: 10343 : if (attname == NULL || strcmp(attname, colname) != 0)
5138 andrew@dunslane.net 6325 : 3364 : appendStringInfo(&targetbuf, " AS %s", quote_identifier(colname));
6326 : : }
6327 : :
6328 : : /* Restore context's output buffer */
6329 : 10369 : context->buf = buf;
6330 : :
6331 : : /* Consider line-wrapping if enabled */
4829 tgl@sss.pgh.pa.us 6332 [ + + + - ]: 10369 : if (PRETTY_INDENT(context) && context->wrapColumn >= 0)
6333 : : {
6334 : : int leading_nl_pos;
6335 : :
6336 : : /* Does the new field start with a new line? */
4507 6337 [ + - + + ]: 10346 : if (targetbuf.len > 0 && targetbuf.data[0] == '\n')
6338 : 279 : leading_nl_pos = 0;
6339 : : else
6340 : 10067 : leading_nl_pos = -1;
6341 : :
6342 : : /* If so, we shouldn't add anything */
6343 [ + + ]: 10346 : if (leading_nl_pos >= 0)
6344 : : {
6345 : : /* instead, remove any trailing spaces currently in buf */
6346 : 279 : removeStringInfoSpaces(buf);
6347 : : }
6348 : : else
6349 : : {
6350 : : char *trailing_nl;
6351 : :
6352 : : /* Locate the start of the current line in the output buffer */
6353 : 10067 : trailing_nl = strrchr(buf->data, '\n');
6354 [ + + ]: 10067 : if (trailing_nl == NULL)
6355 : 2992 : trailing_nl = buf->data;
6356 : : else
6357 : 7075 : trailing_nl++;
6358 : :
6359 : : /*
6360 : : * Add a newline, plus some indentation, if the new field is
6361 : : * not the first and either the new field would cause an
6362 : : * overflow or the last field used more than one line.
6363 : : */
6364 [ + + ]: 10067 : if (colno > 1 &&
6365 [ - + - - ]: 7665 : ((strlen(trailing_nl) + targetbuf.len > context->wrapColumn) ||
6366 : : last_was_multiline))
6367 : 7665 : appendContextKeyword(context, "", -PRETTYINDENT_STD,
6368 : : PRETTYINDENT_STD, PRETTYINDENT_VAR);
6369 : : }
6370 : :
6371 : : /* Remember this field's multiline status for next iteration */
4829 6372 : 10346 : last_was_multiline =
6373 : 10346 : (strchr(targetbuf.data + leading_nl_pos + 1, '\n') != NULL);
6374 : : }
6375 : :
6376 : : /* Add the new field */
2427 drowley@postgresql.o 6377 : 10369 : appendBinaryStringInfo(buf, targetbuf.data, targetbuf.len);
6378 : : }
6379 : :
6380 : : /* clean up */
4829 tgl@sss.pgh.pa.us 6381 : 2433 : pfree(targetbuf.data);
9292 6382 : 2433 : }
6383 : :
6384 : : static void
423 dean.a.rasheed@gmail 6385 : 76 : get_returning_clause(Query *query, deparse_context *context)
6386 : : {
6387 : 76 : StringInfo buf = context->buf;
6388 : :
6389 [ + - ]: 76 : if (query->returningList)
6390 : : {
6391 : 76 : bool have_with = false;
6392 : :
6393 : 76 : appendContextKeyword(context, " RETURNING",
6394 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
6395 : :
6396 : : /* Add WITH (OLD/NEW) options, if they're not the defaults */
6397 [ + + + + ]: 76 : if (query->returningOldAlias && strcmp(query->returningOldAlias, "old") != 0)
6398 : : {
6399 : 9 : appendStringInfo(buf, " WITH (OLD AS %s",
6400 : 9 : quote_identifier(query->returningOldAlias));
6401 : 9 : have_with = true;
6402 : : }
6403 [ + + + + ]: 76 : if (query->returningNewAlias && strcmp(query->returningNewAlias, "new") != 0)
6404 : : {
6405 [ + + ]: 9 : if (have_with)
6406 : 6 : appendStringInfo(buf, ", NEW AS %s",
6407 : 6 : quote_identifier(query->returningNewAlias));
6408 : : else
6409 : : {
6410 : 3 : appendStringInfo(buf, " WITH (NEW AS %s",
6411 : 3 : quote_identifier(query->returningNewAlias));
6412 : 3 : have_with = true;
6413 : : }
6414 : : }
6415 [ + + ]: 76 : if (have_with)
6416 : 12 : appendStringInfoChar(buf, ')');
6417 : :
6418 : : /* Add the returning expressions themselves */
6419 : 76 : get_target_list(query->returningList, context);
6420 : : }
6421 : 76 : }
6422 : :
6423 : : static void
563 tgl@sss.pgh.pa.us 6424 : 378 : get_setop_query(Node *setOp, Query *query, deparse_context *context)
6425 : : {
9292 6426 : 378 : StringInfo buf = context->buf;
6427 : : bool need_paren;
6428 : :
6429 : : /* Guard against excessively long or deeply-nested queries */
4337 6430 [ - + ]: 378 : CHECK_FOR_INTERRUPTS();
6431 : 378 : check_stack_depth();
6432 : :
9292 6433 [ + + ]: 378 : if (IsA(setOp, RangeTblRef))
6434 : : {
6435 : 230 : RangeTblRef *rtr = (RangeTblRef *) setOp;
6436 : 230 : RangeTblEntry *rte = rt_fetch(rtr->rtindex, query->rtable);
9124 bruce@momjian.us 6437 : 230 : Query *subquery = rte->subquery;
6438 : :
9292 tgl@sss.pgh.pa.us 6439 [ - + ]: 230 : Assert(subquery != NULL);
6440 : :
6441 : : /*
6442 : : * We need parens if WITH, ORDER BY, FOR UPDATE, or LIMIT; see gram.y.
6443 : : * Also add parens if the leaf query contains its own set operations.
6444 : : * (That shouldn't happen unless one of the other clauses is also
6445 : : * present, see transformSetOperationTree; but let's be safe.)
6446 : : */
6371 6447 : 690 : need_paren = (subquery->cteList ||
6448 [ + - ]: 230 : subquery->sortClause ||
7922 6449 [ + - ]: 230 : subquery->rowMarks ||
6450 [ + - ]: 230 : subquery->limitOffset ||
480 6451 [ + - + - ]: 690 : subquery->limitCount ||
6452 [ - + ]: 230 : subquery->setOperations);
7922 6453 [ - + ]: 230 : if (need_paren)
7922 tgl@sss.pgh.pa.us 6454 :UBC 0 : appendStringInfoChar(buf, '(');
563 tgl@sss.pgh.pa.us 6455 :CBC 230 : get_query_def(subquery, buf, context->namespaces,
6456 : 230 : context->resultDesc, context->colNamesVisible,
6457 : : context->prettyFlags, context->wrapColumn,
6458 : : context->indentLevel);
7922 6459 [ - + ]: 230 : if (need_paren)
7922 tgl@sss.pgh.pa.us 6460 :UBC 0 : appendStringInfoChar(buf, ')');
6461 : : }
9292 tgl@sss.pgh.pa.us 6462 [ + - ]:CBC 148 : else if (IsA(setOp, SetOperationStmt))
6463 : : {
6464 : 148 : SetOperationStmt *op = (SetOperationStmt *) setOp;
6465 : : int subindent;
6466 : : bool save_colnamesvisible;
6467 : :
6468 : : /*
6469 : : * We force parens when nesting two SetOperationStmts, except when the
6470 : : * lefthand input is another setop of the same kind. Syntactically,
6471 : : * we could omit parens in rather more cases, but it seems best to use
6472 : : * parens to flag cases where the setop operator changes. If we use
6473 : : * parens, we also increase the indentation level for the child query.
6474 : : *
6475 : : * There are some cases in which parens are needed around a leaf query
6476 : : * too, but those are more easily handled at the next level down (see
6477 : : * code above).
6478 : : */
4337 6479 [ + + ]: 148 : if (IsA(op->larg, SetOperationStmt))
6480 : : {
6481 : 66 : SetOperationStmt *lop = (SetOperationStmt *) op->larg;
6482 : :
6483 [ + - + - ]: 66 : if (op->op == lop->op && op->all == lop->all)
6484 : 66 : need_paren = false;
6485 : : else
4337 tgl@sss.pgh.pa.us 6486 :UBC 0 : need_paren = true;
6487 : : }
6488 : : else
4337 tgl@sss.pgh.pa.us 6489 :CBC 82 : need_paren = false;
6490 : :
7922 6491 [ - + ]: 148 : if (need_paren)
6492 : : {
7922 tgl@sss.pgh.pa.us 6493 :UBC 0 : appendStringInfoChar(buf, '(');
4337 6494 : 0 : subindent = PRETTYINDENT_STD;
6495 : 0 : appendContextKeyword(context, "", subindent, 0, 0);
6496 : : }
6497 : : else
4337 tgl@sss.pgh.pa.us 6498 :CBC 148 : subindent = 0;
6499 : :
563 6500 : 148 : get_setop_query(op->larg, query, context);
6501 : :
4337 6502 [ - + ]: 148 : if (need_paren)
4337 tgl@sss.pgh.pa.us 6503 :UBC 0 : appendContextKeyword(context, ") ", -subindent, 0, 0);
4337 tgl@sss.pgh.pa.us 6504 [ + - ]:CBC 148 : else if (PRETTY_INDENT(context))
6505 : 148 : appendContextKeyword(context, "", -subindent, 0, 0);
6506 : : else
8259 bruce@momjian.us 6507 :UBC 0 : appendStringInfoChar(buf, ' ');
6508 : :
9292 tgl@sss.pgh.pa.us 6509 [ + - - - ]:CBC 148 : switch (op->op)
6510 : : {
6511 : 148 : case SETOP_UNION:
4337 6512 : 148 : appendStringInfoString(buf, "UNION ");
9292 6513 : 148 : break;
9292 tgl@sss.pgh.pa.us 6514 :UBC 0 : case SETOP_INTERSECT:
4337 6515 : 0 : appendStringInfoString(buf, "INTERSECT ");
9292 6516 : 0 : break;
6517 : 0 : case SETOP_EXCEPT:
4337 6518 : 0 : appendStringInfoString(buf, "EXCEPT ");
9292 6519 : 0 : break;
6520 : 0 : default:
8267 6521 [ # # ]: 0 : elog(ERROR, "unrecognized set op: %d",
6522 : : (int) op->op);
6523 : : }
9292 tgl@sss.pgh.pa.us 6524 [ + + ]:CBC 148 : if (op->all)
4518 rhaas@postgresql.org 6525 : 142 : appendStringInfoString(buf, "ALL ");
6526 : :
6527 : : /* Always parenthesize if RHS is another setop */
4337 tgl@sss.pgh.pa.us 6528 : 148 : need_paren = IsA(op->rarg, SetOperationStmt);
6529 : :
6530 : : /*
6531 : : * The indentation code here is deliberately a bit different from that
6532 : : * for the lefthand input, because we want the line breaks in
6533 : : * different places.
6534 : : */
7922 6535 [ - + ]: 148 : if (need_paren)
6536 : : {
7922 tgl@sss.pgh.pa.us 6537 :UBC 0 : appendStringInfoChar(buf, '(');
4337 6538 : 0 : subindent = PRETTYINDENT_STD;
6539 : : }
6540 : : else
4337 tgl@sss.pgh.pa.us 6541 :CBC 148 : subindent = 0;
6542 : 148 : appendContextKeyword(context, "", subindent, 0, 0);
6543 : :
6544 : : /*
6545 : : * The output column names of the RHS sub-select don't matter.
6546 : : */
563 6547 : 148 : save_colnamesvisible = context->colNamesVisible;
6548 : 148 : context->colNamesVisible = false;
6549 : :
6550 : 148 : get_setop_query(op->rarg, query, context);
6551 : :
6552 : 148 : context->colNamesVisible = save_colnamesvisible;
6553 : :
6371 6554 [ + - ]: 148 : if (PRETTY_INDENT(context))
4337 6555 : 148 : context->indentLevel -= subindent;
6556 [ - + ]: 148 : if (need_paren)
4337 tgl@sss.pgh.pa.us 6557 :UBC 0 : appendContextKeyword(context, ")", 0, 0, 0);
6558 : : }
6559 : : else
6560 : : {
8267 6561 [ # # ]: 0 : elog(ERROR, "unrecognized node type: %d",
6562 : : (int) nodeTag(setOp));
6563 : : }
9292 tgl@sss.pgh.pa.us 6564 :CBC 378 : }
6565 : :
6566 : : /*
6567 : : * Display a sort/group clause.
6568 : : *
6569 : : * Also returns the expression tree, so caller need not find it again.
6570 : : */
6571 : : static Node *
3956 andres@anarazel.de 6572 : 339 : get_rule_sortgroupclause(Index ref, List *tlist, bool force_colno,
6573 : : deparse_context *context)
6574 : : {
9100 tgl@sss.pgh.pa.us 6575 : 339 : StringInfo buf = context->buf;
6576 : : TargetEntry *tle;
6577 : : Node *expr;
6578 : :
3956 andres@anarazel.de 6579 : 339 : tle = get_sortgroupref_tle(ref, tlist);
8494 tgl@sss.pgh.pa.us 6580 : 339 : expr = (Node *) tle->expr;
6581 : :
6582 : : /*
6583 : : * Use column-number form if requested by caller. Otherwise, if
6584 : : * expression is a constant, force it to be dumped with an explicit cast
6585 : : * as decoration --- this is because a simple integer constant is
6586 : : * ambiguous (and will be misinterpreted by findTargetlistEntrySQL92()) if
6587 : : * we dump it without any decoration. Similarly, if it's just a Var,
6588 : : * there is risk of misinterpretation if the column name is reassigned in
6589 : : * the SELECT list, so we may need to force table qualification. And, if
6590 : : * it's anything more complex than a simple Var, then force extra parens
6591 : : * around it, to ensure it can't be misinterpreted as a cube() or rollup()
6592 : : * construct.
6593 : : */
6643 6594 [ + + ]: 339 : if (force_colno)
6595 : : {
7648 6596 [ - + ]: 6 : Assert(!tle->resjunk);
6597 : 6 : appendStringInfo(buf, "%d", tle->resno);
6598 : : }
563 6599 [ + - ]: 333 : else if (!expr)
6600 : : /* do nothing, probably can't happen */ ;
6601 [ - + ]: 333 : else if (IsA(expr, Const))
6643 tgl@sss.pgh.pa.us 6602 :UBC 0 : get_const_expr((Const *) expr, context, 1);
563 tgl@sss.pgh.pa.us 6603 [ + + ]:CBC 333 : else if (IsA(expr, Var))
6604 : : {
6605 : : /* Tell get_variable to check for name conflict */
6606 : 319 : bool save_varinorderby = context->varInOrderBy;
6607 : :
6608 : 319 : context->varInOrderBy = true;
6609 : 319 : (void) get_variable((Var *) expr, 0, false, context);
6610 : 319 : context->varInOrderBy = save_varinorderby;
6611 : : }
6612 : : else
6613 : : {
6614 : : /*
6615 : : * We must force parens for function-like expressions even if
6616 : : * PRETTY_PAREN is off, since those are the ones in danger of
6617 : : * misparsing. For other expressions we need to force them only if
6618 : : * PRETTY_PAREN is on, since otherwise the expression will output them
6619 : : * itself. (We can't skip the parens.)
6620 : : */
3949 bruce@momjian.us 6621 : 28 : bool need_paren = (PRETTY_PAREN(context)
6622 [ + + ]: 14 : || IsA(expr, FuncExpr)
2129 tgl@sss.pgh.pa.us 6623 [ + - ]: 12 : || IsA(expr, Aggref)
1082 alvherre@alvh.no-ip. 6624 [ + - ]: 12 : || IsA(expr, WindowFunc)
6625 [ + - - + ]: 28 : || IsA(expr, JsonConstructorExpr));
6626 : :
3956 andres@anarazel.de 6627 [ + + ]: 14 : if (need_paren)
3134 peter_e@gmx.net 6628 : 2 : appendStringInfoChar(context->buf, '(');
8578 tgl@sss.pgh.pa.us 6629 : 14 : get_rule_expr(expr, context, true);
3956 andres@anarazel.de 6630 [ + + ]: 14 : if (need_paren)
3134 peter_e@gmx.net 6631 : 2 : appendStringInfoChar(context->buf, ')');
6632 : : }
6633 : :
8717 tgl@sss.pgh.pa.us 6634 : 339 : return expr;
6635 : : }
6636 : :
6637 : : /*
6638 : : * Display a GroupingSet
6639 : : */
6640 : : static void
3956 andres@anarazel.de 6641 : 9 : get_rule_groupingset(GroupingSet *gset, List *targetlist,
6642 : : bool omit_parens, deparse_context *context)
6643 : : {
6644 : : ListCell *l;
6645 : 9 : StringInfo buf = context->buf;
6646 : 9 : bool omit_child_parens = true;
6647 : 9 : char *sep = "";
6648 : :
6649 [ - + + - : 9 : switch (gset->kind)
- - ]
6650 : : {
3956 andres@anarazel.de 6651 :UBC 0 : case GROUPING_SET_EMPTY:
6652 : 0 : appendStringInfoString(buf, "()");
6653 : 0 : return;
6654 : :
3956 andres@anarazel.de 6655 :CBC 6 : case GROUPING_SET_SIMPLE:
6656 : : {
6657 [ + - + - ]: 6 : if (!omit_parens || list_length(gset->content) != 1)
3134 peter_e@gmx.net 6658 : 6 : appendStringInfoChar(buf, '(');
6659 : :
3956 andres@anarazel.de 6660 [ + - + + : 21 : foreach(l, gset->content)
+ + ]
6661 : : {
3949 bruce@momjian.us 6662 : 15 : Index ref = lfirst_int(l);
6663 : :
3956 andres@anarazel.de 6664 : 15 : appendStringInfoString(buf, sep);
6665 : 15 : get_rule_sortgroupclause(ref, targetlist,
6666 : : false, context);
6667 : 15 : sep = ", ";
6668 : : }
6669 : :
6670 [ + - + - ]: 6 : if (!omit_parens || list_length(gset->content) != 1)
3134 peter_e@gmx.net 6671 : 6 : appendStringInfoChar(buf, ')');
6672 : : }
3956 andres@anarazel.de 6673 : 6 : return;
6674 : :
6675 : 3 : case GROUPING_SET_ROLLUP:
6676 : 3 : appendStringInfoString(buf, "ROLLUP(");
6677 : 3 : break;
3956 andres@anarazel.de 6678 :UBC 0 : case GROUPING_SET_CUBE:
6679 : 0 : appendStringInfoString(buf, "CUBE(");
6680 : 0 : break;
6681 : 0 : case GROUPING_SET_SETS:
6682 : 0 : appendStringInfoString(buf, "GROUPING SETS (");
6683 : 0 : omit_child_parens = false;
6684 : 0 : break;
6685 : : }
6686 : :
3956 andres@anarazel.de 6687 [ + - + + :CBC 9 : foreach(l, gset->content)
+ + ]
6688 : : {
6689 : 6 : appendStringInfoString(buf, sep);
6690 : 6 : get_rule_groupingset(lfirst(l), targetlist, omit_child_parens, context);
6691 : 6 : sep = ", ";
6692 : : }
6693 : :
3134 peter_e@gmx.net 6694 : 3 : appendStringInfoChar(buf, ')');
6695 : : }
6696 : :
6697 : : /*
6698 : : * Display an ORDER BY list.
6699 : : */
6700 : : static void
6286 tgl@sss.pgh.pa.us 6701 : 172 : get_rule_orderby(List *orderList, List *targetList,
6702 : : bool force_colno, deparse_context *context)
6703 : : {
6704 : 172 : StringInfo buf = context->buf;
6705 : : const char *sep;
6706 : : ListCell *l;
6707 : :
6708 : 172 : sep = "";
6709 [ + - + + : 360 : foreach(l, orderList)
+ + ]
6710 : : {
6711 : 188 : SortGroupClause *srt = (SortGroupClause *) lfirst(l);
6712 : : Node *sortexpr;
6713 : : Oid sortcoltype;
6714 : : TypeCacheEntry *typentry;
6715 : :
6716 : 188 : appendStringInfoString(buf, sep);
3956 andres@anarazel.de 6717 : 188 : sortexpr = get_rule_sortgroupclause(srt->tleSortGroupRef, targetList,
6718 : : force_colno, context);
6286 tgl@sss.pgh.pa.us 6719 : 188 : sortcoltype = exprType(sortexpr);
6720 : : /* See whether operator is default < or > for datatype */
6721 : 188 : typentry = lookup_type_cache(sortcoltype,
6722 : : TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
6723 [ + + ]: 188 : if (srt->sortop == typentry->lt_opr)
6724 : : {
6725 : : /* ASC is default, so emit nothing for it */
6726 [ - + ]: 174 : if (srt->nulls_first)
4518 rhaas@postgresql.org 6727 :UBC 0 : appendStringInfoString(buf, " NULLS FIRST");
6728 : : }
6286 tgl@sss.pgh.pa.us 6729 [ + + ]:CBC 14 : else if (srt->sortop == typentry->gt_opr)
6730 : : {
4518 rhaas@postgresql.org 6731 : 5 : appendStringInfoString(buf, " DESC");
6732 : : /* DESC defaults to NULLS FIRST */
6286 tgl@sss.pgh.pa.us 6733 [ + + ]: 5 : if (!srt->nulls_first)
4518 rhaas@postgresql.org 6734 : 1 : appendStringInfoString(buf, " NULLS LAST");
6735 : : }
6736 : : else
6737 : : {
6286 tgl@sss.pgh.pa.us 6738 : 9 : appendStringInfo(buf, " USING %s",
6739 : : generate_operator_name(srt->sortop,
6740 : : sortcoltype,
6741 : : sortcoltype));
6742 : : /* be specific to eliminate ambiguity */
6743 [ - + ]: 9 : if (srt->nulls_first)
4518 rhaas@postgresql.org 6744 :UBC 0 : appendStringInfoString(buf, " NULLS FIRST");
6745 : : else
4518 rhaas@postgresql.org 6746 :CBC 9 : appendStringInfoString(buf, " NULLS LAST");
6747 : : }
6286 tgl@sss.pgh.pa.us 6748 : 188 : sep = ", ";
6749 : : }
6750 : 172 : }
6751 : :
6752 : : /*
6753 : : * Display a WINDOW clause.
6754 : : *
6755 : : * Note that the windowClause list might contain only anonymous window
6756 : : * specifications, in which case we should print nothing here.
6757 : : */
6758 : : static void
6759 : 24 : get_rule_windowclause(Query *query, deparse_context *context)
6760 : : {
6761 : 24 : StringInfo buf = context->buf;
6762 : : const char *sep;
6763 : : ListCell *l;
6764 : :
6765 : 24 : sep = NULL;
6766 [ + - + + : 48 : foreach(l, query->windowClause)
+ + ]
6767 : : {
6768 : 24 : WindowClause *wc = (WindowClause *) lfirst(l);
6769 : :
6770 [ + + ]: 24 : if (wc->name == NULL)
6771 : 21 : continue; /* ignore anonymous windows */
6772 : :
6286 tgl@sss.pgh.pa.us 6773 [ + - ]:GBC 3 : if (sep == NULL)
6774 : 3 : appendContextKeyword(context, " WINDOW ",
6775 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
6776 : : else
6286 tgl@sss.pgh.pa.us 6777 :UBC 0 : appendStringInfoString(buf, sep);
6778 : :
6286 tgl@sss.pgh.pa.us 6779 :GBC 3 : appendStringInfo(buf, "%s AS ", quote_identifier(wc->name));
6780 : :
6781 : 3 : get_rule_windowspec(wc, query->targetList, context);
6782 : :
6783 : 3 : sep = ", ";
6784 : : }
6286 tgl@sss.pgh.pa.us 6785 :CBC 24 : }
6786 : :
6787 : : /*
6788 : : * Display a window definition
6789 : : */
6790 : : static void
6791 : 24 : get_rule_windowspec(WindowClause *wc, List *targetList,
6792 : : deparse_context *context)
6793 : : {
6794 : 24 : StringInfo buf = context->buf;
6795 : 24 : bool needspace = false;
6796 : : const char *sep;
6797 : : ListCell *l;
6798 : :
6799 : 24 : appendStringInfoChar(buf, '(');
6800 [ - + ]: 24 : if (wc->refname)
6801 : : {
6286 tgl@sss.pgh.pa.us 6802 :UBC 0 : appendStringInfoString(buf, quote_identifier(wc->refname));
6803 : 0 : needspace = true;
6804 : : }
6805 : : /* partition clauses are always inherited, so only print if no refname */
6286 tgl@sss.pgh.pa.us 6806 [ - + - - ]:CBC 24 : if (wc->partitionClause && !wc->refname)
6807 : : {
6286 tgl@sss.pgh.pa.us 6808 [ # # ]:UBC 0 : if (needspace)
6809 : 0 : appendStringInfoChar(buf, ' ');
6810 : 0 : appendStringInfoString(buf, "PARTITION BY ");
6811 : 0 : sep = "";
6812 [ # # # # : 0 : foreach(l, wc->partitionClause)
# # ]
6813 : : {
6814 : 0 : SortGroupClause *grp = (SortGroupClause *) lfirst(l);
6815 : :
6816 : 0 : appendStringInfoString(buf, sep);
3956 andres@anarazel.de 6817 : 0 : get_rule_sortgroupclause(grp->tleSortGroupRef, targetList,
6818 : : false, context);
6286 tgl@sss.pgh.pa.us 6819 : 0 : sep = ", ";
6820 : : }
6821 : 0 : needspace = true;
6822 : : }
6823 : : /* print ordering clause only if not inherited */
6286 tgl@sss.pgh.pa.us 6824 [ + - + - ]:CBC 24 : if (wc->orderClause && !wc->copiedOrder)
6825 : : {
6826 [ - + ]: 24 : if (needspace)
6286 tgl@sss.pgh.pa.us 6827 :UBC 0 : appendStringInfoChar(buf, ' ');
6286 tgl@sss.pgh.pa.us 6828 :CBC 24 : appendStringInfoString(buf, "ORDER BY ");
6829 : 24 : get_rule_orderby(wc->orderClause, targetList, false, context);
6830 : 24 : needspace = true;
6831 : : }
6832 : : /* framing clause is never inherited, so print unless it's default */
6283 6833 [ + + ]: 24 : if (wc->frameOptions & FRAMEOPTION_NONDEFAULT)
6834 : : {
6835 [ + - ]: 21 : if (needspace)
6836 : 21 : appendStringInfoChar(buf, ' ');
369 6837 : 21 : get_window_frame_options(wc->frameOptions,
6838 : : wc->startOffset, wc->endOffset,
6839 : : context);
6840 : : }
6841 : 24 : appendStringInfoChar(buf, ')');
6842 : 24 : }
6843 : :
6844 : : /*
6845 : : * Append the description of a window's framing options to context->buf
6846 : : */
6847 : : static void
6848 : 119 : get_window_frame_options(int frameOptions,
6849 : : Node *startOffset, Node *endOffset,
6850 : : deparse_context *context)
6851 : : {
6852 : 119 : StringInfo buf = context->buf;
6853 : :
6854 [ + - ]: 119 : if (frameOptions & FRAMEOPTION_NONDEFAULT)
6855 : : {
6856 [ + + ]: 119 : if (frameOptions & FRAMEOPTION_RANGE)
6283 6857 : 10 : appendStringInfoString(buf, "RANGE ");
369 6858 [ + + ]: 109 : else if (frameOptions & FRAMEOPTION_ROWS)
6283 6859 : 103 : appendStringInfoString(buf, "ROWS ");
369 6860 [ + - ]: 6 : else if (frameOptions & FRAMEOPTION_GROUPS)
2958 6861 : 6 : appendStringInfoString(buf, "GROUPS ");
6862 : : else
6283 tgl@sss.pgh.pa.us 6863 :UBC 0 : Assert(false);
369 tgl@sss.pgh.pa.us 6864 [ + + ]:CBC 119 : if (frameOptions & FRAMEOPTION_BETWEEN)
6283 6865 : 46 : appendStringInfoString(buf, "BETWEEN ");
369 6866 [ + + ]: 119 : if (frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
6283 6867 : 76 : appendStringInfoString(buf, "UNBOUNDED PRECEDING ");
369 6868 [ + + ]: 43 : else if (frameOptions & FRAMEOPTION_START_CURRENT_ROW)
6283 6869 : 13 : appendStringInfoString(buf, "CURRENT ROW ");
369 6870 [ + - ]: 30 : else if (frameOptions & FRAMEOPTION_START_OFFSET)
6871 : : {
6872 : 30 : get_rule_expr(startOffset, context, false);
6873 [ + - ]: 30 : if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
5875 6874 : 30 : appendStringInfoString(buf, " PRECEDING ");
369 tgl@sss.pgh.pa.us 6875 [ # # ]:UBC 0 : else if (frameOptions & FRAMEOPTION_START_OFFSET_FOLLOWING)
5875 6876 : 0 : appendStringInfoString(buf, " FOLLOWING ");
6877 : : else
6878 : 0 : Assert(false);
6879 : : }
6880 : : else
6283 6881 : 0 : Assert(false);
369 tgl@sss.pgh.pa.us 6882 [ + + ]:CBC 119 : if (frameOptions & FRAMEOPTION_BETWEEN)
6883 : : {
6283 6884 : 46 : appendStringInfoString(buf, "AND ");
369 6885 [ + + ]: 46 : if (frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
6283 6886 : 10 : appendStringInfoString(buf, "UNBOUNDED FOLLOWING ");
369 6887 [ + + ]: 36 : else if (frameOptions & FRAMEOPTION_END_CURRENT_ROW)
6283 6888 : 3 : appendStringInfoString(buf, "CURRENT ROW ");
369 6889 [ + - ]: 33 : else if (frameOptions & FRAMEOPTION_END_OFFSET)
6890 : : {
6891 : 33 : get_rule_expr(endOffset, context, false);
6892 [ - + ]: 33 : if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
5875 tgl@sss.pgh.pa.us 6893 :UBC 0 : appendStringInfoString(buf, " PRECEDING ");
369 tgl@sss.pgh.pa.us 6894 [ + - ]:CBC 33 : else if (frameOptions & FRAMEOPTION_END_OFFSET_FOLLOWING)
5875 6895 : 33 : appendStringInfoString(buf, " FOLLOWING ");
6896 : : else
5875 tgl@sss.pgh.pa.us 6897 :UBC 0 : Assert(false);
6898 : : }
6899 : : else
6283 6900 : 0 : Assert(false);
6901 : : }
369 tgl@sss.pgh.pa.us 6902 [ + + ]:CBC 119 : if (frameOptions & FRAMEOPTION_EXCLUDE_CURRENT_ROW)
2958 6903 : 3 : appendStringInfoString(buf, "EXCLUDE CURRENT ROW ");
369 6904 [ + + ]: 116 : else if (frameOptions & FRAMEOPTION_EXCLUDE_GROUP)
2958 6905 : 3 : appendStringInfoString(buf, "EXCLUDE GROUP ");
369 6906 [ + + ]: 113 : else if (frameOptions & FRAMEOPTION_EXCLUDE_TIES)
2958 6907 : 3 : appendStringInfoString(buf, "EXCLUDE TIES ");
6908 : : /* we will now have a trailing space; remove it */
369 6909 : 119 : buf->data[--(buf->len)] = '\0';
6910 : : }
6911 : 119 : }
6912 : :
6913 : : /*
6914 : : * Return the description of a window's framing options as a palloc'd string
6915 : : */
6916 : : char *
6917 : 98 : get_window_frame_options_for_explain(int frameOptions,
6918 : : Node *startOffset, Node *endOffset,
6919 : : List *dpcontext, bool forceprefix)
6920 : : {
6921 : : StringInfoData buf;
6922 : : deparse_context context;
6923 : :
6924 : 98 : initStringInfo(&buf);
6925 : 98 : context.buf = &buf;
6926 : 98 : context.namespaces = dpcontext;
6927 : 98 : context.resultDesc = NULL;
6928 : 98 : context.targetList = NIL;
6929 : 98 : context.windowClause = NIL;
6930 : 98 : context.varprefix = forceprefix;
6931 : 98 : context.prettyFlags = 0;
6932 : 98 : context.wrapColumn = WRAP_COLUMN_DEFAULT;
6933 : 98 : context.indentLevel = 0;
6934 : 98 : context.colNamesVisible = true;
6935 : 98 : context.inGroupBy = false;
6936 : 98 : context.varInOrderBy = false;
6937 : 98 : context.appendparents = NULL;
6938 : :
6939 : 98 : get_window_frame_options(frameOptions, startOffset, endOffset, &context);
6940 : :
6941 : 98 : return buf.data;
6942 : : }
6943 : :
6944 : : /* ----------
6945 : : * get_insert_query_def - Parse back an INSERT parsetree
6946 : : * ----------
6947 : : */
6948 : : static void
563 6949 : 173 : get_insert_query_def(Query *query, deparse_context *context)
6950 : : {
9660 6951 : 173 : StringInfo buf = context->buf;
9292 6952 : 173 : RangeTblEntry *select_rte = NULL;
7165 mail@joeconway.com 6953 : 173 : RangeTblEntry *values_rte = NULL;
6954 : : RangeTblEntry *rte;
6955 : : char *sep;
6956 : : ListCell *l;
6957 : : List *strippedexprs;
6958 : :
6959 : : /* Insert the WITH clause if given */
5630 tgl@sss.pgh.pa.us 6960 : 173 : get_with_clause(query, context);
6961 : :
6962 : : /*
6963 : : * If it's an INSERT ... SELECT or multi-row VALUES, there will be a
6964 : : * single RTE for the SELECT or VALUES. Plain VALUES has neither.
6965 : : */
10057 bruce@momjian.us 6966 [ + - + + : 676 : foreach(l, query->rtable)
+ + ]
6967 : : {
6968 : 503 : rte = (RangeTblEntry *) lfirst(l);
6969 : :
7165 mail@joeconway.com 6970 [ + + ]: 503 : if (rte->rtekind == RTE_SUBQUERY)
6971 : : {
6972 [ - + ]: 25 : if (select_rte)
7165 mail@joeconway.com 6973 [ # # ]:UBC 0 : elog(ERROR, "too many subquery RTEs in INSERT");
7165 mail@joeconway.com 6974 :CBC 25 : select_rte = rte;
6975 : : }
6976 : :
6977 [ + + ]: 503 : if (rte->rtekind == RTE_VALUES)
6978 : : {
6979 [ - + ]: 22 : if (values_rte)
7165 mail@joeconway.com 6980 [ # # ]:UBC 0 : elog(ERROR, "too many values RTEs in INSERT");
7165 mail@joeconway.com 6981 :CBC 22 : values_rte = rte;
6982 : : }
6983 : : }
6984 [ + + - + ]: 173 : if (select_rte && values_rte)
7165 mail@joeconway.com 6985 [ # # ]:UBC 0 : elog(ERROR, "both subquery and values RTEs in INSERT");
6986 : :
6987 : : /*
6988 : : * Start the query with INSERT INTO relname
6989 : : */
9632 tgl@sss.pgh.pa.us 6990 :CBC 173 : rte = rt_fetch(query->resultRelation, query->rtable);
8717 6991 [ - + ]: 173 : Assert(rte->rtekind == RTE_RELATION);
6992 : :
8264 6993 [ + - ]: 173 : if (PRETTY_INDENT(context))
6994 : : {
8259 bruce@momjian.us 6995 : 173 : context->indentLevel += PRETTYINDENT_STD;
6996 : 173 : appendStringInfoChar(buf, ' ');
6997 : : }
1122 tgl@sss.pgh.pa.us 6998 : 173 : appendStringInfo(buf, "INSERT INTO %s",
6999 : : generate_relation_name(rte->relid, NIL));
7000 : :
7001 : : /* Print the relation alias, if needed; INSERT requires explicit AS */
7002 : 173 : get_rte_alias(rte, query->resultRelation, true, context);
7003 : :
7004 : : /* always want a space here */
7005 : 173 : appendStringInfoChar(buf, ' ');
7006 : :
7007 : : /*
7008 : : * Add the insert-column-names list. Any indirection decoration needed on
7009 : : * the column names can be inferred from the top targetlist.
7010 : : */
7949 7011 : 173 : strippedexprs = NIL;
7012 : 173 : sep = "";
4895 7013 [ + - ]: 173 : if (query->targetList)
7014 : 173 : appendStringInfoChar(buf, '(');
10057 bruce@momjian.us 7015 [ + - + + : 630 : foreach(l, query->targetList)
+ + ]
7016 : : {
9315 tgl@sss.pgh.pa.us 7017 : 457 : TargetEntry *tle = (TargetEntry *) lfirst(l);
7018 : :
7648 7019 [ - + ]: 457 : if (tle->resjunk)
9298 tgl@sss.pgh.pa.us 7020 :UBC 0 : continue; /* ignore junk entries */
7021 : :
7624 neilc@samurai.com 7022 :CBC 457 : appendStringInfoString(buf, sep);
10057 bruce@momjian.us 7023 : 457 : sep = ", ";
7024 : :
7025 : : /*
7026 : : * Put out name of target column; look in the catalogs, not at
7027 : : * tle->resname, since resname will fail to track RENAME.
7028 : : */
8079 neilc@samurai.com 7029 : 457 : appendStringInfoString(buf,
2953 alvherre@alvh.no-ip. 7030 : 457 : quote_identifier(get_attname(rte->relid,
7031 : 457 : tle->resno,
7032 : : false)));
7033 : :
7034 : : /*
7035 : : * Print any indirection needed (subfields or subscripts), and strip
7036 : : * off the top-level nodes representing the indirection assignments.
7037 : : * Add the stripped expressions to strippedexprs. (If it's a
7038 : : * single-VALUES statement, the stripped expressions are the VALUES to
7039 : : * print below. Otherwise they're just Vars and not really
7040 : : * interesting.)
7041 : : */
3511 tgl@sss.pgh.pa.us 7042 : 457 : strippedexprs = lappend(strippedexprs,
7043 : 457 : processIndirection((Node *) tle->expr,
7044 : : context));
7045 : : }
4895 7046 [ + - ]: 173 : if (query->targetList)
4518 rhaas@postgresql.org 7047 : 173 : appendStringInfoString(buf, ") ");
7048 : :
3265 peter_e@gmx.net 7049 [ - + ]: 173 : if (query->override)
7050 : : {
3265 peter_e@gmx.net 7051 [ # # ]:UBC 0 : if (query->override == OVERRIDING_SYSTEM_VALUE)
7052 : 0 : appendStringInfoString(buf, "OVERRIDING SYSTEM VALUE ");
7053 [ # # ]: 0 : else if (query->override == OVERRIDING_USER_VALUE)
7054 : 0 : appendStringInfoString(buf, "OVERRIDING USER VALUE ");
7055 : : }
7056 : :
7165 mail@joeconway.com 7057 [ + + ]:CBC 173 : if (select_rte)
7058 : : {
7059 : : /* Add the SELECT */
1579 tgl@sss.pgh.pa.us 7060 : 25 : get_query_def(select_rte->subquery, buf, context->namespaces, NULL,
7061 : : false,
7062 : : context->prettyFlags, context->wrapColumn,
7063 : : context->indentLevel);
7064 : : }
7165 mail@joeconway.com 7065 [ + + ]: 148 : else if (values_rte)
7066 : : {
7067 : : /* Add the multi-VALUES expression lists */
7068 : 22 : get_values_def(values_rte->values_lists, context);
7069 : : }
4895 tgl@sss.pgh.pa.us 7070 [ + - ]: 126 : else if (strippedexprs)
7071 : : {
7072 : : /* Add the single-VALUES expression list */
8264 7073 : 126 : appendContextKeyword(context, "VALUES (",
7074 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
1522 7075 : 126 : get_rule_list_toplevel(strippedexprs, context, false);
9520 7076 : 126 : appendStringInfoChar(buf, ')');
7077 : : }
7078 : : else
7079 : : {
7080 : : /* No expressions, so it must be DEFAULT VALUES */
4518 rhaas@postgresql.org 7081 :UBC 0 : appendStringInfoString(buf, "DEFAULT VALUES");
7082 : : }
7083 : :
7084 : : /* Add ON CONFLICT if present */
3964 andres@anarazel.de 7085 [ + + ]:CBC 173 : if (query->onConflict)
7086 : : {
7087 : 18 : OnConflictExpr *confl = query->onConflict;
7088 : :
3909 heikki.linnakangas@i 7089 : 18 : appendStringInfoString(buf, " ON CONFLICT");
7090 : :
3953 andres@anarazel.de 7091 [ + + ]: 18 : if (confl->arbiterElems)
7092 : : {
7093 : : /* Add the single-VALUES expression list */
7094 : 15 : appendStringInfoChar(buf, '(');
7095 : 15 : get_rule_expr((Node *) confl->arbiterElems, context, false);
7096 : 15 : appendStringInfoChar(buf, ')');
7097 : :
7098 : : /* Add a WHERE clause (for partial indexes) if given */
7099 [ + + ]: 15 : if (confl->arbiterWhere != NULL)
7100 : : {
7101 : : bool save_varprefix;
7102 : :
7103 : : /*
7104 : : * Force non-prefixing of Vars, since parser assumes that they
7105 : : * belong to target relation. WHERE clause does not use
7106 : : * InferenceElem, so this is separately required.
7107 : : */
3689 tgl@sss.pgh.pa.us 7108 : 6 : save_varprefix = context->varprefix;
7109 : 6 : context->varprefix = false;
7110 : :
3953 andres@anarazel.de 7111 : 6 : appendContextKeyword(context, " WHERE ",
7112 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
7113 : 6 : get_rule_expr(confl->arbiterWhere, context, false);
7114 : :
3689 tgl@sss.pgh.pa.us 7115 : 6 : context->varprefix = save_varprefix;
7116 : : }
7117 : : }
3595 7118 [ - + ]: 3 : else if (OidIsValid(confl->constraint))
7119 : : {
3949 bruce@momjian.us 7120 :UBC 0 : char *constraint = get_constraint_name(confl->constraint);
7121 : :
3595 tgl@sss.pgh.pa.us 7122 [ # # ]: 0 : if (!constraint)
7123 [ # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u",
7124 : : confl->constraint);
3953 andres@anarazel.de 7125 : 0 : appendStringInfo(buf, " ON CONSTRAINT %s",
7126 : : quote_identifier(constraint));
7127 : : }
7128 : :
3964 andres@anarazel.de 7129 [ + + ]:CBC 18 : if (confl->action == ONCONFLICT_NOTHING)
7130 : : {
3953 7131 : 9 : appendStringInfoString(buf, " DO NOTHING");
7132 : : }
31 dean.a.rasheed@gmail 7133 [ + + ]:GNC 9 : else if (confl->action == ONCONFLICT_UPDATE)
7134 : : {
3953 andres@anarazel.de 7135 :CBC 6 : appendStringInfoString(buf, " DO UPDATE SET ");
7136 : : /* Deparse targetlist */
3964 7137 : 6 : get_update_query_targetlist_def(query, confl->onConflictSet,
7138 : : context, rte);
7139 : :
7140 : : /* Add a WHERE clause if given */
7141 [ + - ]: 6 : if (confl->onConflictWhere != NULL)
7142 : : {
7143 : 6 : appendContextKeyword(context, " WHERE ",
7144 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
7145 : 6 : get_rule_expr(confl->onConflictWhere, context, false);
7146 : : }
7147 : : }
7148 : : else
7149 : : {
31 dean.a.rasheed@gmail 7150 [ - + ]:GNC 3 : Assert(confl->action == ONCONFLICT_SELECT);
7151 : 3 : appendStringInfoString(buf, " DO SELECT");
7152 : :
7153 : : /* Add FOR [KEY] UPDATE/SHARE clause if present */
7154 [ + - ]: 3 : if (confl->lockStrength != LCS_NONE)
7155 : 3 : appendStringInfoString(buf, get_lock_clause_strength(confl->lockStrength));
7156 : :
7157 : : /* Add a WHERE clause if given */
7158 [ + - ]: 3 : if (confl->onConflictWhere != NULL)
7159 : : {
7160 : 3 : appendContextKeyword(context, " WHERE ",
7161 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
7162 : 3 : get_rule_expr(confl->onConflictWhere, context, false);
7163 : : }
7164 : : }
7165 : : }
7166 : :
7167 : : /* Add RETURNING if present */
7155 tgl@sss.pgh.pa.us 7168 [ + + ]:CBC 173 : if (query->returningList)
423 dean.a.rasheed@gmail 7169 : 42 : get_returning_clause(query, context);
10065 bruce@momjian.us 7170 : 173 : }
7171 : :
7172 : :
7173 : : /* ----------
7174 : : * get_update_query_def - Parse back an UPDATE parsetree
7175 : : * ----------
7176 : : */
7177 : : static void
563 tgl@sss.pgh.pa.us 7178 : 77 : get_update_query_def(Query *query, deparse_context *context)
7179 : : {
7868 bruce@momjian.us 7180 : 77 : StringInfo buf = context->buf;
7181 : : RangeTblEntry *rte;
7182 : :
7183 : : /* Insert the WITH clause if given */
5630 tgl@sss.pgh.pa.us 7184 : 77 : get_with_clause(query, context);
7185 : :
7186 : : /*
7187 : : * Start the query with UPDATE relname SET
7188 : : */
9632 7189 : 77 : rte = rt_fetch(query->resultRelation, query->rtable);
8717 7190 [ - + ]: 77 : Assert(rte->rtekind == RTE_RELATION);
8264 7191 [ + - ]: 77 : if (PRETTY_INDENT(context))
7192 : : {
8259 bruce@momjian.us 7193 : 77 : appendStringInfoChar(buf, ' ');
7194 : 77 : context->indentLevel += PRETTYINDENT_STD;
7195 : : }
6525 tgl@sss.pgh.pa.us 7196 : 154 : appendStringInfo(buf, "UPDATE %s%s",
9409 7197 [ + - ]: 77 : only_marker(rte),
7198 : : generate_relation_name(rte->relid, NIL));
7199 : :
7200 : : /* Print the relation alias, if needed */
1122 7201 : 77 : get_rte_alias(rte, query->resultRelation, false, context);
7202 : :
6525 7203 : 77 : appendStringInfoString(buf, " SET ");
7204 : :
7205 : : /* Deparse targetlist */
3964 andres@anarazel.de 7206 : 77 : get_update_query_targetlist_def(query, query->targetList, context, rte);
7207 : :
7208 : : /* Add the FROM clause if needed */
7209 : 77 : get_from_clause(query, " FROM ", context);
7210 : :
7211 : : /* Add a WHERE clause if given */
7212 [ + + ]: 77 : if (query->jointree->quals != NULL)
7213 : : {
7214 : 57 : appendContextKeyword(context, " WHERE ",
7215 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
7216 : 57 : get_rule_expr(query->jointree->quals, context, false);
7217 : : }
7218 : :
7219 : : /* Add RETURNING if present */
7220 [ + + ]: 77 : if (query->returningList)
423 dean.a.rasheed@gmail 7221 : 23 : get_returning_clause(query, context);
3964 andres@anarazel.de 7222 : 77 : }
7223 : :
7224 : :
7225 : : /* ----------
7226 : : * get_update_query_targetlist_def - Parse back an UPDATE targetlist
7227 : : * ----------
7228 : : */
7229 : : static void
7230 : 95 : get_update_query_targetlist_def(Query *query, List *targetList,
7231 : : deparse_context *context, RangeTblEntry *rte)
7232 : : {
7233 : 95 : StringInfo buf = context->buf;
7234 : : ListCell *l;
7235 : : ListCell *next_ma_cell;
7236 : : int remaining_ma_columns;
7237 : : const char *sep;
7238 : : SubLink *cur_ma_sublink;
7239 : : List *ma_sublinks;
7240 : :
7241 : : /*
7242 : : * Prepare to deal with MULTIEXPR assignments: collect the source SubLinks
7243 : : * into a list. We expect them to appear, in ID order, in resjunk tlist
7244 : : * entries.
7245 : : */
4288 tgl@sss.pgh.pa.us 7246 : 95 : ma_sublinks = NIL;
7247 [ + + ]: 95 : if (query->hasSubLinks) /* else there can't be any */
7248 : : {
3964 andres@anarazel.de 7249 [ + - + + : 21 : foreach(l, targetList)
+ + ]
7250 : : {
4288 tgl@sss.pgh.pa.us 7251 : 15 : TargetEntry *tle = (TargetEntry *) lfirst(l);
7252 : :
7253 [ + + + - ]: 15 : if (tle->resjunk && IsA(tle->expr, SubLink))
7254 : : {
7255 : 3 : SubLink *sl = (SubLink *) tle->expr;
7256 : :
7257 [ + - ]: 3 : if (sl->subLinkType == MULTIEXPR_SUBLINK)
7258 : : {
7259 : 3 : ma_sublinks = lappend(ma_sublinks, sl);
7260 [ - + ]: 3 : Assert(sl->subLinkId == list_length(ma_sublinks));
7261 : : }
7262 : : }
7263 : : }
7264 : : }
7265 : 95 : next_ma_cell = list_head(ma_sublinks);
7266 : 95 : cur_ma_sublink = NULL;
7267 : 95 : remaining_ma_columns = 0;
7268 : :
7269 : : /* Add the comma separated list of 'attname = value' */
10057 bruce@momjian.us 7270 : 95 : sep = "";
3964 andres@anarazel.de 7271 [ + - + + : 244 : foreach(l, targetList)
+ + ]
7272 : : {
9298 tgl@sss.pgh.pa.us 7273 : 149 : TargetEntry *tle = (TargetEntry *) lfirst(l);
7274 : : Node *expr;
7275 : :
7648 7276 [ + + ]: 149 : if (tle->resjunk)
9298 7277 : 3 : continue; /* ignore junk entries */
7278 : :
7279 : : /* Emit separator (OK whether we're in multiassignment or not) */
7624 neilc@samurai.com 7280 : 146 : appendStringInfoString(buf, sep);
10057 bruce@momjian.us 7281 : 146 : sep = ", ";
7282 : :
7283 : : /*
7284 : : * Check to see if we're starting a multiassignment group: if so,
7285 : : * output a left paren.
7286 : : */
4288 tgl@sss.pgh.pa.us 7287 [ + + + - ]: 146 : if (next_ma_cell != NULL && cur_ma_sublink == NULL)
7288 : : {
7289 : : /*
7290 : : * We must dig down into the expr to see if it's a PARAM_MULTIEXPR
7291 : : * Param. That could be buried under FieldStores and
7292 : : * SubscriptingRefs and CoerceToDomains (cf processIndirection()),
7293 : : * and underneath those there could be an implicit type coercion.
7294 : : * Because we would ignore implicit type coercions anyway, we
7295 : : * don't need to be as careful as processIndirection() is about
7296 : : * descending past implicit CoerceToDomains.
7297 : : */
7298 : 3 : expr = (Node *) tle->expr;
7299 [ + - ]: 6 : while (expr)
7300 : : {
7301 [ - + ]: 6 : if (IsA(expr, FieldStore))
7302 : : {
4288 tgl@sss.pgh.pa.us 7303 :UBC 0 : FieldStore *fstore = (FieldStore *) expr;
7304 : :
7305 : 0 : expr = (Node *) linitial(fstore->newvals);
7306 : : }
2599 alvherre@alvh.no-ip. 7307 [ + + ]:CBC 6 : else if (IsA(expr, SubscriptingRef))
7308 : : {
7309 : 3 : SubscriptingRef *sbsref = (SubscriptingRef *) expr;
7310 : :
7311 [ - + ]: 3 : if (sbsref->refassgnexpr == NULL)
4288 tgl@sss.pgh.pa.us 7312 :UBC 0 : break;
7313 : :
2599 alvherre@alvh.no-ip. 7314 :CBC 3 : expr = (Node *) sbsref->refassgnexpr;
7315 : : }
3168 tgl@sss.pgh.pa.us 7316 [ - + ]: 3 : else if (IsA(expr, CoerceToDomain))
7317 : : {
3168 tgl@sss.pgh.pa.us 7318 :UBC 0 : CoerceToDomain *cdomain = (CoerceToDomain *) expr;
7319 : :
7320 [ # # ]: 0 : if (cdomain->coercionformat != COERCE_IMPLICIT_CAST)
7321 : 0 : break;
7322 : 0 : expr = (Node *) cdomain->arg;
7323 : : }
7324 : : else
4288 tgl@sss.pgh.pa.us 7325 :CBC 3 : break;
7326 : : }
7327 : 3 : expr = strip_implicit_coercions(expr);
7328 : :
7329 [ + - + - ]: 3 : if (expr && IsA(expr, Param) &&
7330 [ + - ]: 3 : ((Param *) expr)->paramkind == PARAM_MULTIEXPR)
7331 : : {
7332 : 3 : cur_ma_sublink = (SubLink *) lfirst(next_ma_cell);
2435 7333 : 3 : next_ma_cell = lnext(ma_sublinks, next_ma_cell);
2236 alvherre@alvh.no-ip. 7334 : 3 : remaining_ma_columns = count_nonjunk_tlist_entries(((Query *) cur_ma_sublink->subselect)->targetList);
4288 tgl@sss.pgh.pa.us 7335 [ - + ]: 3 : Assert(((Param *) expr)->paramid ==
7336 : : ((cur_ma_sublink->subLinkId << 16) | 1));
7337 : 3 : appendStringInfoChar(buf, '(');
7338 : : }
7339 : : }
7340 : :
7341 : : /*
7342 : : * Put out name of target column; look in the catalogs, not at
7343 : : * tle->resname, since resname will fail to track RENAME.
7344 : : */
7949 7345 : 146 : appendStringInfoString(buf,
2953 alvherre@alvh.no-ip. 7346 : 146 : quote_identifier(get_attname(rte->relid,
7347 : 146 : tle->resno,
7348 : : false)));
7349 : :
7350 : : /*
7351 : : * Print any indirection needed (subfields or subscripts), and strip
7352 : : * off the top-level nodes representing the indirection assignments.
7353 : : */
3511 tgl@sss.pgh.pa.us 7354 : 146 : expr = processIndirection((Node *) tle->expr, context);
7355 : :
7356 : : /*
7357 : : * If we're in a multiassignment, skip printing anything more, unless
7358 : : * this is the last column; in which case, what we print should be the
7359 : : * sublink, not the Param.
7360 : : */
4288 7361 [ + + ]: 146 : if (cur_ma_sublink != NULL)
7362 : : {
7363 [ + + ]: 9 : if (--remaining_ma_columns > 0)
7364 : 6 : continue; /* not the last column of multiassignment */
7365 : 3 : appendStringInfoChar(buf, ')');
7366 : 3 : expr = (Node *) cur_ma_sublink;
7367 : 3 : cur_ma_sublink = NULL;
7368 : : }
7369 : :
4518 rhaas@postgresql.org 7370 : 140 : appendStringInfoString(buf, " = ");
7371 : :
7949 tgl@sss.pgh.pa.us 7372 : 140 : get_rule_expr(expr, context, false);
7373 : : }
10065 bruce@momjian.us 7374 : 95 : }
7375 : :
7376 : :
7377 : : /* ----------
7378 : : * get_delete_query_def - Parse back a DELETE parsetree
7379 : : * ----------
7380 : : */
7381 : : static void
563 tgl@sss.pgh.pa.us 7382 : 38 : get_delete_query_def(Query *query, deparse_context *context)
7383 : : {
9660 7384 : 38 : StringInfo buf = context->buf;
7385 : : RangeTblEntry *rte;
7386 : :
7387 : : /* Insert the WITH clause if given */
5630 7388 : 38 : get_with_clause(query, context);
7389 : :
7390 : : /*
7391 : : * Start the query with DELETE FROM relname
7392 : : */
9632 7393 : 38 : rte = rt_fetch(query->resultRelation, query->rtable);
8717 7394 [ - + ]: 38 : Assert(rte->rtekind == RTE_RELATION);
8264 7395 [ + - ]: 38 : if (PRETTY_INDENT(context))
7396 : : {
8259 bruce@momjian.us 7397 : 38 : appendStringInfoChar(buf, ' ');
6525 tgl@sss.pgh.pa.us 7398 : 38 : context->indentLevel += PRETTYINDENT_STD;
7399 : : }
9632 7400 : 76 : appendStringInfo(buf, "DELETE FROM %s%s",
9409 7401 [ + - ]: 38 : only_marker(rte),
7402 : : generate_relation_name(rte->relid, NIL));
7403 : :
7404 : : /* Print the relation alias, if needed */
1122 7405 : 38 : get_rte_alias(rte, query->resultRelation, false, context);
7406 : :
7407 : : /* Add the USING clause if given */
7531 7408 : 38 : get_from_clause(query, " USING ", context);
7409 : :
7410 : : /* Add a WHERE clause if given */
9298 7411 [ + - ]: 38 : if (query->jointree->quals != NULL)
7412 : : {
8264 7413 : 38 : appendContextKeyword(context, " WHERE ",
7414 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
8578 7415 : 38 : get_rule_expr(query->jointree->quals, context, false);
7416 : : }
7417 : :
7418 : : /* Add RETURNING if present */
7155 7419 [ + + ]: 38 : if (query->returningList)
423 dean.a.rasheed@gmail 7420 : 8 : get_returning_clause(query, context);
10065 bruce@momjian.us 7421 : 38 : }
7422 : :
7423 : :
7424 : : /* ----------
7425 : : * get_merge_query_def - Parse back a MERGE parsetree
7426 : : * ----------
7427 : : */
7428 : : static void
563 tgl@sss.pgh.pa.us 7429 : 6 : get_merge_query_def(Query *query, deparse_context *context)
7430 : : {
1043 7431 : 6 : StringInfo buf = context->buf;
7432 : : RangeTblEntry *rte;
7433 : : ListCell *lc;
7434 : : bool haveNotMatchedBySource;
7435 : :
7436 : : /* Insert the WITH clause if given */
7437 : 6 : get_with_clause(query, context);
7438 : :
7439 : : /*
7440 : : * Start the query with MERGE INTO relname
7441 : : */
7442 : 6 : rte = rt_fetch(query->resultRelation, query->rtable);
7443 [ - + ]: 6 : Assert(rte->rtekind == RTE_RELATION);
7444 [ + - ]: 6 : if (PRETTY_INDENT(context))
7445 : : {
7446 : 6 : appendStringInfoChar(buf, ' ');
7447 : 6 : context->indentLevel += PRETTYINDENT_STD;
7448 : : }
7449 : 12 : appendStringInfo(buf, "MERGE INTO %s%s",
7450 [ + - ]: 6 : only_marker(rte),
7451 : : generate_relation_name(rte->relid, NIL));
7452 : :
7453 : : /* Print the relation alias, if needed */
7454 : 6 : get_rte_alias(rte, query->resultRelation, false, context);
7455 : :
7456 : : /* Print the source relation and join clause */
7457 : 6 : get_from_clause(query, " USING ", context);
7458 : 6 : appendContextKeyword(context, " ON ",
7459 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
715 dean.a.rasheed@gmail 7460 : 6 : get_rule_expr(query->mergeJoinCondition, context, false);
7461 : :
7462 : : /*
7463 : : * Test for any NOT MATCHED BY SOURCE actions. If there are none, then
7464 : : * any NOT MATCHED BY TARGET actions are output as "WHEN NOT MATCHED", per
7465 : : * SQL standard. Otherwise, we have a non-SQL-standard query, so output
7466 : : * "BY SOURCE" / "BY TARGET" qualifiers for all NOT MATCHED actions, to be
7467 : : * more explicit.
7468 : : */
7469 : 6 : haveNotMatchedBySource = false;
7470 [ + - + + : 42 : foreach(lc, query->mergeActionList)
+ + ]
7471 : : {
7472 : 39 : MergeAction *action = lfirst_node(MergeAction, lc);
7473 : :
7474 [ + + ]: 39 : if (action->matchKind == MERGE_WHEN_NOT_MATCHED_BY_SOURCE)
7475 : : {
7476 : 3 : haveNotMatchedBySource = true;
7477 : 3 : break;
7478 : : }
7479 : : }
7480 : :
7481 : : /* Print each merge action */
1043 tgl@sss.pgh.pa.us 7482 [ + - + + : 45 : foreach(lc, query->mergeActionList)
+ + ]
7483 : : {
7484 : 39 : MergeAction *action = lfirst_node(MergeAction, lc);
7485 : :
7486 : 39 : appendContextKeyword(context, " WHEN ",
7487 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
715 dean.a.rasheed@gmail 7488 [ + + + - ]: 39 : switch (action->matchKind)
7489 : : {
7490 : 18 : case MERGE_WHEN_MATCHED:
704 drowley@postgresql.o 7491 : 18 : appendStringInfoString(buf, "MATCHED");
715 dean.a.rasheed@gmail 7492 : 18 : break;
7493 : 3 : case MERGE_WHEN_NOT_MATCHED_BY_SOURCE:
704 drowley@postgresql.o 7494 : 3 : appendStringInfoString(buf, "NOT MATCHED BY SOURCE");
715 dean.a.rasheed@gmail 7495 : 3 : break;
7496 : 18 : case MERGE_WHEN_NOT_MATCHED_BY_TARGET:
7497 [ + + ]: 18 : if (haveNotMatchedBySource)
704 drowley@postgresql.o 7498 : 3 : appendStringInfoString(buf, "NOT MATCHED BY TARGET");
7499 : : else
7500 : 15 : appendStringInfoString(buf, "NOT MATCHED");
715 dean.a.rasheed@gmail 7501 : 18 : break;
715 dean.a.rasheed@gmail 7502 :UBC 0 : default:
7503 [ # # ]: 0 : elog(ERROR, "unrecognized matchKind: %d",
7504 : : (int) action->matchKind);
7505 : : }
7506 : :
1043 tgl@sss.pgh.pa.us 7507 [ + + ]:CBC 39 : if (action->qual)
7508 : : {
7509 : 24 : appendContextKeyword(context, " AND ",
7510 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 3);
7511 : 24 : get_rule_expr(action->qual, context, false);
7512 : : }
7513 : 39 : appendContextKeyword(context, " THEN ",
7514 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 3);
7515 : :
7516 [ + + ]: 39 : if (action->commandType == CMD_INSERT)
7517 : : {
7518 : : /* This generally matches get_insert_query_def() */
7519 : 18 : List *strippedexprs = NIL;
7520 : 18 : const char *sep = "";
7521 : : ListCell *lc2;
7522 : :
7523 : 18 : appendStringInfoString(buf, "INSERT");
7524 : :
7525 [ + + ]: 18 : if (action->targetList)
7526 : 15 : appendStringInfoString(buf, " (");
7527 [ + + + + : 51 : foreach(lc2, action->targetList)
+ + ]
7528 : : {
7529 : 33 : TargetEntry *tle = (TargetEntry *) lfirst(lc2);
7530 : :
7531 [ - + ]: 33 : Assert(!tle->resjunk);
7532 : :
7533 : 33 : appendStringInfoString(buf, sep);
7534 : 33 : sep = ", ";
7535 : :
7536 : 33 : appendStringInfoString(buf,
7537 : 33 : quote_identifier(get_attname(rte->relid,
7538 : 33 : tle->resno,
7539 : : false)));
7540 : 33 : strippedexprs = lappend(strippedexprs,
7541 : 33 : processIndirection((Node *) tle->expr,
7542 : : context));
7543 : : }
7544 [ + + ]: 18 : if (action->targetList)
7545 : 15 : appendStringInfoChar(buf, ')');
7546 : :
7547 [ + + ]: 18 : if (action->override)
7548 : : {
7549 [ - + ]: 3 : if (action->override == OVERRIDING_SYSTEM_VALUE)
1043 tgl@sss.pgh.pa.us 7550 :UBC 0 : appendStringInfoString(buf, " OVERRIDING SYSTEM VALUE");
1043 tgl@sss.pgh.pa.us 7551 [ + - ]:CBC 3 : else if (action->override == OVERRIDING_USER_VALUE)
7552 : 3 : appendStringInfoString(buf, " OVERRIDING USER VALUE");
7553 : : }
7554 : :
7555 [ + + ]: 18 : if (strippedexprs)
7556 : : {
7557 : 15 : appendContextKeyword(context, " VALUES (",
7558 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 4);
7559 : 15 : get_rule_list_toplevel(strippedexprs, context, false);
7560 : 15 : appendStringInfoChar(buf, ')');
7561 : : }
7562 : : else
7563 : 3 : appendStringInfoString(buf, " DEFAULT VALUES");
7564 : : }
7565 [ + + ]: 21 : else if (action->commandType == CMD_UPDATE)
7566 : : {
7567 : 12 : appendStringInfoString(buf, "UPDATE SET ");
7568 : 12 : get_update_query_targetlist_def(query, action->targetList,
7569 : : context, rte);
7570 : : }
7571 [ + + ]: 9 : else if (action->commandType == CMD_DELETE)
7572 : 6 : appendStringInfoString(buf, "DELETE");
7573 [ + - ]: 3 : else if (action->commandType == CMD_NOTHING)
7574 : 3 : appendStringInfoString(buf, "DO NOTHING");
7575 : : }
7576 : :
7577 : : /* Add RETURNING if present */
728 dean.a.rasheed@gmail 7578 [ + + ]: 6 : if (query->returningList)
423 7579 : 3 : get_returning_clause(query, context);
1043 tgl@sss.pgh.pa.us 7580 : 6 : }
7581 : :
7582 : :
7583 : : /* ----------
7584 : : * get_utility_query_def - Parse back a UTILITY parsetree
7585 : : * ----------
7586 : : */
7587 : : static void
9202 7588 : 8 : get_utility_query_def(Query *query, deparse_context *context)
7589 : : {
7590 : 8 : StringInfo buf = context->buf;
7591 : :
7592 [ + - + - ]: 8 : if (query->utilityStmt && IsA(query->utilityStmt, NotifyStmt))
7593 : 8 : {
7594 : 8 : NotifyStmt *stmt = (NotifyStmt *) query->utilityStmt;
7595 : :
8264 7596 : 8 : appendContextKeyword(context, "",
7597 : : 0, PRETTYINDENT_STD, 1);
8759 7598 : 8 : appendStringInfo(buf, "NOTIFY %s",
6404 7599 : 8 : quote_identifier(stmt->conditionname));
5871 7600 [ - + ]: 8 : if (stmt->payload)
7601 : : {
5871 tgl@sss.pgh.pa.us 7602 :UBC 0 : appendStringInfoString(buf, ", ");
7603 : 0 : simple_quote_literal(buf, stmt->payload);
7604 : : }
7605 : : }
7606 : : else
7607 : : {
7608 : : /* Currently only NOTIFY utility commands can appear in rules */
8267 7609 [ # # ]: 0 : elog(ERROR, "unexpected utility statement type");
7610 : : }
9202 tgl@sss.pgh.pa.us 7611 :CBC 8 : }
7612 : :
7613 : : /*
7614 : : * Display a Var appropriately.
7615 : : *
7616 : : * In some cases (currently only when recursing into an unnamed join)
7617 : : * the Var's varlevelsup has to be interpreted with respect to a context
7618 : : * above the current one; levelsup indicates the offset.
7619 : : *
7620 : : * If istoplevel is true, the Var is at the top level of a SELECT's
7621 : : * targetlist, which means we need special treatment of whole-row Vars.
7622 : : * Instead of the normal "tab.*", we'll print "tab.*::typename", which is a
7623 : : * dirty hack to prevent "tab.*" from being expanded into multiple columns.
7624 : : * (The parser will strip the useless coercion, so no inefficiency is added in
7625 : : * dump and reload.) We used to print just "tab" in such cases, but that is
7626 : : * ambiguous and will yield the wrong result if "tab" is also a plain column
7627 : : * name in the query.
7628 : : *
7629 : : * Returns the attname of the Var, or NULL if the Var has no attname (because
7630 : : * it is a whole-row Var or a subplan output reference).
7631 : : */
7632 : : static char *
5070 7633 : 97304 : get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
7634 : : {
6960 7635 : 97304 : StringInfo buf = context->buf;
7636 : : RangeTblEntry *rte;
7637 : : AttrNumber attnum;
7638 : : int netlevelsup;
7639 : : deparse_namespace *dpns;
7640 : : int varno;
7641 : : AttrNumber varattno;
7642 : : deparse_columns *colinfo;
7643 : : char *refname;
7644 : : char *attname;
7645 : : bool need_prefix;
7646 : :
7647 : : /* Find appropriate nesting depth */
7731 7648 : 97304 : netlevelsup = var->varlevelsup + levelsup;
7649 [ - + ]: 97304 : if (netlevelsup >= list_length(context->namespaces))
7731 tgl@sss.pgh.pa.us 7650 [ # # ]:UBC 0 : elog(ERROR, "bogus varlevelsup: %d offset %d",
7651 : : var->varlevelsup, levelsup);
7963 tgl@sss.pgh.pa.us 7652 :CBC 97304 : dpns = (deparse_namespace *) list_nth(context->namespaces,
7653 : : netlevelsup);
7654 : :
7655 : : /*
7656 : : * If we have a syntactic referent for the Var, and we're working from a
7657 : : * parse tree, prefer to use the syntactic referent. Otherwise, fall back
7658 : : * on the semantic referent. (Forcing use of the semantic referent when
7659 : : * printing plan trees is a design choice that's perhaps more motivated by
7660 : : * backwards compatibility than anything else. But it does have the
7661 : : * advantage of making plans more explicit.)
7662 : : */
2257 7663 [ + + + + ]: 97304 : if (var->varnosyn > 0 && dpns->plan == NULL)
7664 : : {
7665 : 19899 : varno = var->varnosyn;
7666 : 19899 : varattno = var->varattnosyn;
7667 : : }
7668 : : else
7669 : : {
7670 : 77405 : varno = var->varno;
7671 : 77405 : varattno = var->varattno;
7672 : : }
7673 : :
7674 : : /*
7675 : : * Try to find the relevant RTE in this rtable. In a plan tree, it's
7676 : : * likely that varno is OUTER_VAR or INNER_VAR, in which case we must dig
7677 : : * down into the subplans, or INDEX_VAR, which is resolved similarly. Also
7678 : : * find the aliases previously assigned for this RTE.
7679 : : */
7680 [ + + + - ]: 97304 : if (varno >= 1 && varno <= list_length(dpns->rtable))
7681 : : {
7682 : : /*
7683 : : * We might have been asked to map child Vars to some parent relation.
7684 : : */
2286 7685 [ + + + + ]: 70969 : if (context->appendparents && dpns->appendrels)
7686 : : {
1642 7687 : 1951 : int pvarno = varno;
2286 7688 : 1951 : AttrNumber pvarattno = varattno;
7689 : 1951 : AppendRelInfo *appinfo = dpns->appendrels[pvarno];
7690 : 1951 : bool found = false;
7691 : :
7692 : : /* Only map up to inheritance parents, not UNION ALL appendrels */
7693 [ + + ]: 3936 : while (appinfo &&
7694 : 2160 : rt_fetch(appinfo->parent_relid,
7695 [ + + ]: 2160 : dpns->rtable)->rtekind == RTE_RELATION)
7696 : : {
7697 : 1985 : found = false;
7698 [ + + ]: 1985 : if (pvarattno > 0) /* system columns stay as-is */
7699 : : {
7700 [ - + ]: 1846 : if (pvarattno > appinfo->num_child_cols)
2286 tgl@sss.pgh.pa.us 7701 :UBC 0 : break; /* safety check */
2286 tgl@sss.pgh.pa.us 7702 :CBC 1846 : pvarattno = appinfo->parent_colnos[pvarattno - 1];
7703 [ - + ]: 1846 : if (pvarattno == 0)
2286 tgl@sss.pgh.pa.us 7704 :UBC 0 : break; /* Var is local to child */
7705 : : }
7706 : :
2286 tgl@sss.pgh.pa.us 7707 :CBC 1985 : pvarno = appinfo->parent_relid;
7708 : 1985 : found = true;
7709 : :
7710 : : /* If the parent is itself a child, continue up. */
7711 [ + - - + ]: 1985 : Assert(pvarno > 0 && pvarno <= list_length(dpns->rtable));
7712 : 1985 : appinfo = dpns->appendrels[pvarno];
7713 : : }
7714 : :
7715 : : /*
7716 : : * If we found an ancestral rel, and that rel is included in
7717 : : * appendparents, print that column not the original one.
7718 : : */
7719 [ + + + + ]: 1951 : if (found && bms_is_member(pvarno, context->appendparents))
7720 : : {
7721 : 1590 : varno = pvarno;
7722 : 1590 : varattno = pvarattno;
7723 : : }
7724 : : }
7725 : :
7726 : 70969 : rte = rt_fetch(varno, dpns->rtable);
7727 : :
7728 : : /* might be returning old/new column value */
423 dean.a.rasheed@gmail 7729 [ + + ]: 70969 : if (var->varreturningtype == VAR_RETURNING_OLD)
7730 : 208 : refname = dpns->ret_old_alias;
7731 [ + + ]: 70761 : else if (var->varreturningtype == VAR_RETURNING_NEW)
7732 : 207 : refname = dpns->ret_new_alias;
7733 : : else
7734 : 70554 : refname = (char *) list_nth(dpns->rtable_names, varno - 1);
7735 : :
2286 tgl@sss.pgh.pa.us 7736 : 70969 : colinfo = deparse_columns_fetch(varno, dpns);
7737 : 70969 : attnum = varattno;
7738 : : }
7739 : : else
7740 : : {
7741 : 26335 : resolve_special_varno((Node *) var, context,
7742 : : get_special_variable, NULL);
3609 rhaas@postgresql.org 7743 : 26335 : return NULL;
7744 : : }
7745 : :
7746 : : /*
7747 : : * The planner will sometimes emit Vars referencing resjunk elements of a
7748 : : * subquery's target list (this is currently only possible if it chooses
7749 : : * to generate a "physical tlist" for a SubqueryScan or CteScan node).
7750 : : * Although we prefer to print subquery-referencing Vars using the
7751 : : * subquery's alias, that's not possible for resjunk items since they have
7752 : : * no alias. So in that case, drill down to the subplan and print the
7753 : : * contents of the referenced tlist item. This works because in a plan
7754 : : * tree, such Vars can only occur in a SubqueryScan or CteScan node, and
7755 : : * we'll have set dpns->inner_plan to reference the child plan node.
7756 : : */
5728 tgl@sss.pgh.pa.us 7757 [ + + + + : 73340 : if ((rte->rtekind == RTE_SUBQUERY || rte->rtekind == RTE_CTE) &&
+ + ]
7758 : 2371 : attnum > list_length(rte->eref->colnames) &&
2286 7759 [ + - ]: 1 : dpns->inner_plan)
7760 : : {
7761 : : TargetEntry *tle;
7762 : : deparse_namespace save_dpns;
7763 : :
7764 : 1 : tle = get_tle_by_resno(dpns->inner_tlist, attnum);
5728 7765 [ - + ]: 1 : if (!tle)
3544 tgl@sss.pgh.pa.us 7766 [ # # ]:UBC 0 : elog(ERROR, "invalid attnum %d for relation \"%s\"",
7767 : : attnum, rte->eref->aliasname);
7768 : :
5728 tgl@sss.pgh.pa.us 7769 [ - + ]:CBC 1 : Assert(netlevelsup == 0);
2286 7770 : 1 : push_child_plan(dpns, dpns->inner_plan, &save_dpns);
7771 : :
7772 : : /*
7773 : : * Force parentheses because our caller probably assumed a Var is a
7774 : : * simple expression.
7775 : : */
5728 7776 [ - + ]: 1 : if (!IsA(tle->expr, Var))
5728 tgl@sss.pgh.pa.us 7777 :UBC 0 : appendStringInfoChar(buf, '(');
5728 tgl@sss.pgh.pa.us 7778 :CBC 1 : get_rule_expr((Node *) tle->expr, context, true);
7779 [ - + ]: 1 : if (!IsA(tle->expr, Var))
5728 tgl@sss.pgh.pa.us 7780 :UBC 0 : appendStringInfoChar(buf, ')');
7781 : :
5724 tgl@sss.pgh.pa.us 7782 :CBC 1 : pop_child_plan(dpns, &save_dpns);
5728 7783 : 1 : return NULL;
7784 : : }
7785 : :
7786 : : /*
7787 : : * If it's an unnamed join, look at the expansion of the alias variable.
7788 : : * If it's a simple reference to one of the input vars, then recursively
7789 : : * print the name of that var instead. When it's not a simple reference,
7790 : : * we have to just print the unqualified join column name. (This can only
7791 : : * happen with "dangerous" merged columns in a JOIN USING; we took pains
7792 : : * previously to make the unqualified column name unique in such cases.)
7793 : : *
7794 : : * This wouldn't work in decompiling plan trees, because we don't store
7795 : : * joinaliasvars lists after planning; but a plan tree should never
7796 : : * contain a join alias variable.
7797 : : */
4923 7798 [ + + + + ]: 70968 : if (rte->rtekind == RTE_JOIN && rte->alias == NULL)
7799 : : {
7800 [ - + ]: 54 : if (rte->joinaliasvars == NIL)
4923 tgl@sss.pgh.pa.us 7801 [ # # ]:UBC 0 : elog(ERROR, "cannot decompile join alias var in plan tree");
4923 tgl@sss.pgh.pa.us 7802 [ + - ]:CBC 54 : if (attnum > 0)
7803 : : {
7804 : : Var *aliasvar;
7805 : :
7806 : 54 : aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1);
7807 : : /* we intentionally don't strip implicit coercions here */
4618 7808 [ + - - + ]: 54 : if (aliasvar && IsA(aliasvar, Var))
7809 : : {
4923 tgl@sss.pgh.pa.us 7810 :UBC 0 : return get_variable(aliasvar, var->varlevelsup + levelsup,
7811 : : istoplevel, context);
7812 : : }
7813 : : }
7814 : :
7815 : : /*
7816 : : * Unnamed join has no refname. (Note: since it's unnamed, there is
7817 : : * no way the user could have referenced it to create a whole-row Var
7818 : : * for it. So we don't have to cover that case below.)
7819 : : */
4923 tgl@sss.pgh.pa.us 7820 [ - + ]:CBC 54 : Assert(refname == NULL);
7821 : : }
7822 : :
7281 7823 [ + + ]: 70968 : if (attnum == InvalidAttrNumber)
6960 7824 : 541 : attname = NULL;
4822 7825 [ + + ]: 70427 : else if (attnum > 0)
7826 : : {
7827 : : /* Get column name to use from the colinfo struct */
3544 7828 [ - + ]: 69431 : if (attnum > colinfo->num_cols)
3544 tgl@sss.pgh.pa.us 7829 [ # # ]:UBC 0 : elog(ERROR, "invalid attnum %d for relation \"%s\"",
7830 : : attnum, rte->eref->aliasname);
4822 tgl@sss.pgh.pa.us 7831 :CBC 69431 : attname = colinfo->colnames[attnum - 1];
7832 : :
7833 : : /*
7834 : : * If we find a Var referencing a dropped column, it seems better to
7835 : : * print something (anything) than to fail. In general this should
7836 : : * not happen, but it used to be possible for some cases involving
7837 : : * functions returning named composite types, and perhaps there are
7838 : : * still bugs out there.
7839 : : */
1333 7840 [ + + ]: 69431 : if (attname == NULL)
7841 : 3 : attname = "?dropped?column?";
7842 : : }
7843 : : else
7844 : : {
7845 : : /* System column - name is fixed, get it from the catalog */
6960 7846 : 996 : attname = get_rte_attribute_name(rte, attnum);
7847 : : }
7848 : :
423 dean.a.rasheed@gmail 7849 [ + + + + ]: 104341 : need_prefix = (context->varprefix || attname == NULL ||
7850 [ + + ]: 33373 : var->varreturningtype != VAR_RETURNING_DEFAULT);
7851 : :
7852 : : /*
7853 : : * If we're considering a plain Var in an ORDER BY (but not GROUP BY)
7854 : : * clause, we may need to add a table-name prefix to prevent
7855 : : * findTargetlistEntrySQL92 from misinterpreting the name as an
7856 : : * output-column name. To avoid cluttering the output with unnecessary
7857 : : * prefixes, do so only if there is a name match to a SELECT tlist item
7858 : : * that is different from the Var.
7859 : : */
563 tgl@sss.pgh.pa.us 7860 [ + + + + : 70968 : if (context->varInOrderBy && !context->inGroupBy && !need_prefix)
+ + ]
7861 : : {
7862 : 126 : int colno = 0;
7863 : :
7864 [ + + + + : 488 : foreach_node(TargetEntry, tle, context->targetList)
+ + ]
7865 : : {
7866 : : char *colname;
7867 : :
7868 [ - + ]: 242 : if (tle->resjunk)
563 tgl@sss.pgh.pa.us 7869 :UBC 0 : continue; /* ignore junk entries */
563 tgl@sss.pgh.pa.us 7870 :CBC 242 : colno++;
7871 : :
7872 : : /* This must match colname-choosing logic in get_target_list() */
7873 [ + - + - ]: 242 : if (context->resultDesc && colno <= context->resultDesc->natts)
7874 : 242 : colname = NameStr(TupleDescAttr(context->resultDesc,
7875 : : colno - 1)->attname);
7876 : : else
563 tgl@sss.pgh.pa.us 7877 :UBC 0 : colname = tle->resname;
7878 : :
563 tgl@sss.pgh.pa.us 7879 [ + - + + ]:CBC 242 : if (colname && strcmp(colname, attname) == 0 &&
7880 [ + + ]: 87 : !equal(var, tle->expr))
7881 : : {
7882 : 6 : need_prefix = true;
7883 : 6 : break;
7884 : : }
7885 : : }
7886 : : }
7887 : :
7888 [ + + + + ]: 70968 : if (refname && need_prefix)
7889 : : {
5974 7890 : 37561 : appendStringInfoString(buf, quote_identifier(refname));
5070 7891 : 37561 : appendStringInfoChar(buf, '.');
7892 : : }
6960 7893 [ + + ]: 70968 : if (attname)
7894 : 70427 : appendStringInfoString(buf, quote_identifier(attname));
7895 : : else
7896 : : {
7897 : 541 : appendStringInfoChar(buf, '*');
5070 7898 [ + + ]: 541 : if (istoplevel)
7899 : 42 : appendStringInfo(buf, "::%s",
7900 : : format_type_with_typemod(var->vartype,
7901 : : var->vartypmod));
7902 : : }
7903 : :
6960 7904 : 70968 : return attname;
7905 : : }
7906 : :
7907 : : /*
7908 : : * Deparse a Var which references OUTER_VAR, INNER_VAR, or INDEX_VAR. This
7909 : : * routine is actually a callback for resolve_special_varno, which handles
7910 : : * finding the correct TargetEntry. We get the expression contained in that
7911 : : * TargetEntry and just need to deparse it, a job we can throw back on
7912 : : * get_rule_expr.
7913 : : */
7914 : : static void
2286 7915 : 26335 : get_special_variable(Node *node, deparse_context *context, void *callback_arg)
7916 : : {
3609 rhaas@postgresql.org 7917 : 26335 : StringInfo buf = context->buf;
7918 : :
7919 : : /*
7920 : : * For a non-Var referent, force parentheses because our caller probably
7921 : : * assumed a Var is a simple expression.
7922 : : */
7923 [ + + ]: 26335 : if (!IsA(node, Var))
7924 : 2651 : appendStringInfoChar(buf, '(');
7925 : 26335 : get_rule_expr(node, context, true);
7926 [ + + ]: 26335 : if (!IsA(node, Var))
7927 : 2651 : appendStringInfoChar(buf, ')');
7928 : 26335 : }
7929 : :
7930 : : /*
7931 : : * Chase through plan references to special varnos (OUTER_VAR, INNER_VAR,
7932 : : * INDEX_VAR) until we find a real Var or some kind of non-Var node; then,
7933 : : * invoke the callback provided.
7934 : : */
7935 : : static void
2286 tgl@sss.pgh.pa.us 7936 : 74157 : resolve_special_varno(Node *node, deparse_context *context,
7937 : : rsv_callback callback, void *callback_arg)
7938 : : {
7939 : : Var *var;
7940 : : deparse_namespace *dpns;
7941 : :
7942 : : /* This function is recursive, so let's be paranoid. */
7943 : 74157 : check_stack_depth();
7944 : :
7945 : : /* If it's not a Var, invoke the callback. */
3609 rhaas@postgresql.org 7946 [ + + ]: 74157 : if (!IsA(node, Var))
7947 : : {
2286 tgl@sss.pgh.pa.us 7948 : 3042 : (*callback) (node, context, callback_arg);
3609 rhaas@postgresql.org 7949 : 3042 : return;
7950 : : }
7951 : :
7952 : : /* Find appropriate nesting depth */
7953 : 71115 : var = (Var *) node;
7954 : 71115 : dpns = (deparse_namespace *) list_nth(context->namespaces,
7955 : 71115 : var->varlevelsup);
7956 : :
7957 : : /*
7958 : : * If varno is special, recurse. (Don't worry about varnosyn; if we're
7959 : : * here, we already decided not to use that.)
7960 : : */
7961 [ + + + - ]: 71115 : if (var->varno == OUTER_VAR && dpns->outer_tlist)
7962 : : {
7963 : : TargetEntry *tle;
7964 : : deparse_namespace save_dpns;
7965 : : Bitmapset *save_appendparents;
7966 : :
7967 : 35908 : tle = get_tle_by_resno(dpns->outer_tlist, var->varattno);
7968 [ - + ]: 35908 : if (!tle)
3609 rhaas@postgresql.org 7969 [ # # ]:UBC 0 : elog(ERROR, "bogus varattno for OUTER_VAR var: %d", var->varattno);
7970 : :
7971 : : /*
7972 : : * If we're descending to the first child of an Append or MergeAppend,
7973 : : * update appendparents. This will affect deparsing of all Vars
7974 : : * appearing within the eventually-resolved subexpression.
7975 : : */
2286 tgl@sss.pgh.pa.us 7976 :CBC 35908 : save_appendparents = context->appendparents;
7977 : :
7978 [ + + ]: 35908 : if (IsA(dpns->plan, Append))
7979 : 2274 : context->appendparents = bms_union(context->appendparents,
7980 : 2274 : ((Append *) dpns->plan)->apprelids);
7981 [ + + ]: 33634 : else if (IsA(dpns->plan, MergeAppend))
7982 : 316 : context->appendparents = bms_union(context->appendparents,
7983 : 316 : ((MergeAppend *) dpns->plan)->apprelids);
7984 : :
7985 : 35908 : push_child_plan(dpns, dpns->outer_plan, &save_dpns);
7986 : 35908 : resolve_special_varno((Node *) tle->expr, context,
7987 : : callback, callback_arg);
3609 rhaas@postgresql.org 7988 : 35908 : pop_child_plan(dpns, &save_dpns);
2286 tgl@sss.pgh.pa.us 7989 : 35908 : context->appendparents = save_appendparents;
3609 rhaas@postgresql.org 7990 : 35908 : return;
7991 : : }
7992 [ + + + - ]: 35207 : else if (var->varno == INNER_VAR && dpns->inner_tlist)
7993 : : {
7994 : : TargetEntry *tle;
7995 : : deparse_namespace save_dpns;
7996 : :
7997 : 8741 : tle = get_tle_by_resno(dpns->inner_tlist, var->varattno);
7998 [ - + ]: 8741 : if (!tle)
3609 rhaas@postgresql.org 7999 [ # # ]:UBC 0 : elog(ERROR, "bogus varattno for INNER_VAR var: %d", var->varattno);
8000 : :
2286 tgl@sss.pgh.pa.us 8001 :CBC 8741 : push_child_plan(dpns, dpns->inner_plan, &save_dpns);
8002 : 8741 : resolve_special_varno((Node *) tle->expr, context,
8003 : : callback, callback_arg);
3609 rhaas@postgresql.org 8004 : 8741 : pop_child_plan(dpns, &save_dpns);
8005 : 8741 : return;
8006 : : }
8007 [ + + + - ]: 26466 : else if (var->varno == INDEX_VAR && dpns->index_tlist)
8008 : : {
8009 : : TargetEntry *tle;
8010 : :
8011 : 2782 : tle = get_tle_by_resno(dpns->index_tlist, var->varattno);
8012 [ - + ]: 2782 : if (!tle)
3609 rhaas@postgresql.org 8013 [ # # ]:UBC 0 : elog(ERROR, "bogus varattno for INDEX_VAR var: %d", var->varattno);
8014 : :
2286 tgl@sss.pgh.pa.us 8015 :CBC 2782 : resolve_special_varno((Node *) tle->expr, context,
8016 : : callback, callback_arg);
3609 rhaas@postgresql.org 8017 : 2782 : return;
8018 : : }
8019 [ + - - + ]: 23684 : else if (var->varno < 1 || var->varno > list_length(dpns->rtable))
3609 rhaas@postgresql.org 8020 [ # # ]:UBC 0 : elog(ERROR, "bogus varno: %d", var->varno);
8021 : :
8022 : : /* Not special. Just invoke the callback. */
2286 tgl@sss.pgh.pa.us 8023 :CBC 23684 : (*callback) (node, context, callback_arg);
8024 : : }
8025 : :
8026 : : /*
8027 : : * Get the name of a field of an expression of composite type. The
8028 : : * expression is usually a Var, but we handle other cases too.
8029 : : *
8030 : : * levelsup is an extra offset to interpret the Var's varlevelsup correctly.
8031 : : *
8032 : : * This is fairly straightforward when the expression has a named composite
8033 : : * type; we need only look up the type in the catalogs. However, the type
8034 : : * could also be RECORD. Since no actual table or view column is allowed to
8035 : : * have type RECORD, a Var of type RECORD must refer to a JOIN or FUNCTION RTE
8036 : : * or to a subquery output. We drill down to find the ultimate defining
8037 : : * expression and attempt to infer the field name from it. We ereport if we
8038 : : * can't determine the name.
8039 : : *
8040 : : * Similarly, a PARAM of type RECORD has to refer to some expression of
8041 : : * a determinable composite type.
8042 : : */
8043 : : static const char *
7593 8044 : 865 : get_name_for_var_field(Var *var, int fieldno,
8045 : : int levelsup, deparse_context *context)
8046 : : {
8047 : : RangeTblEntry *rte;
8048 : : AttrNumber attnum;
8049 : : int netlevelsup;
8050 : : deparse_namespace *dpns;
8051 : : int varno;
8052 : : AttrNumber varattno;
8053 : : TupleDesc tupleDesc;
8054 : : Node *expr;
8055 : :
8056 : : /*
8057 : : * If it's a RowExpr that was expanded from a whole-row Var, use the
8058 : : * column names attached to it. (We could let get_expr_result_tupdesc()
8059 : : * handle this, but it's much cheaper to just pull out the name we need.)
8060 : : */
6369 8061 [ + + ]: 865 : if (IsA(var, RowExpr))
8062 : : {
6121 bruce@momjian.us 8063 : 18 : RowExpr *r = (RowExpr *) var;
8064 : :
6369 tgl@sss.pgh.pa.us 8065 [ + - + - ]: 18 : if (fieldno > 0 && fieldno <= list_length(r->colnames))
8066 : 18 : return strVal(list_nth(r->colnames, fieldno - 1));
8067 : : }
8068 : :
8069 : : /*
8070 : : * If it's a Param of type RECORD, try to find what the Param refers to.
8071 : : */
5303 8072 [ + + ]: 847 : if (IsA(var, Param))
8073 : : {
8074 : 9 : Param *param = (Param *) var;
8075 : : ListCell *ancestor_cell;
8076 : :
8077 : 9 : expr = find_param_referent(param, context, &dpns, &ancestor_cell);
8078 [ + - ]: 9 : if (expr)
8079 : : {
8080 : : /* Found a match, so recurse to decipher the field name */
8081 : : deparse_namespace save_dpns;
8082 : : const char *result;
8083 : :
8084 : 9 : push_ancestor_plan(dpns, ancestor_cell, &save_dpns);
8085 : 9 : result = get_name_for_var_field((Var *) expr, fieldno,
8086 : : 0, context);
8087 : 9 : pop_ancestor_plan(dpns, &save_dpns);
8088 : 9 : return result;
8089 : : }
8090 : : }
8091 : :
8092 : : /*
8093 : : * If it's a Var of type RECORD, we have to find what the Var refers to;
8094 : : * if not, we can use get_expr_result_tupdesc().
8095 : : */
6960 8096 [ + + ]: 838 : if (!IsA(var, Var) ||
8097 [ + + ]: 798 : var->vartype != RECORDOID)
8098 : : {
3062 8099 : 715 : tupleDesc = get_expr_result_tupdesc((Node *) var, false);
8100 : : /* Got the tupdesc, so we can extract the field name */
6960 8101 [ + - - + ]: 715 : Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
3129 andres@anarazel.de 8102 : 715 : return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname);
8103 : : }
8104 : :
8105 : : /* Find appropriate nesting depth */
6960 tgl@sss.pgh.pa.us 8106 : 123 : netlevelsup = var->varlevelsup + levelsup;
8107 [ - + ]: 123 : if (netlevelsup >= list_length(context->namespaces))
6960 tgl@sss.pgh.pa.us 8108 [ # # ]:UBC 0 : elog(ERROR, "bogus varlevelsup: %d offset %d",
8109 : : var->varlevelsup, levelsup);
6960 tgl@sss.pgh.pa.us 8110 :CBC 123 : dpns = (deparse_namespace *) list_nth(context->namespaces,
8111 : : netlevelsup);
8112 : :
8113 : : /*
8114 : : * If we have a syntactic referent for the Var, and we're working from a
8115 : : * parse tree, prefer to use the syntactic referent. Otherwise, fall back
8116 : : * on the semantic referent. (See comments in get_variable().)
8117 : : */
2257 8118 [ + + + + ]: 123 : if (var->varnosyn > 0 && dpns->plan == NULL)
8119 : : {
8120 : 48 : varno = var->varnosyn;
8121 : 48 : varattno = var->varattnosyn;
8122 : : }
8123 : : else
8124 : : {
8125 : 75 : varno = var->varno;
8126 : 75 : varattno = var->varattno;
8127 : : }
8128 : :
8129 : : /*
8130 : : * Try to find the relevant RTE in this rtable. In a plan tree, it's
8131 : : * likely that varno is OUTER_VAR or INNER_VAR, in which case we must dig
8132 : : * down into the subplans, or INDEX_VAR, which is resolved similarly.
8133 : : *
8134 : : * Note: unlike get_variable and resolve_special_varno, we need not worry
8135 : : * about inheritance mapping: a child Var should have the same datatype as
8136 : : * its parent, and here we're really only interested in the Var's type.
8137 : : */
8138 [ + + + - ]: 123 : if (varno >= 1 && varno <= list_length(dpns->rtable))
8139 : : {
8140 : 84 : rte = rt_fetch(varno, dpns->rtable);
8141 : 84 : attnum = varattno;
8142 : : }
8143 [ + + + - ]: 39 : else if (varno == OUTER_VAR && dpns->outer_tlist)
8144 : : {
8145 : : TargetEntry *tle;
8146 : : deparse_namespace save_dpns;
8147 : : const char *result;
8148 : :
8149 : 30 : tle = get_tle_by_resno(dpns->outer_tlist, varattno);
6960 8150 [ - + ]: 30 : if (!tle)
2257 tgl@sss.pgh.pa.us 8151 [ # # ]:UBC 0 : elog(ERROR, "bogus varattno for OUTER_VAR var: %d", varattno);
8152 : :
6960 tgl@sss.pgh.pa.us 8153 [ - + ]:CBC 30 : Assert(netlevelsup == 0);
2286 8154 : 30 : push_child_plan(dpns, dpns->outer_plan, &save_dpns);
8155 : :
6960 8156 : 30 : result = get_name_for_var_field((Var *) tle->expr, fieldno,
8157 : : levelsup, context);
8158 : :
5724 8159 : 30 : pop_child_plan(dpns, &save_dpns);
6960 8160 : 30 : return result;
8161 : : }
2257 8162 [ + - + - ]: 9 : else if (varno == INNER_VAR && dpns->inner_tlist)
8163 : : {
8164 : : TargetEntry *tle;
8165 : : deparse_namespace save_dpns;
8166 : : const char *result;
8167 : :
8168 : 9 : tle = get_tle_by_resno(dpns->inner_tlist, varattno);
6960 8169 [ - + ]: 9 : if (!tle)
2257 tgl@sss.pgh.pa.us 8170 [ # # ]:UBC 0 : elog(ERROR, "bogus varattno for INNER_VAR var: %d", varattno);
8171 : :
6960 tgl@sss.pgh.pa.us 8172 [ - + ]:CBC 9 : Assert(netlevelsup == 0);
2286 8173 : 9 : push_child_plan(dpns, dpns->inner_plan, &save_dpns);
8174 : :
6960 8175 : 9 : result = get_name_for_var_field((Var *) tle->expr, fieldno,
8176 : : levelsup, context);
8177 : :
5724 8178 : 9 : pop_child_plan(dpns, &save_dpns);
6960 8179 : 9 : return result;
8180 : : }
2257 tgl@sss.pgh.pa.us 8181 [ # # # # ]:UBC 0 : else if (varno == INDEX_VAR && dpns->index_tlist)
8182 : : {
8183 : : TargetEntry *tle;
8184 : : const char *result;
8185 : :
8186 : 0 : tle = get_tle_by_resno(dpns->index_tlist, varattno);
5269 8187 [ # # ]: 0 : if (!tle)
2257 8188 [ # # ]: 0 : elog(ERROR, "bogus varattno for INDEX_VAR var: %d", varattno);
8189 : :
5269 8190 [ # # ]: 0 : Assert(netlevelsup == 0);
8191 : :
8192 : 0 : result = get_name_for_var_field((Var *) tle->expr, fieldno,
8193 : : levelsup, context);
8194 : :
8195 : 0 : return result;
8196 : : }
8197 : : else
8198 : : {
2257 8199 [ # # ]: 0 : elog(ERROR, "bogus varno: %d", varno);
8200 : : return NULL; /* keep compiler quiet */
8201 : : }
8202 : :
7593 tgl@sss.pgh.pa.us 8203 [ + + ]:CBC 84 : if (attnum == InvalidAttrNumber)
8204 : : {
8205 : : /* Var is whole-row reference to RTE, so select the right field */
8206 : 12 : return get_rte_attribute_name(rte, fieldno);
8207 : : }
8208 : :
8209 : : /*
8210 : : * This part has essentially the same logic as the parser's
8211 : : * expandRecordVariable() function, but we are dealing with a different
8212 : : * representation of the input context, and we only need one field name
8213 : : * not a TupleDesc. Also, we need special cases for finding subquery and
8214 : : * CTE subplans when deparsing Plan trees.
8215 : : */
8216 : 72 : expr = (Node *) var; /* default if we can't drill down */
8217 : :
8218 [ - + - - : 72 : switch (rte->rtekind)
+ - - ]
8219 : : {
7593 tgl@sss.pgh.pa.us 8220 :UBC 0 : case RTE_RELATION:
8221 : : case RTE_VALUES:
8222 : : case RTE_NAMEDTUPLESTORE:
8223 : : case RTE_RESULT:
8224 : :
8225 : : /*
8226 : : * This case should not occur: a column of a table, values list,
8227 : : * or ENR shouldn't have type RECORD. Fall through and fail (most
8228 : : * likely) at the bottom.
8229 : : */
8230 : 0 : break;
7593 tgl@sss.pgh.pa.us 8231 :CBC 36 : case RTE_SUBQUERY:
8232 : : /* Subselect-in-FROM: examine sub-select's output expr */
8233 : : {
6960 8234 [ + + ]: 36 : if (rte->subquery)
8235 : : {
8236 : 21 : TargetEntry *ste = get_tle_by_resno(rte->subquery->targetList,
8237 : : attnum);
8238 : :
8239 [ + - - + ]: 21 : if (ste == NULL || ste->resjunk)
6960 tgl@sss.pgh.pa.us 8240 [ # # ]:UBC 0 : elog(ERROR, "subquery %s does not have attribute %d",
8241 : : rte->eref->aliasname, attnum);
6960 tgl@sss.pgh.pa.us 8242 :CBC 21 : expr = (Node *) ste->expr;
8243 [ + + ]: 21 : if (IsA(expr, Var))
8244 : : {
8245 : : /*
8246 : : * Recurse into the sub-select to see what its Var
8247 : : * refers to. We have to build an additional level of
8248 : : * namespace to keep in step with varlevelsup in the
8249 : : * subselect; furthermore, the subquery RTE might be
8250 : : * from an outer query level, in which case the
8251 : : * namespace for the subselect must have that outer
8252 : : * level as parent namespace.
8253 : : */
912 8254 : 9 : List *save_nslist = context->namespaces;
8255 : : List *parent_namespaces;
8256 : : deparse_namespace mydpns;
8257 : : const char *result;
8258 : :
8259 : 9 : parent_namespaces = list_copy_tail(context->namespaces,
8260 : : netlevelsup);
8261 : :
4822 8262 : 9 : set_deparse_for_query(&mydpns, rte->subquery,
8263 : : parent_namespaces);
8264 : :
912 8265 : 9 : context->namespaces = lcons(&mydpns, parent_namespaces);
8266 : :
6960 8267 : 9 : result = get_name_for_var_field((Var *) expr, fieldno,
8268 : : 0, context);
8269 : :
912 8270 : 9 : context->namespaces = save_nslist;
8271 : :
6960 8272 : 9 : return result;
8273 : : }
8274 : : /* else fall through to inspect the expression */
8275 : : }
8276 : : else
8277 : : {
8278 : : /*
8279 : : * We're deparsing a Plan tree so we don't have complete
8280 : : * RTE entries (in particular, rte->subquery is NULL). But
8281 : : * the only place we'd normally see a Var directly
8282 : : * referencing a SUBQUERY RTE is in a SubqueryScan plan
8283 : : * node, and we can look into the child plan's tlist
8284 : : * instead. An exception occurs if the subquery was
8285 : : * proven empty and optimized away: then we'd find such a
8286 : : * Var in a childless Result node, and there's nothing in
8287 : : * the plan tree that would let us figure out what it had
8288 : : * originally referenced. In that case, fall back on
8289 : : * printing "fN", analogously to the default column names
8290 : : * for RowExprs.
8291 : : */
8292 : : TargetEntry *tle;
8293 : : deparse_namespace save_dpns;
8294 : : const char *result;
8295 : :
2286 8296 [ + + ]: 15 : if (!dpns->inner_plan)
8297 : : {
583 8298 : 6 : char *dummy_name = palloc(32);
8299 : :
581 8300 [ + - - + ]: 6 : Assert(dpns->plan && IsA(dpns->plan, Result));
583 8301 : 6 : snprintf(dummy_name, 32, "f%d", fieldno);
8302 : 6 : return dummy_name;
8303 : : }
581 8304 [ + - - + ]: 9 : Assert(dpns->plan && IsA(dpns->plan, SubqueryScan));
8305 : :
5269 8306 : 9 : tle = get_tle_by_resno(dpns->inner_tlist, attnum);
6960 8307 [ - + ]: 9 : if (!tle)
6960 tgl@sss.pgh.pa.us 8308 [ # # ]:UBC 0 : elog(ERROR, "bogus varattno for subquery var: %d",
8309 : : attnum);
6960 tgl@sss.pgh.pa.us 8310 [ - + ]:CBC 9 : Assert(netlevelsup == 0);
2286 8311 : 9 : push_child_plan(dpns, dpns->inner_plan, &save_dpns);
8312 : :
6960 8313 : 9 : result = get_name_for_var_field((Var *) tle->expr, fieldno,
8314 : : levelsup, context);
8315 : :
5724 8316 : 9 : pop_child_plan(dpns, &save_dpns);
7593 8317 : 9 : return result;
8318 : : }
8319 : : }
8320 : 12 : break;
7593 tgl@sss.pgh.pa.us 8321 :UBC 0 : case RTE_JOIN:
8322 : : /* Join RTE --- recursively inspect the alias variable */
6870 8323 [ # # ]: 0 : if (rte->joinaliasvars == NIL)
8324 [ # # ]: 0 : elog(ERROR, "cannot decompile join alias var in plan tree");
7593 8325 [ # # # # ]: 0 : Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars));
8326 : 0 : expr = (Node *) list_nth(rte->joinaliasvars, attnum - 1);
4618 8327 [ # # ]: 0 : Assert(expr != NULL);
8328 : : /* we intentionally don't strip implicit coercions here */
7593 8329 [ # # ]: 0 : if (IsA(expr, Var))
8330 : 0 : return get_name_for_var_field((Var *) expr, fieldno,
8331 : 0 : var->varlevelsup + levelsup,
8332 : : context);
8333 : : /* else fall through to inspect the expression */
8334 : 0 : break;
8335 : 0 : case RTE_FUNCTION:
8336 : : case RTE_TABLEFUNC:
8337 : :
8338 : : /*
8339 : : * We couldn't get here unless a function is declared with one of
8340 : : * its result columns as RECORD, which is not allowed.
8341 : : */
8342 : 0 : break;
6371 tgl@sss.pgh.pa.us 8343 :CBC 36 : case RTE_CTE:
8344 : : /* CTE reference: examine subquery's output expr */
8345 : : {
6369 8346 : 36 : CommonTableExpr *cte = NULL;
8347 : : Index ctelevelsup;
8348 : : ListCell *lc;
8349 : :
8350 : : /*
8351 : : * Try to find the referenced CTE using the namespace stack.
8352 : : */
8353 : 36 : ctelevelsup = rte->ctelevelsup + netlevelsup;
8354 [ + + ]: 36 : if (ctelevelsup >= list_length(context->namespaces))
8355 : 6 : lc = NULL;
8356 : : else
8357 : : {
8358 : : deparse_namespace *ctedpns;
8359 : :
8360 : : ctedpns = (deparse_namespace *)
8361 : 30 : list_nth(context->namespaces, ctelevelsup);
8362 [ + + + - : 33 : foreach(lc, ctedpns->ctes)
+ + ]
8363 : : {
8364 : 18 : cte = (CommonTableExpr *) lfirst(lc);
8365 [ + + ]: 18 : if (strcmp(cte->ctename, rte->ctename) == 0)
8366 : 15 : break;
8367 : : }
8368 : : }
8369 [ + + ]: 36 : if (lc != NULL)
8370 : : {
8371 : 15 : Query *ctequery = (Query *) cte->ctequery;
5453 bruce@momjian.us 8372 [ - + + - ]: 15 : TargetEntry *ste = get_tle_by_resno(GetCTETargetList(cte),
8373 : : attnum);
8374 : :
6369 tgl@sss.pgh.pa.us 8375 [ + - - + ]: 15 : if (ste == NULL || ste->resjunk)
912 tgl@sss.pgh.pa.us 8376 [ # # ]:UBC 0 : elog(ERROR, "CTE %s does not have attribute %d",
8377 : : rte->eref->aliasname, attnum);
6369 tgl@sss.pgh.pa.us 8378 :CBC 15 : expr = (Node *) ste->expr;
8379 [ + + ]: 15 : if (IsA(expr, Var))
8380 : : {
8381 : : /*
8382 : : * Recurse into the CTE to see what its Var refers to.
8383 : : * We have to build an additional level of namespace
8384 : : * to keep in step with varlevelsup in the CTE;
8385 : : * furthermore it could be an outer CTE (compare
8386 : : * SUBQUERY case above).
8387 : : */
8388 : 9 : List *save_nslist = context->namespaces;
8389 : : List *parent_namespaces;
8390 : : deparse_namespace mydpns;
8391 : : const char *result;
8392 : :
912 8393 : 9 : parent_namespaces = list_copy_tail(context->namespaces,
8394 : : ctelevelsup);
8395 : :
4822 8396 : 9 : set_deparse_for_query(&mydpns, ctequery,
8397 : : parent_namespaces);
8398 : :
912 8399 : 9 : context->namespaces = lcons(&mydpns, parent_namespaces);
8400 : :
6369 8401 : 9 : result = get_name_for_var_field((Var *) expr, fieldno,
8402 : : 0, context);
8403 : :
8404 : 9 : context->namespaces = save_nslist;
8405 : :
8406 : 9 : return result;
8407 : : }
8408 : : /* else fall through to inspect the expression */
8409 : : }
8410 : : else
8411 : : {
8412 : : /*
8413 : : * We're deparsing a Plan tree so we don't have a CTE
8414 : : * list. But the only places we'd normally see a Var
8415 : : * directly referencing a CTE RTE are in CteScan or
8416 : : * WorkTableScan plan nodes. For those cases,
8417 : : * set_deparse_plan arranged for dpns->inner_plan to be
8418 : : * the plan node that emits the CTE or RecursiveUnion
8419 : : * result, and we can look at its tlist instead. As
8420 : : * above, this can fail if the CTE has been proven empty,
8421 : : * in which case fall back to "fN".
8422 : : */
8423 : : TargetEntry *tle;
8424 : : deparse_namespace save_dpns;
8425 : : const char *result;
8426 : :
2286 8427 [ + + ]: 21 : if (!dpns->inner_plan)
8428 : : {
583 8429 : 3 : char *dummy_name = palloc(32);
8430 : :
581 8431 [ + - - + ]: 3 : Assert(dpns->plan && IsA(dpns->plan, Result));
583 8432 : 3 : snprintf(dummy_name, 32, "f%d", fieldno);
8433 : 3 : return dummy_name;
8434 : : }
581 8435 [ + - + + : 18 : Assert(dpns->plan && (IsA(dpns->plan, CteScan) ||
- + ]
8436 : : IsA(dpns->plan, WorkTableScan)));
8437 : :
5269 8438 : 18 : tle = get_tle_by_resno(dpns->inner_tlist, attnum);
6369 8439 [ - + ]: 18 : if (!tle)
6369 tgl@sss.pgh.pa.us 8440 [ # # ]:UBC 0 : elog(ERROR, "bogus varattno for subquery var: %d",
8441 : : attnum);
6369 tgl@sss.pgh.pa.us 8442 [ - + ]:CBC 18 : Assert(netlevelsup == 0);
2286 8443 : 18 : push_child_plan(dpns, dpns->inner_plan, &save_dpns);
8444 : :
6369 8445 : 18 : result = get_name_for_var_field((Var *) tle->expr, fieldno,
8446 : : levelsup, context);
8447 : :
5724 8448 : 18 : pop_child_plan(dpns, &save_dpns);
6369 8449 : 18 : return result;
8450 : : }
8451 : : }
6371 8452 : 6 : break;
551 rguo@postgresql.org 8453 :UBC 0 : case RTE_GROUP:
8454 : :
8455 : : /*
8456 : : * We couldn't get here: any Vars that reference the RTE_GROUP RTE
8457 : : * should have been replaced with the underlying grouping
8458 : : * expressions.
8459 : : */
8460 : 0 : break;
8461 : : }
8462 : :
8463 : : /*
8464 : : * We now have an expression we can't expand any more, so see if
8465 : : * get_expr_result_tupdesc() can do anything with it.
8466 : : */
3062 tgl@sss.pgh.pa.us 8467 :CBC 18 : tupleDesc = get_expr_result_tupdesc(expr, false);
8468 : : /* Got the tupdesc, so we can extract the field name */
7593 8469 [ + - - + ]: 18 : Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
3129 andres@anarazel.de 8470 : 18 : return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname);
8471 : : }
8472 : :
8473 : : /*
8474 : : * Try to find the referenced expression for a PARAM_EXEC Param that might
8475 : : * reference a parameter supplied by an upper NestLoop or SubPlan plan node.
8476 : : *
8477 : : * If successful, return the expression and set *dpns_p and *ancestor_cell_p
8478 : : * appropriately for calling push_ancestor_plan(). If no referent can be
8479 : : * found, return NULL.
8480 : : */
8481 : : static Node *
5303 tgl@sss.pgh.pa.us 8482 : 3694 : find_param_referent(Param *param, deparse_context *context,
8483 : : deparse_namespace **dpns_p, ListCell **ancestor_cell_p)
8484 : : {
8485 : : /* Initialize output parameters to prevent compiler warnings */
8486 : 3694 : *dpns_p = NULL;
8487 : 3694 : *ancestor_cell_p = NULL;
8488 : :
8489 : : /*
8490 : : * If it's a PARAM_EXEC parameter, look for a matching NestLoopParam or
8491 : : * SubPlan argument. This will necessarily be in some ancestor of the
8492 : : * current expression's Plan node.
8493 : : */
5724 8494 [ + + ]: 3694 : if (param->paramkind == PARAM_EXEC)
8495 : : {
8496 : : deparse_namespace *dpns;
8497 : : Plan *child_plan;
8498 : : ListCell *lc;
8499 : :
8500 : 3248 : dpns = (deparse_namespace *) linitial(context->namespaces);
2286 8501 : 3248 : child_plan = dpns->plan;
8502 : :
5724 8503 [ + + + + : 5726 : foreach(lc, dpns->ancestors)
+ + ]
8504 : : {
2286 8505 : 4839 : Node *ancestor = (Node *) lfirst(lc);
8506 : : ListCell *lc2;
8507 : :
8508 : : /*
8509 : : * NestLoops transmit params to their inner child only.
8510 : : */
8511 [ + + ]: 4839 : if (IsA(ancestor, NestLoop) &&
1215 8512 [ + + ]: 2211 : child_plan == innerPlan(ancestor))
8513 : : {
2286 8514 : 2119 : NestLoop *nl = (NestLoop *) ancestor;
8515 : :
5724 8516 [ + + + + : 2620 : foreach(lc2, nl->nestParams)
+ + ]
8517 : : {
5453 bruce@momjian.us 8518 : 2531 : NestLoopParam *nlp = (NestLoopParam *) lfirst(lc2);
8519 : :
5724 tgl@sss.pgh.pa.us 8520 [ + + ]: 2531 : if (nlp->paramno == param->paramid)
8521 : : {
8522 : : /* Found a match, so return it */
5303 8523 : 2030 : *dpns_p = dpns;
8524 : 2030 : *ancestor_cell_p = lc;
8525 : 2030 : return (Node *) nlp->paramval;
8526 : : }
8527 : : }
8528 : : }
8529 : :
8530 : : /*
8531 : : * If ancestor is a SubPlan, check the arguments it provides.
8532 : : */
2286 8533 [ + + ]: 2809 : if (IsA(ancestor, SubPlan))
5724 8534 : 216 : {
2286 8535 : 547 : SubPlan *subplan = (SubPlan *) ancestor;
8536 : : ListCell *lc3;
8537 : : ListCell *lc4;
8538 : :
5724 8539 [ + + + + : 715 : forboth(lc3, subplan->parParam, lc4, subplan->args)
+ + + + +
+ + - +
+ ]
8540 : : {
5453 bruce@momjian.us 8541 : 499 : int paramid = lfirst_int(lc3);
8542 : 499 : Node *arg = (Node *) lfirst(lc4);
8543 : :
5724 tgl@sss.pgh.pa.us 8544 [ + + ]: 499 : if (paramid == param->paramid)
8545 : : {
8546 : : /*
8547 : : * Found a match, so return it. But, since Vars in
8548 : : * the arg are to be evaluated in the surrounding
8549 : : * context, we have to point to the next ancestor item
8550 : : * that is *not* a SubPlan.
8551 : : */
8552 : : ListCell *rest;
8553 : :
2286 8554 [ + - + - : 331 : for_each_cell(rest, dpns->ancestors,
+ - ]
8555 : : lnext(dpns->ancestors, lc))
8556 : : {
8557 : 331 : Node *ancestor2 = (Node *) lfirst(rest);
8558 : :
8559 [ + - ]: 331 : if (!IsA(ancestor2, SubPlan))
8560 : : {
8561 : 331 : *dpns_p = dpns;
8562 : 331 : *ancestor_cell_p = rest;
8563 : 331 : return arg;
8564 : : }
8565 : : }
2286 tgl@sss.pgh.pa.us 8566 [ # # ]:UBC 0 : elog(ERROR, "SubPlan cannot be outermost ancestor");
8567 : : }
8568 : : }
8569 : :
8570 : : /* SubPlan isn't a kind of Plan, so skip the rest */
2286 tgl@sss.pgh.pa.us 8571 :CBC 216 : continue;
8572 : : }
8573 : :
8574 : : /*
8575 : : * We need not consider the ancestor's initPlan list, since
8576 : : * initplans never have any parParams.
8577 : : */
8578 : :
8579 : : /* No luck, crawl up to next ancestor */
8580 : 2262 : child_plan = (Plan *) ancestor;
8581 : : }
8582 : : }
8583 : :
8584 : : /* No referent found */
5303 8585 : 1333 : return NULL;
8586 : : }
8587 : :
8588 : : /*
8589 : : * Try to find a subplan/initplan that emits the value for a PARAM_EXEC Param.
8590 : : *
8591 : : * If successful, return the generating subplan/initplan and set *column_p
8592 : : * to the subplan's 0-based output column number.
8593 : : * Otherwise, return NULL.
8594 : : */
8595 : : static SubPlan *
726 8596 : 1333 : find_param_generator(Param *param, deparse_context *context, int *column_p)
8597 : : {
8598 : : /* Initialize output parameter to prevent compiler warnings */
8599 : 1333 : *column_p = 0;
8600 : :
8601 : : /*
8602 : : * If it's a PARAM_EXEC parameter, search the current plan node as well as
8603 : : * ancestor nodes looking for a subplan or initplan that emits the value
8604 : : * for the Param. It could appear in the setParams of an initplan or
8605 : : * MULTIEXPR_SUBLINK subplan, or in the paramIds of an ancestral SubPlan.
8606 : : */
8607 [ + + ]: 1333 : if (param->paramkind == PARAM_EXEC)
8608 : : {
8609 : : SubPlan *result;
8610 : : deparse_namespace *dpns;
8611 : : ListCell *lc;
8612 : :
8613 : 887 : dpns = (deparse_namespace *) linitial(context->namespaces);
8614 : :
8615 : : /* First check the innermost plan node's initplans */
8616 : 887 : result = find_param_generator_initplan(param, dpns->plan, column_p);
8617 [ + + ]: 887 : if (result)
8618 : 264 : return result;
8619 : :
8620 : : /*
8621 : : * The plan's targetlist might contain MULTIEXPR_SUBLINK SubPlans,
8622 : : * which can be referenced by Params elsewhere in the targetlist.
8623 : : * (Such Params should always be in the same targetlist, so there's no
8624 : : * need to do this work at upper plan nodes.)
8625 : : */
8626 [ + + + + : 3164 : foreach_node(TargetEntry, tle, dpns->plan->targetlist)
+ + ]
8627 : : {
8628 [ + - + + ]: 1970 : if (tle->expr && IsA(tle->expr, SubPlan))
8629 : : {
8630 : 50 : SubPlan *subplan = (SubPlan *) tle->expr;
8631 : :
8632 [ + + ]: 50 : if (subplan->subLinkType == MULTIEXPR_SUBLINK)
8633 : : {
8634 [ + - + - : 39 : foreach_int(paramid, subplan->setParam)
+ - ]
8635 : : {
8636 [ + + ]: 39 : if (paramid == param->paramid)
8637 : : {
8638 : : /* Found a match, so return it. */
8639 : 26 : *column_p = foreach_current_index(paramid);
8640 : 26 : return subplan;
8641 : : }
8642 : : }
8643 : : }
8644 : : }
8645 : : }
8646 : :
8647 : : /* No luck, so check the ancestor nodes */
8648 [ + - + - : 774 : foreach(lc, dpns->ancestors)
+ - ]
8649 : : {
8650 : 774 : Node *ancestor = (Node *) lfirst(lc);
8651 : :
8652 : : /*
8653 : : * If ancestor is a SubPlan, check the paramIds it provides.
8654 : : */
8655 [ + + ]: 774 : if (IsA(ancestor, SubPlan))
726 tgl@sss.pgh.pa.us 8656 :UBC 0 : {
726 tgl@sss.pgh.pa.us 8657 :CBC 144 : SubPlan *subplan = (SubPlan *) ancestor;
8658 : :
8659 [ + - + - : 163 : foreach_int(paramid, subplan->paramIds)
+ - ]
8660 : : {
8661 [ + + ]: 163 : if (paramid == param->paramid)
8662 : : {
8663 : : /* Found a match, so return it. */
8664 : 144 : *column_p = foreach_current_index(paramid);
8665 : 144 : return subplan;
8666 : : }
8667 : : }
8668 : :
8669 : : /* SubPlan isn't a kind of Plan, so skip the rest */
726 tgl@sss.pgh.pa.us 8670 :UBC 0 : continue;
8671 : : }
8672 : :
8673 : : /*
8674 : : * Otherwise, it's some kind of Plan node, so check its initplans.
8675 : : */
726 tgl@sss.pgh.pa.us 8676 :CBC 630 : result = find_param_generator_initplan(param, (Plan *) ancestor,
8677 : : column_p);
8678 [ + + ]: 630 : if (result)
8679 : 453 : return result;
8680 : :
8681 : : /* No luck, crawl up to next ancestor */
8682 : : }
8683 : : }
8684 : :
8685 : : /* No generator found */
8686 : 446 : return NULL;
8687 : : }
8688 : :
8689 : : /*
8690 : : * Subroutine for find_param_generator: search one Plan node's initplans
8691 : : */
8692 : : static SubPlan *
8693 : 1517 : find_param_generator_initplan(Param *param, Plan *plan, int *column_p)
8694 : : {
8695 [ + + + - : 2389 : foreach_node(SubPlan, subplan, plan->initPlan)
+ + ]
8696 : : {
8697 [ + - + + : 936 : foreach_int(paramid, subplan->setParam)
+ + ]
8698 : : {
8699 [ + + ]: 792 : if (paramid == param->paramid)
8700 : : {
8701 : : /* Found a match, so return it. */
8702 : 717 : *column_p = foreach_current_index(paramid);
8703 : 717 : return subplan;
8704 : : }
8705 : : }
8706 : : }
8707 : 800 : return NULL;
8708 : : }
8709 : :
8710 : : /*
8711 : : * Display a Param appropriately.
8712 : : */
8713 : : static void
5303 8714 : 3685 : get_parameter(Param *param, deparse_context *context)
8715 : : {
8716 : : Node *expr;
8717 : : deparse_namespace *dpns;
8718 : : ListCell *ancestor_cell;
8719 : : SubPlan *subplan;
8720 : : int column;
8721 : :
8722 : : /*
8723 : : * If it's a PARAM_EXEC parameter, try to locate the expression from which
8724 : : * the parameter was computed. This stanza handles only cases in which
8725 : : * the Param represents an input to the subplan we are currently in.
8726 : : */
8727 : 3685 : expr = find_param_referent(param, context, &dpns, &ancestor_cell);
8728 [ + + ]: 3685 : if (expr)
8729 : : {
8730 : : /* Found a match, so print it */
8731 : : deparse_namespace save_dpns;
8732 : : bool save_varprefix;
8733 : : bool need_paren;
8734 : :
8735 : : /* Switch attention to the ancestor plan node */
8736 : 2352 : push_ancestor_plan(dpns, ancestor_cell, &save_dpns);
8737 : :
8738 : : /*
8739 : : * Force prefixing of Vars, since they won't belong to the relation
8740 : : * being scanned in the original plan node.
8741 : : */
8742 : 2352 : save_varprefix = context->varprefix;
8743 : 2352 : context->varprefix = true;
8744 : :
8745 : : /*
8746 : : * A Param's expansion is typically a Var, Aggref, GroupingFunc, or
8747 : : * upper-level Param, which wouldn't need extra parentheses.
8748 : : * Otherwise, insert parens to ensure the expression looks atomic.
8749 : : */
8750 [ + + ]: 2364 : need_paren = !(IsA(expr, Var) ||
8751 [ + + ]: 12 : IsA(expr, Aggref) ||
1455 8752 [ + + ]: 9 : IsA(expr, GroupingFunc) ||
5303 8753 [ - + ]: 6 : IsA(expr, Param));
8754 [ - + ]: 2352 : if (need_paren)
5303 tgl@sss.pgh.pa.us 8755 :UBC 0 : appendStringInfoChar(context->buf, '(');
8756 : :
5303 tgl@sss.pgh.pa.us 8757 :CBC 2352 : get_rule_expr(expr, context, false);
8758 : :
8759 [ - + ]: 2352 : if (need_paren)
5303 tgl@sss.pgh.pa.us 8760 :UBC 0 : appendStringInfoChar(context->buf, ')');
8761 : :
5303 tgl@sss.pgh.pa.us 8762 :CBC 2352 : context->varprefix = save_varprefix;
8763 : :
8764 : 2352 : pop_ancestor_plan(dpns, &save_dpns);
8765 : :
8766 : 2352 : return;
8767 : : }
8768 : :
8769 : : /*
8770 : : * Alternatively, maybe it's a subplan output, which we print as a
8771 : : * reference to the subplan. (We could drill down into the subplan and
8772 : : * print the relevant targetlist expression, but that has been deemed too
8773 : : * confusing since it would violate normal SQL scope rules. Also, we're
8774 : : * relying on this reference to show that the testexpr containing the
8775 : : * Param has anything to do with that subplan at all.)
8776 : : */
726 8777 : 1333 : subplan = find_param_generator(param, context, &column);
8778 [ + + ]: 1333 : if (subplan)
8779 : : {
8780 : : const char *nameprefix;
8781 : :
159 rhaas@postgresql.org 8782 [ + + ]:GNC 887 : if (subplan->isInitPlan)
8783 : 717 : nameprefix = "InitPlan ";
8784 : : else
8785 : 170 : nameprefix = "SubPlan ";
8786 : :
8787 : 887 : appendStringInfo(context->buf, "(%s%s%s).col%d",
726 tgl@sss.pgh.pa.us 8788 [ + + ]:CBC 887 : subplan->useHashTable ? "hashed " : "",
8789 : : nameprefix,
8790 : : subplan->plan_name, column + 1);
8791 : :
8792 : 887 : return;
8793 : : }
8794 : :
8795 : : /*
8796 : : * If it's an external parameter, see if the outermost namespace provides
8797 : : * function argument names.
8798 : : */
1579 8799 [ + - + - ]: 446 : if (param->paramkind == PARAM_EXTERN && context->namespaces != NIL)
8800 : : {
8801 : 446 : dpns = llast(context->namespaces);
8802 [ + + ]: 446 : if (dpns->argnames &&
8803 [ + - ]: 34 : param->paramid > 0 &&
8804 [ + - ]: 34 : param->paramid <= dpns->numargs)
8805 : : {
1803 peter@eisentraut.org 8806 : 34 : char *argname = dpns->argnames[param->paramid - 1];
8807 : :
8808 [ + - ]: 34 : if (argname)
8809 : : {
8810 : 34 : bool should_qualify = false;
8811 : : ListCell *lc;
8812 : :
8813 : : /*
8814 : : * Qualify the parameter name if there are any other deparse
8815 : : * namespaces with range tables. This avoids qualifying in
8816 : : * trivial cases like "RETURN a + b", but makes it safe in all
8817 : : * other cases.
8818 : : */
8819 [ + - + + : 78 : foreach(lc, context->namespaces)
+ + ]
8820 : : {
1257 drowley@postgresql.o 8821 : 59 : deparse_namespace *depns = lfirst(lc);
8822 : :
8823 [ + + ]: 59 : if (depns->rtable_names != NIL)
8824 : : {
1803 peter@eisentraut.org 8825 : 15 : should_qualify = true;
8826 : 15 : break;
8827 : : }
8828 : : }
8829 [ + + ]: 34 : if (should_qualify)
8830 : : {
8831 : 15 : appendStringInfoString(context->buf, quote_identifier(dpns->funcname));
8832 : 15 : appendStringInfoChar(context->buf, '.');
8833 : : }
8834 : :
8835 : 34 : appendStringInfoString(context->buf, quote_identifier(argname));
8836 : 34 : return;
8837 : : }
8838 : : }
8839 : : }
8840 : :
8841 : : /*
8842 : : * Not PARAM_EXEC, or couldn't find referent: just print $N.
8843 : : *
8844 : : * It's a bug if we get here for anything except PARAM_EXTERN Params, but
8845 : : * in production builds printing $N seems more useful than failing.
8846 : : */
726 tgl@sss.pgh.pa.us 8847 [ - + ]: 412 : Assert(param->paramkind == PARAM_EXTERN);
8848 : :
5303 8849 : 412 : appendStringInfo(context->buf, "$%d", param->paramid);
8850 : : }
8851 : :
8852 : : /*
8853 : : * get_simple_binary_op_name
8854 : : *
8855 : : * helper function for isSimpleNode
8856 : : * will return single char binary operator name, or NULL if it's not
8857 : : */
8858 : : static const char *
8255 bruce@momjian.us 8859 : 75 : get_simple_binary_op_name(OpExpr *expr)
8860 : : {
8264 tgl@sss.pgh.pa.us 8861 : 75 : List *args = expr->args;
8862 : :
7959 neilc@samurai.com 8863 [ + - ]: 75 : if (list_length(args) == 2)
8864 : : {
8865 : : /* binary operator */
7963 8866 : 75 : Node *arg1 = (Node *) linitial(args);
8264 tgl@sss.pgh.pa.us 8867 : 75 : Node *arg2 = (Node *) lsecond(args);
8868 : : const char *op;
8869 : :
8870 : 75 : op = generate_operator_name(expr->opno, exprType(arg1), exprType(arg2));
8871 [ + - ]: 75 : if (strlen(op) == 1)
8259 bruce@momjian.us 8872 : 75 : return op;
8873 : : }
8264 tgl@sss.pgh.pa.us 8874 :UBC 0 : return NULL;
8875 : : }
8876 : :
8877 : :
8878 : : /*
8879 : : * isSimpleNode - check if given node is simple (doesn't need parenthesizing)
8880 : : *
8881 : : * true : simple in the context of parent node's type
8882 : : * false : not simple
8883 : : */
8884 : : static bool
8264 tgl@sss.pgh.pa.us 8885 :CBC 2953 : isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
8886 : : {
8259 bruce@momjian.us 8887 [ - + ]: 2953 : if (!node)
8264 tgl@sss.pgh.pa.us 8888 :UBC 0 : return false;
8889 : :
8259 bruce@momjian.us 8890 [ + + - + :CBC 2953 : switch (nodeTag(node))
- - + - -
- - + + +
- + ]
8891 : : {
8264 tgl@sss.pgh.pa.us 8892 : 2501 : case T_Var:
8893 : : case T_Const:
8894 : : case T_Param:
8895 : : case T_CoerceToDomainValue:
8896 : : case T_SetToDefault:
8897 : : case T_CurrentOfExpr:
8898 : : /* single words: always simple */
8899 : 2501 : return true;
8900 : :
2599 alvherre@alvh.no-ip. 8901 : 248 : case T_SubscriptingRef:
8902 : : case T_ArrayExpr:
8903 : : case T_RowExpr:
8904 : : case T_CoalesceExpr:
8905 : : case T_MinMaxExpr:
8906 : : case T_SQLValueFunction:
8907 : : case T_XmlExpr:
8908 : : case T_NextValueExpr:
8909 : : case T_NullIfExpr:
8910 : : case T_Aggref:
8911 : : case T_GroupingFunc:
8912 : : case T_WindowFunc:
8913 : : case T_MergeSupportFunc:
8914 : : case T_FuncExpr:
8915 : : case T_JsonConstructorExpr:
8916 : : case T_JsonExpr:
8917 : : /* function-like: name(..) or name[..] */
8264 tgl@sss.pgh.pa.us 8918 : 248 : return true;
8919 : :
8920 : : /* CASE keywords act as parentheses */
8264 tgl@sss.pgh.pa.us 8921 :UBC 0 : case T_CaseExpr:
8922 : 0 : return true;
8923 : :
8264 tgl@sss.pgh.pa.us 8924 :CBC 36 : case T_FieldSelect:
8925 : :
8926 : : /*
8927 : : * appears simple since . has top precedence, unless parent is
8928 : : * T_FieldSelect itself!
8929 : : */
1649 michael@paquier.xyz 8930 : 36 : return !IsA(parentNode, FieldSelect);
8931 : :
7949 tgl@sss.pgh.pa.us 8932 :UBC 0 : case T_FieldStore:
8933 : :
8934 : : /*
8935 : : * treat like FieldSelect (probably doesn't matter)
8936 : : */
1649 michael@paquier.xyz 8937 : 0 : return !IsA(parentNode, FieldStore);
8938 : :
8264 tgl@sss.pgh.pa.us 8939 : 0 : case T_CoerceToDomain:
8940 : : /* maybe simple, check args */
8259 bruce@momjian.us 8941 : 0 : return isSimpleNode((Node *) ((CoerceToDomain *) node)->arg,
8942 : : node, prettyFlags);
8264 tgl@sss.pgh.pa.us 8943 :CBC 9 : case T_RelabelType:
8259 bruce@momjian.us 8944 : 9 : return isSimpleNode((Node *) ((RelabelType *) node)->arg,
8945 : : node, prettyFlags);
6858 tgl@sss.pgh.pa.us 8946 :UBC 0 : case T_CoerceViaIO:
8947 : 0 : return isSimpleNode((Node *) ((CoerceViaIO *) node)->arg,
8948 : : node, prettyFlags);
6928 8949 : 0 : case T_ArrayCoerceExpr:
8950 : 0 : return isSimpleNode((Node *) ((ArrayCoerceExpr *) node)->arg,
8951 : : node, prettyFlags);
7764 8952 : 0 : case T_ConvertRowtypeExpr:
8953 : 0 : return isSimpleNode((Node *) ((ConvertRowtypeExpr *) node)->arg,
8954 : : node, prettyFlags);
423 dean.a.rasheed@gmail 8955 : 0 : case T_ReturningExpr:
8956 : 0 : return isSimpleNode((Node *) ((ReturningExpr *) node)->retexpr,
8957 : : node, prettyFlags);
8958 : :
8264 tgl@sss.pgh.pa.us 8959 :CBC 138 : case T_OpExpr:
8960 : : {
8961 : : /* depends on parent node type; needs further checking */
8259 bruce@momjian.us 8962 [ + - + + ]: 138 : if (prettyFlags & PRETTYFLAG_PAREN && IsA(parentNode, OpExpr))
8963 : : {
8964 : : const char *op;
8965 : : const char *parentOp;
8966 : : bool is_lopriop;
8967 : : bool is_hipriop;
8968 : : bool is_lopriparent;
8969 : : bool is_hipriparent;
8970 : :
8971 : 39 : op = get_simple_binary_op_name((OpExpr *) node);
8972 [ - + ]: 39 : if (!op)
8259 bruce@momjian.us 8973 :UBC 0 : return false;
8974 : :
8975 : : /* We know only the basic operators + - and * / % */
8259 bruce@momjian.us 8976 :CBC 39 : is_lopriop = (strchr("+-", *op) != NULL);
8977 : 39 : is_hipriop = (strchr("*/%", *op) != NULL);
8978 [ + + + + ]: 39 : if (!(is_lopriop || is_hipriop))
8979 : 3 : return false;
8980 : :
8981 : 36 : parentOp = get_simple_binary_op_name((OpExpr *) parentNode);
8982 [ - + ]: 36 : if (!parentOp)
8259 bruce@momjian.us 8983 :UBC 0 : return false;
8984 : :
8259 bruce@momjian.us 8985 :CBC 36 : is_lopriparent = (strchr("+-", *parentOp) != NULL);
8986 : 36 : is_hipriparent = (strchr("*/%", *parentOp) != NULL);
8987 [ + + - + ]: 36 : if (!(is_lopriparent || is_hipriparent))
8259 bruce@momjian.us 8988 :UBC 0 : return false;
8989 : :
8259 bruce@momjian.us 8990 [ + + + - ]:CBC 36 : if (is_hipriop && is_lopriparent)
8991 : 6 : return true; /* op binds tighter than parent */
8992 : :
8993 [ + - + + ]: 30 : if (is_lopriop && is_hipriparent)
8994 : 24 : return false;
8995 : :
8996 : : /*
8997 : : * Operators are same priority --- can skip parens only if
8998 : : * we have (a - b) - c, not a - (b - c).
8999 : : */
7963 neilc@samurai.com 9000 [ + + ]: 6 : if (node == (Node *) linitial(((OpExpr *) parentNode)->args))
8259 bruce@momjian.us 9001 : 3 : return true;
9002 : :
9003 : 3 : return false;
9004 : : }
9005 : : /* else do the same stuff as for T_SubLink et al. */
9006 : : }
9007 : : pg_fallthrough;
9008 : :
9009 : : case T_SubLink:
9010 : : case T_NullTest:
9011 : : case T_BooleanTest:
9012 : : case T_DistinctExpr:
9013 : : case T_JsonIsPredicate:
8264 tgl@sss.pgh.pa.us 9014 [ + + + ]: 108 : switch (nodeTag(parentNode))
9015 : : {
9016 : 30 : case T_FuncExpr:
9017 : : {
9018 : : /* special handling for casts and COERCE_SQL_SYNTAX */
8259 bruce@momjian.us 9019 : 30 : CoercionForm type = ((FuncExpr *) parentNode)->funcformat;
9020 : :
9021 [ + + + + ]: 30 : if (type == COERCE_EXPLICIT_CAST ||
1200 tgl@sss.pgh.pa.us 9022 [ + - ]: 3 : type == COERCE_IMPLICIT_CAST ||
9023 : : type == COERCE_SQL_SYNTAX)
8259 bruce@momjian.us 9024 : 30 : return false;
8259 bruce@momjian.us 9025 :UBC 0 : return true; /* own parentheses */
9026 : : }
3189 tgl@sss.pgh.pa.us 9027 :CBC 63 : case T_BoolExpr: /* lower precedence */
9028 : : case T_SubscriptingRef: /* other separators */
9029 : : case T_ArrayExpr: /* other separators */
9030 : : case T_RowExpr: /* other separators */
9031 : : case T_CoalesceExpr: /* own parentheses */
9032 : : case T_MinMaxExpr: /* own parentheses */
9033 : : case T_XmlExpr: /* own parentheses */
9034 : : case T_NullIfExpr: /* other separators */
9035 : : case T_Aggref: /* own parentheses */
9036 : : case T_GroupingFunc: /* own parentheses */
9037 : : case T_WindowFunc: /* own parentheses */
9038 : : case T_CaseExpr: /* other separators */
8264 9039 : 63 : return true;
9040 : 15 : default:
9041 : 15 : return false;
9042 : : }
9043 : :
9044 : 9 : case T_BoolExpr:
9045 [ + - - - ]: 9 : switch (nodeTag(parentNode))
9046 : : {
9047 : 9 : case T_BoolExpr:
9048 [ + - ]: 9 : if (prettyFlags & PRETTYFLAG_PAREN)
9049 : : {
9050 : : BoolExprType type;
9051 : : BoolExprType parentType;
9052 : :
8259 bruce@momjian.us 9053 : 9 : type = ((BoolExpr *) node)->boolop;
9054 : 9 : parentType = ((BoolExpr *) parentNode)->boolop;
8264 tgl@sss.pgh.pa.us 9055 [ + + - ]: 9 : switch (type)
9056 : : {
9057 : 6 : case NOT_EXPR:
9058 : : case AND_EXPR:
9059 [ + + + - ]: 6 : if (parentType == AND_EXPR || parentType == OR_EXPR)
9060 : 6 : return true;
8264 tgl@sss.pgh.pa.us 9061 :UBC 0 : break;
8264 tgl@sss.pgh.pa.us 9062 :CBC 3 : case OR_EXPR:
9063 [ - + ]: 3 : if (parentType == OR_EXPR)
8264 tgl@sss.pgh.pa.us 9064 :UBC 0 : return true;
8264 tgl@sss.pgh.pa.us 9065 :CBC 3 : break;
9066 : : }
9067 : : }
9068 : 3 : return false;
8264 tgl@sss.pgh.pa.us 9069 :UBC 0 : case T_FuncExpr:
9070 : : {
9071 : : /* special handling for casts and COERCE_SQL_SYNTAX */
8259 bruce@momjian.us 9072 : 0 : CoercionForm type = ((FuncExpr *) parentNode)->funcformat;
9073 : :
9074 [ # # # # ]: 0 : if (type == COERCE_EXPLICIT_CAST ||
1200 tgl@sss.pgh.pa.us 9075 [ # # ]: 0 : type == COERCE_IMPLICIT_CAST ||
9076 : : type == COERCE_SQL_SYNTAX)
8259 bruce@momjian.us 9077 : 0 : return false;
9078 : 0 : return true; /* own parentheses */
9079 : : }
2599 alvherre@alvh.no-ip. 9080 : 0 : case T_SubscriptingRef: /* other separators */
9081 : : case T_ArrayExpr: /* other separators */
9082 : : case T_RowExpr: /* other separators */
9083 : : case T_CoalesceExpr: /* own parentheses */
9084 : : case T_MinMaxExpr: /* own parentheses */
9085 : : case T_XmlExpr: /* own parentheses */
9086 : : case T_NullIfExpr: /* other separators */
9087 : : case T_Aggref: /* own parentheses */
9088 : : case T_GroupingFunc: /* own parentheses */
9089 : : case T_WindowFunc: /* own parentheses */
9090 : : case T_CaseExpr: /* other separators */
9091 : : case T_JsonExpr: /* own parentheses */
8264 tgl@sss.pgh.pa.us 9092 : 0 : return true;
9093 : 0 : default:
9094 : 0 : return false;
9095 : : }
9096 : :
1082 alvherre@alvh.no-ip. 9097 : 0 : case T_JsonValueExpr:
9098 : : /* maybe simple, check args */
9099 : 0 : return isSimpleNode((Node *) ((JsonValueExpr *) node)->raw_expr,
9100 : : node, prettyFlags);
9101 : :
8264 tgl@sss.pgh.pa.us 9102 :CBC 3 : default:
9103 : 3 : break;
9104 : : }
9105 : : /* those we don't know: in dubio complexo */
8259 bruce@momjian.us 9106 : 3 : return false;
9107 : : }
9108 : :
9109 : :
9110 : : /*
9111 : : * appendContextKeyword - append a keyword to buffer
9112 : : *
9113 : : * If prettyPrint is enabled, perform a line break, and adjust indentation.
9114 : : * Otherwise, just append the keyword.
9115 : : */
9116 : : static void
8264 tgl@sss.pgh.pa.us 9117 : 15528 : appendContextKeyword(deparse_context *context, const char *str,
9118 : : int indentBefore, int indentAfter, int indentPlus)
9119 : : {
4507 9120 : 15528 : StringInfo buf = context->buf;
9121 : :
8259 bruce@momjian.us 9122 [ + + ]: 15528 : if (PRETTY_INDENT(context))
9123 : : {
9124 : : int indentAmount;
9125 : :
8264 tgl@sss.pgh.pa.us 9126 : 15046 : context->indentLevel += indentBefore;
9127 : :
9128 : : /* remove any trailing spaces currently in the buffer ... */
4507 9129 : 15046 : removeStringInfoSpaces(buf);
9130 : : /* ... then add a newline and some spaces */
9131 : 15046 : appendStringInfoChar(buf, '\n');
9132 : :
4337 9133 [ + - ]: 15046 : if (context->indentLevel < PRETTYINDENT_LIMIT)
9134 : 15046 : indentAmount = Max(context->indentLevel, 0) + indentPlus;
9135 : : else
9136 : : {
9137 : : /*
9138 : : * If we're indented more than PRETTYINDENT_LIMIT characters, try
9139 : : * to conserve horizontal space by reducing the per-level
9140 : : * indentation. For best results the scale factor here should
9141 : : * divide all the indent amounts that get added to indentLevel
9142 : : * (PRETTYINDENT_STD, etc). It's important that the indentation
9143 : : * not grow unboundedly, else deeply-nested trees use O(N^2)
9144 : : * whitespace; so we also wrap modulo PRETTYINDENT_LIMIT.
9145 : : */
4337 tgl@sss.pgh.pa.us 9146 :UBC 0 : indentAmount = PRETTYINDENT_LIMIT +
9147 : 0 : (context->indentLevel - PRETTYINDENT_LIMIT) /
9148 : : (PRETTYINDENT_STD / 2);
9149 : 0 : indentAmount %= PRETTYINDENT_LIMIT;
9150 : : /* scale/wrap logic affects indentLevel, but not indentPlus */
9151 : 0 : indentAmount += indentPlus;
9152 : : }
4337 tgl@sss.pgh.pa.us 9153 :CBC 15046 : appendStringInfoSpaces(buf, indentAmount);
9154 : :
4507 9155 : 15046 : appendStringInfoString(buf, str);
9156 : :
8264 9157 : 15046 : context->indentLevel += indentAfter;
9158 [ - + ]: 15046 : if (context->indentLevel < 0)
8264 tgl@sss.pgh.pa.us 9159 :UBC 0 : context->indentLevel = 0;
9160 : : }
9161 : : else
4507 tgl@sss.pgh.pa.us 9162 :CBC 482 : appendStringInfoString(buf, str);
8264 9163 : 15528 : }
9164 : :
9165 : : /*
9166 : : * removeStringInfoSpaces - delete trailing spaces from a buffer.
9167 : : *
9168 : : * Possibly this should move to stringinfo.c at some point.
9169 : : */
9170 : : static void
4507 9171 : 15325 : removeStringInfoSpaces(StringInfo str)
9172 : : {
9173 [ + + + + ]: 23964 : while (str->len > 0 && str->data[str->len - 1] == ' ')
9174 : 8639 : str->data[--(str->len)] = '\0';
9175 : 15325 : }
9176 : :
9177 : :
9178 : : /*
9179 : : * get_rule_expr_paren - deparse expr using get_rule_expr,
9180 : : * embracing the string with parentheses if necessary for prettyPrint.
9181 : : *
9182 : : * Never embrace if prettyFlags=0, because it's done in the calling node.
9183 : : *
9184 : : * Any node that does *not* embrace its argument node by sql syntax (with
9185 : : * parentheses, non-operator keywords like CASE/WHEN/ON, or comma etc) should
9186 : : * use get_rule_expr_paren instead of get_rule_expr so parentheses can be
9187 : : * added.
9188 : : */
9189 : : static void
8259 bruce@momjian.us 9190 : 84191 : get_rule_expr_paren(Node *node, deparse_context *context,
9191 : : bool showimplicit, Node *parentNode)
9192 : : {
9193 : : bool need_paren;
9194 : :
8264 tgl@sss.pgh.pa.us 9195 [ + + ]: 87135 : need_paren = PRETTY_PAREN(context) &&
9196 [ + + ]: 2944 : !isSimpleNode(node, parentNode, context->prettyFlags);
9197 : :
9198 [ + + ]: 84191 : if (need_paren)
8259 bruce@momjian.us 9199 : 81 : appendStringInfoChar(context->buf, '(');
9200 : :
8264 tgl@sss.pgh.pa.us 9201 : 84191 : get_rule_expr(node, context, showimplicit);
9202 : :
9203 [ + + ]: 84191 : if (need_paren)
8259 bruce@momjian.us 9204 : 81 : appendStringInfoChar(context->buf, ')');
8264 tgl@sss.pgh.pa.us 9205 : 84191 : }
9206 : :
9207 : : static void
724 amitlan@postgresql.o 9208 : 42 : get_json_behavior(JsonBehavior *behavior, deparse_context *context,
9209 : : const char *on)
9210 : : {
9211 : : /*
9212 : : * The order of array elements must correspond to the order of
9213 : : * JsonBehaviorType members.
9214 : : */
9215 : 42 : const char *behavior_names[] =
9216 : : {
9217 : : " NULL",
9218 : : " ERROR",
9219 : : " EMPTY",
9220 : : " TRUE",
9221 : : " FALSE",
9222 : : " UNKNOWN",
9223 : : " EMPTY ARRAY",
9224 : : " EMPTY OBJECT",
9225 : : " DEFAULT "
9226 : : };
9227 : :
9228 [ + - - + ]: 42 : if ((int) behavior->btype < 0 || behavior->btype >= lengthof(behavior_names))
724 amitlan@postgresql.o 9229 [ # # ]:UBC 0 : elog(ERROR, "invalid json behavior type: %d", behavior->btype);
9230 : :
724 amitlan@postgresql.o 9231 :CBC 42 : appendStringInfoString(context->buf, behavior_names[behavior->btype]);
9232 : :
9233 [ + + ]: 42 : if (behavior->btype == JSON_BEHAVIOR_DEFAULT)
9234 : 9 : get_rule_expr(behavior->expr, context, false);
9235 : :
9236 : 42 : appendStringInfo(context->buf, " ON %s", on);
9237 : 42 : }
9238 : :
9239 : : /*
9240 : : * get_json_expr_options
9241 : : *
9242 : : * Parse back common options for JSON_QUERY, JSON_VALUE, JSON_EXISTS and
9243 : : * JSON_TABLE columns.
9244 : : */
9245 : : static void
9246 : 228 : get_json_expr_options(JsonExpr *jsexpr, deparse_context *context,
9247 : : JsonBehaviorType default_behavior)
9248 : : {
9249 [ + + ]: 228 : if (jsexpr->op == JSON_QUERY_OP)
9250 : : {
9251 [ + + ]: 105 : if (jsexpr->wrapper == JSW_CONDITIONAL)
704 drowley@postgresql.o 9252 : 6 : appendStringInfoString(context->buf, " WITH CONDITIONAL WRAPPER");
724 amitlan@postgresql.o 9253 [ + + ]: 99 : else if (jsexpr->wrapper == JSW_UNCONDITIONAL)
704 drowley@postgresql.o 9254 : 15 : appendStringInfoString(context->buf, " WITH UNCONDITIONAL WRAPPER");
9255 : : /* The default */
706 amitlan@postgresql.o 9256 [ + + + - ]: 84 : else if (jsexpr->wrapper == JSW_NONE || jsexpr->wrapper == JSW_UNSPEC)
704 drowley@postgresql.o 9257 : 84 : appendStringInfoString(context->buf, " WITHOUT WRAPPER");
9258 : :
724 amitlan@postgresql.o 9259 [ + + ]: 105 : if (jsexpr->omit_quotes)
704 drowley@postgresql.o 9260 : 21 : appendStringInfoString(context->buf, " OMIT QUOTES");
9261 : : /* The default */
9262 : : else
9263 : 84 : appendStringInfoString(context->buf, " KEEP QUOTES");
9264 : : }
9265 : :
724 amitlan@postgresql.o 9266 [ + + + + ]: 228 : if (jsexpr->on_empty && jsexpr->on_empty->btype != default_behavior)
9267 : 15 : get_json_behavior(jsexpr->on_empty, context, "EMPTY");
9268 : :
9269 [ + - + + ]: 228 : if (jsexpr->on_error && jsexpr->on_error->btype != default_behavior)
9270 : 24 : get_json_behavior(jsexpr->on_error, context, "ERROR");
9271 : 228 : }
9272 : :
9273 : : /* ----------
9274 : : * get_rule_expr - Parse back an expression
9275 : : *
9276 : : * Note: showimplicit determines whether we display any implicit cast that
9277 : : * is present at the top of the expression tree. It is a passed argument,
9278 : : * not a field of the context struct, because we change the value as we
9279 : : * recurse down into the expression. In general we suppress implicit casts
9280 : : * when the result type is known with certainty (eg, the arguments of an
9281 : : * OR must be boolean). We display implicit casts for arguments of functions
9282 : : * and operators, since this is needed to be certain that the same function
9283 : : * or operator will be chosen when the expression is re-parsed.
9284 : : * ----------
9285 : : */
9286 : : static void
8578 tgl@sss.pgh.pa.us 9287 : 183095 : get_rule_expr(Node *node, deparse_context *context,
9288 : : bool showimplicit)
9289 : : {
9660 9290 : 183095 : StringInfo buf = context->buf;
9291 : :
10057 bruce@momjian.us 9292 [ + + ]: 183095 : if (node == NULL)
9661 tgl@sss.pgh.pa.us 9293 : 45 : return;
9294 : :
9295 : : /* Guard against excessively long or deeply-nested queries */
4337 9296 [ - + ]: 183050 : CHECK_FOR_INTERRUPTS();
9297 : 183050 : check_stack_depth();
9298 : :
9299 : : /*
9300 : : * Each level of get_rule_expr must emit an indivisible term
9301 : : * (parenthesized if necessary) to ensure result is reparsed into the same
9302 : : * expression tree. The only exception is that when the input is a List,
9303 : : * we emit the component items comma-separated with no surrounding
9304 : : * decoration; this is convenient for most callers.
9305 : : */
10057 bruce@momjian.us 9306 [ + + + + : 183050 : switch (nodeTag(node))
+ + + + +
+ + + + +
+ + + - +
+ + + + +
+ + - + +
+ + + + +
+ + + + +
+ - + + +
+ + + + +
+ - ]
9307 : : {
9696 tgl@sss.pgh.pa.us 9308 : 88352 : case T_Var:
5070 9309 : 88352 : (void) get_variable((Var *) node, 0, false, context);
10057 bruce@momjian.us 9310 : 88352 : break;
9311 : :
8494 tgl@sss.pgh.pa.us 9312 : 32065 : case T_Const:
6643 9313 : 32065 : get_const_expr((Const *) node, context, 0);
8494 9314 : 32065 : break;
9315 : :
9316 : 3685 : case T_Param:
5724 9317 : 3685 : get_parameter((Param *) node, context);
10057 bruce@momjian.us 9318 : 3685 : break;
9319 : :
9696 tgl@sss.pgh.pa.us 9320 : 1994 : case T_Aggref:
3609 rhaas@postgresql.org 9321 : 1994 : get_agg_expr((Aggref *) node, context, (Aggref *) node);
9696 tgl@sss.pgh.pa.us 9322 : 1994 : break;
9323 : :
3956 andres@anarazel.de 9324 : 56 : case T_GroupingFunc:
9325 : : {
9326 : 56 : GroupingFunc *gexpr = (GroupingFunc *) node;
9327 : :
9328 : 56 : appendStringInfoString(buf, "GROUPING(");
9329 : 56 : get_rule_expr((Node *) gexpr->args, context, true);
9330 : 56 : appendStringInfoChar(buf, ')');
9331 : : }
9332 : 56 : break;
9333 : :
6286 tgl@sss.pgh.pa.us 9334 : 162 : case T_WindowFunc:
9335 : 162 : get_windowfunc_expr((WindowFunc *) node, context);
9336 : 162 : break;
9337 : :
728 dean.a.rasheed@gmail 9338 : 3 : case T_MergeSupportFunc:
9339 : 3 : appendStringInfoString(buf, "MERGE_ACTION()");
9340 : 3 : break;
9341 : :
2599 alvherre@alvh.no-ip. 9342 : 194 : case T_SubscriptingRef:
9343 : : {
9344 : 194 : SubscriptingRef *sbsref = (SubscriptingRef *) node;
9345 : : bool need_parens;
9346 : :
9347 : : /*
9348 : : * If the argument is a CaseTestExpr, we must be inside a
9349 : : * FieldStore, ie, we are assigning to an element of an array
9350 : : * within a composite column. Since we already punted on
9351 : : * displaying the FieldStore's target information, just punt
9352 : : * here too, and display only the assignment source
9353 : : * expression.
9354 : : */
9355 [ - + ]: 194 : if (IsA(sbsref->refexpr, CaseTestExpr))
9356 : : {
2599 alvherre@alvh.no-ip. 9357 [ # # ]:UBC 0 : Assert(sbsref->refassgnexpr);
9358 : 0 : get_rule_expr((Node *) sbsref->refassgnexpr,
9359 : : context, showimplicit);
5869 tgl@sss.pgh.pa.us 9360 : 0 : break;
9361 : : }
9362 : :
9363 : : /*
9364 : : * Parenthesize the argument unless it's a simple Var or a
9365 : : * FieldSelect. (In particular, if it's another
9366 : : * SubscriptingRef, we *must* parenthesize to avoid
9367 : : * confusion.)
9368 : : */
2599 alvherre@alvh.no-ip. 9369 [ + + ]:CBC 301 : need_parens = !IsA(sbsref->refexpr, Var) &&
9370 [ + + ]: 107 : !IsA(sbsref->refexpr, FieldSelect);
8377 tgl@sss.pgh.pa.us 9371 [ + + ]: 194 : if (need_parens)
9372 : 47 : appendStringInfoChar(buf, '(');
2599 alvherre@alvh.no-ip. 9373 : 194 : get_rule_expr((Node *) sbsref->refexpr, context, showimplicit);
8377 tgl@sss.pgh.pa.us 9374 [ + + ]: 194 : if (need_parens)
9375 : 47 : appendStringInfoChar(buf, ')');
9376 : :
9377 : : /*
9378 : : * If there's a refassgnexpr, we want to print the node in the
9379 : : * format "container[subscripts] := refassgnexpr". This is
9380 : : * not legal SQL, so decompilation of INSERT or UPDATE
9381 : : * statements should always use processIndirection as part of
9382 : : * the statement-level syntax. We should only see this when
9383 : : * EXPLAIN tries to print the targetlist of a plan resulting
9384 : : * from such a statement.
9385 : : */
2599 alvherre@alvh.no-ip. 9386 [ + + ]: 194 : if (sbsref->refassgnexpr)
9387 : : {
9388 : : Node *refassgnexpr;
9389 : :
9390 : : /*
9391 : : * Use processIndirection to print this node's subscripts
9392 : : * as well as any additional field selections or
9393 : : * subscripting in immediate descendants. It returns the
9394 : : * RHS expr that is actually being "assigned".
9395 : : */
3511 tgl@sss.pgh.pa.us 9396 : 6 : refassgnexpr = processIndirection(node, context);
5869 9397 : 6 : appendStringInfoString(buf, " := ");
9398 : 6 : get_rule_expr(refassgnexpr, context, showimplicit);
9399 : : }
9400 : : else
9401 : : {
9402 : : /* Just an ordinary container fetch, so print subscripts */
2599 alvherre@alvh.no-ip. 9403 : 188 : printSubscripts(sbsref, context);
9404 : : }
9405 : : }
8494 tgl@sss.pgh.pa.us 9406 : 194 : break;
9407 : :
9408 : 6513 : case T_FuncExpr:
9409 : 6513 : get_func_expr((FuncExpr *) node, context, showimplicit);
9410 : 6513 : break;
9411 : :
6002 9412 : 21 : case T_NamedArgExpr:
9413 : : {
9414 : 21 : NamedArgExpr *na = (NamedArgExpr *) node;
9415 : :
3971 rhaas@postgresql.org 9416 : 21 : appendStringInfo(buf, "%s => ", quote_identifier(na->name));
6002 tgl@sss.pgh.pa.us 9417 : 21 : get_rule_expr((Node *) na->arg, context, showimplicit);
9418 : : }
9419 : 21 : break;
9420 : :
8494 9421 : 31643 : case T_OpExpr:
9422 : 31643 : get_oper_expr((OpExpr *) node, context);
9423 : 31643 : break;
9424 : :
9425 : 12 : case T_DistinctExpr:
9426 : : {
9427 : 12 : DistinctExpr *expr = (DistinctExpr *) node;
9428 : 12 : List *args = expr->args;
7963 neilc@samurai.com 9429 : 12 : Node *arg1 = (Node *) linitial(args);
8295 tgl@sss.pgh.pa.us 9430 : 12 : Node *arg2 = (Node *) lsecond(args);
9431 : :
8264 9432 [ + + ]: 12 : if (!PRETTY_PAREN(context))
8259 bruce@momjian.us 9433 : 9 : appendStringInfoChar(buf, '(');
8264 tgl@sss.pgh.pa.us 9434 : 12 : get_rule_expr_paren(arg1, context, true, node);
4518 rhaas@postgresql.org 9435 : 12 : appendStringInfoString(buf, " IS DISTINCT FROM ");
8264 tgl@sss.pgh.pa.us 9436 : 12 : get_rule_expr_paren(arg2, context, true, node);
9437 [ + + ]: 12 : if (!PRETTY_PAREN(context))
8259 bruce@momjian.us 9438 : 9 : appendStringInfoChar(buf, ')');
9439 : : }
8295 tgl@sss.pgh.pa.us 9440 : 12 : break;
9441 : :
5475 9442 : 80 : case T_NullIfExpr:
9443 : : {
9444 : 80 : NullIfExpr *nullifexpr = (NullIfExpr *) node;
9445 : :
4518 rhaas@postgresql.org 9446 : 80 : appendStringInfoString(buf, "NULLIF(");
5475 tgl@sss.pgh.pa.us 9447 : 80 : get_rule_expr((Node *) nullifexpr->args, context, true);
9448 : 80 : appendStringInfoChar(buf, ')');
9449 : : }
9450 : 80 : break;
9451 : :
8295 9452 : 1521 : case T_ScalarArrayOpExpr:
9453 : : {
9454 : 1521 : ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
9455 : 1521 : List *args = expr->args;
7963 neilc@samurai.com 9456 : 1521 : Node *arg1 = (Node *) linitial(args);
8295 tgl@sss.pgh.pa.us 9457 : 1521 : Node *arg2 = (Node *) lsecond(args);
9458 : :
8264 9459 [ + + ]: 1521 : if (!PRETTY_PAREN(context))
8259 bruce@momjian.us 9460 : 1515 : appendStringInfoChar(buf, '(');
8264 tgl@sss.pgh.pa.us 9461 : 1521 : get_rule_expr_paren(arg1, context, true, node);
8295 9462 : 1521 : appendStringInfo(buf, " %s %s (",
9463 : : generate_operator_name(expr->opno,
9464 : : exprType(arg1),
9465 : : get_base_element_type(exprType(arg2))),
9466 [ + + ]: 1521 : expr->useOr ? "ANY" : "ALL");
8264 9467 : 1521 : get_rule_expr_paren(arg2, context, true, node);
9468 : :
9469 : : /*
9470 : : * There's inherent ambiguity in "x op ANY/ALL (y)" when y is
9471 : : * a bare sub-SELECT. Since we're here, the sub-SELECT must
9472 : : * be meant as a scalar sub-SELECT yielding an array value to
9473 : : * be used in ScalarArrayOpExpr; but the grammar will
9474 : : * preferentially interpret such a construct as an ANY/ALL
9475 : : * SubLink. To prevent misparsing the output that way, insert
9476 : : * a dummy coercion (which will be stripped by parse analysis,
9477 : : * so no inefficiency is added in dump and reload). This is
9478 : : * indeed most likely what the user wrote to get the construct
9479 : : * accepted in the first place.
9480 : : */
3615 9481 [ + + ]: 1521 : if (IsA(arg2, SubLink) &&
9482 [ + - ]: 3 : ((SubLink *) arg2)->subLinkType == EXPR_SUBLINK)
9483 : 3 : appendStringInfo(buf, "::%s",
9484 : : format_type_with_typemod(exprType(arg2),
9485 : : exprTypmod(arg2)));
8264 9486 : 1521 : appendStringInfoChar(buf, ')');
9487 [ + + ]: 1521 : if (!PRETTY_PAREN(context))
8259 bruce@momjian.us 9488 : 1515 : appendStringInfoChar(buf, ')');
9489 : : }
10057 9490 : 1521 : break;
9491 : :
8494 tgl@sss.pgh.pa.us 9492 : 5699 : case T_BoolExpr:
9493 : : {
9494 : 5699 : BoolExpr *expr = (BoolExpr *) node;
7963 neilc@samurai.com 9495 : 5699 : Node *first_arg = linitial(expr->args);
9496 : : ListCell *arg;
9497 : :
8494 tgl@sss.pgh.pa.us 9498 [ + + + - ]: 5699 : switch (expr->boolop)
9499 : : {
9500 : 4521 : case AND_EXPR:
8264 9501 [ + + ]: 4521 : if (!PRETTY_PAREN(context))
8259 bruce@momjian.us 9502 : 4494 : appendStringInfoChar(buf, '(');
7963 neilc@samurai.com 9503 : 4521 : get_rule_expr_paren(first_arg, context,
9504 : : false, node);
1994 tgl@sss.pgh.pa.us 9505 [ + - + + : 10296 : for_each_from(arg, expr->args, 1)
+ + ]
9506 : : {
4518 rhaas@postgresql.org 9507 : 5775 : appendStringInfoString(buf, " AND ");
7963 neilc@samurai.com 9508 : 5775 : get_rule_expr_paren((Node *) lfirst(arg), context,
9509 : : false, node);
9510 : : }
8264 tgl@sss.pgh.pa.us 9511 [ + + ]: 4521 : if (!PRETTY_PAREN(context))
8259 bruce@momjian.us 9512 : 4494 : appendStringInfoChar(buf, ')');
8494 tgl@sss.pgh.pa.us 9513 : 4521 : break;
9514 : :
9515 : 956 : case OR_EXPR:
8264 9516 [ + + ]: 956 : if (!PRETTY_PAREN(context))
8259 bruce@momjian.us 9517 : 950 : appendStringInfoChar(buf, '(');
7963 neilc@samurai.com 9518 : 956 : get_rule_expr_paren(first_arg, context,
9519 : : false, node);
1994 tgl@sss.pgh.pa.us 9520 [ + - + + : 2269 : for_each_from(arg, expr->args, 1)
+ + ]
9521 : : {
4518 rhaas@postgresql.org 9522 : 1313 : appendStringInfoString(buf, " OR ");
7963 neilc@samurai.com 9523 : 1313 : get_rule_expr_paren((Node *) lfirst(arg), context,
9524 : : false, node);
9525 : : }
8264 tgl@sss.pgh.pa.us 9526 [ + + ]: 956 : if (!PRETTY_PAREN(context))
8259 bruce@momjian.us 9527 : 950 : appendStringInfoChar(buf, ')');
8494 tgl@sss.pgh.pa.us 9528 : 956 : break;
9529 : :
9530 : 222 : case NOT_EXPR:
8264 9531 [ + + ]: 222 : if (!PRETTY_PAREN(context))
8259 bruce@momjian.us 9532 : 216 : appendStringInfoChar(buf, '(');
4518 rhaas@postgresql.org 9533 : 222 : appendStringInfoString(buf, "NOT ");
7963 neilc@samurai.com 9534 : 222 : get_rule_expr_paren(first_arg, context,
9535 : : false, node);
8264 tgl@sss.pgh.pa.us 9536 [ + + ]: 222 : if (!PRETTY_PAREN(context))
8259 bruce@momjian.us 9537 : 216 : appendStringInfoChar(buf, ')');
8494 tgl@sss.pgh.pa.us 9538 : 222 : break;
9539 : :
8494 tgl@sss.pgh.pa.us 9540 :UBC 0 : default:
8267 9541 [ # # ]: 0 : elog(ERROR, "unrecognized boolop: %d",
9542 : : (int) expr->boolop);
9543 : : }
9544 : : }
8494 tgl@sss.pgh.pa.us 9545 :CBC 5699 : break;
9546 : :
9547 : 230 : case T_SubLink:
9548 : 230 : get_sublink_expr((SubLink *) node, context);
9549 : 230 : break;
9550 : :
8492 9551 : 397 : case T_SubPlan:
9552 : : {
6121 bruce@momjian.us 9553 : 397 : SubPlan *subplan = (SubPlan *) node;
9554 : :
9555 : : /*
9556 : : * We cannot see an already-planned subplan in rule deparsing,
9557 : : * only while EXPLAINing a query plan. We don't try to
9558 : : * reconstruct the original SQL, just reference the subplan
9559 : : * that appears elsewhere in EXPLAIN's result. It does seem
9560 : : * useful to show the subLinkType and testexpr (if any), and
9561 : : * we also note whether the subplan will be hashed.
9562 : : */
726 tgl@sss.pgh.pa.us 9563 [ + + + + : 397 : switch (subplan->subLinkType)
+ + + -
- ]
9564 : : {
9565 : 51 : case EXISTS_SUBLINK:
9566 : 51 : appendStringInfoString(buf, "EXISTS(");
9567 [ - + ]: 51 : Assert(subplan->testexpr == NULL);
9568 : 51 : break;
9569 : 3 : case ALL_SUBLINK:
9570 : 3 : appendStringInfoString(buf, "(ALL ");
9571 [ - + ]: 3 : Assert(subplan->testexpr != NULL);
9572 : 3 : break;
9573 : 116 : case ANY_SUBLINK:
9574 : 116 : appendStringInfoString(buf, "(ANY ");
9575 [ - + ]: 116 : Assert(subplan->testexpr != NULL);
9576 : 116 : break;
9577 : 3 : case ROWCOMPARE_SUBLINK:
9578 : : /* Parenthesizing the testexpr seems sufficient */
9579 : 3 : appendStringInfoChar(buf, '(');
9580 [ - + ]: 3 : Assert(subplan->testexpr != NULL);
9581 : 3 : break;
9582 : 205 : case EXPR_SUBLINK:
9583 : : /* No need to decorate these subplan references */
9584 : 205 : appendStringInfoChar(buf, '(');
9585 [ - + ]: 205 : Assert(subplan->testexpr == NULL);
9586 : 205 : break;
9587 : 13 : case MULTIEXPR_SUBLINK:
9588 : : /* MULTIEXPR isn't executed in the normal way */
9589 : 13 : appendStringInfoString(buf, "(rescan ");
9590 [ - + ]: 13 : Assert(subplan->testexpr == NULL);
9591 : 13 : break;
9592 : 6 : case ARRAY_SUBLINK:
9593 : 6 : appendStringInfoString(buf, "ARRAY(");
9594 [ - + ]: 6 : Assert(subplan->testexpr == NULL);
9595 : 6 : break;
726 tgl@sss.pgh.pa.us 9596 :UBC 0 : case CTE_SUBLINK:
9597 : : /* This case is unreachable within expressions */
9598 : 0 : appendStringInfoString(buf, "CTE(");
9599 [ # # ]: 0 : Assert(subplan->testexpr == NULL);
9600 : 0 : break;
9601 : : }
9602 : :
726 tgl@sss.pgh.pa.us 9603 [ + + ]:CBC 397 : if (subplan->testexpr != NULL)
9604 : : {
9605 : : deparse_namespace *dpns;
9606 : :
9607 : : /*
9608 : : * Push SubPlan into ancestors list while deparsing
9609 : : * testexpr, so that we can handle PARAM_EXEC references
9610 : : * to the SubPlan's paramIds. (This makes it look like
9611 : : * the SubPlan is an "ancestor" of the current plan node,
9612 : : * which is a little weird, but it does no harm.) In this
9613 : : * path, we don't need to mention the SubPlan explicitly,
9614 : : * because the referencing Params will show its existence.
9615 : : */
9616 : 122 : dpns = (deparse_namespace *) linitial(context->namespaces);
9617 : 122 : dpns->ancestors = lcons(subplan, dpns->ancestors);
9618 : :
9619 : 122 : get_rule_expr(subplan->testexpr, context, showimplicit);
9620 : 122 : appendStringInfoChar(buf, ')');
9621 : :
9622 : 122 : dpns->ancestors = list_delete_first(dpns->ancestors);
9623 : : }
9624 : : else
9625 : : {
9626 : : const char *nameprefix;
9627 : :
9628 : : /* No referencing Params, so show the SubPlan's name */
159 rhaas@postgresql.org 9629 [ - + ]:GNC 275 : if (subplan->isInitPlan)
159 rhaas@postgresql.org 9630 :UNC 0 : nameprefix = "InitPlan ";
9631 : : else
159 rhaas@postgresql.org 9632 :GNC 275 : nameprefix = "SubPlan ";
726 tgl@sss.pgh.pa.us 9633 [ - + ]:CBC 275 : if (subplan->useHashTable)
159 rhaas@postgresql.org 9634 :UNC 0 : appendStringInfo(buf, "hashed %s%s)",
9635 : : nameprefix, subplan->plan_name);
9636 : : else
159 rhaas@postgresql.org 9637 :GNC 275 : appendStringInfo(buf, "%s%s)",
9638 : : nameprefix, subplan->plan_name);
9639 : : }
9640 : : }
8494 tgl@sss.pgh.pa.us 9641 :CBC 397 : break;
9642 : :
6414 tgl@sss.pgh.pa.us 9643 :UBC 0 : case T_AlternativeSubPlan:
9644 : : {
6188 9645 : 0 : AlternativeSubPlan *asplan = (AlternativeSubPlan *) node;
9646 : : ListCell *lc;
9647 : :
9648 : : /*
9649 : : * This case cannot be reached in normal usage, since no
9650 : : * AlternativeSubPlan can appear either in parsetrees or
9651 : : * finished plan trees. We keep it just in case somebody
9652 : : * wants to use this code to print planner data structures.
9653 : : */
4518 rhaas@postgresql.org 9654 : 0 : appendStringInfoString(buf, "(alternatives: ");
6188 tgl@sss.pgh.pa.us 9655 [ # # # # : 0 : foreach(lc, asplan->subplans)
# # ]
9656 : : {
3261 9657 : 0 : SubPlan *splan = lfirst_node(SubPlan, lc);
9658 : : const char *nameprefix;
9659 : :
159 rhaas@postgresql.org 9660 [ # # ]:UNC 0 : if (splan->isInitPlan)
9661 : 0 : nameprefix = "InitPlan ";
9662 : : else
9663 : 0 : nameprefix = "SubPlan ";
6188 tgl@sss.pgh.pa.us 9664 [ # # ]:UBC 0 : if (splan->useHashTable)
159 rhaas@postgresql.org 9665 :UNC 0 : appendStringInfo(buf, "hashed %s%s", nameprefix,
9666 : : splan->plan_name);
9667 : : else
9668 : 0 : appendStringInfo(buf, "%s%s", nameprefix,
9669 : : splan->plan_name);
2435 tgl@sss.pgh.pa.us 9670 [ # # ]:UBC 0 : if (lnext(asplan->subplans, lc))
4518 rhaas@postgresql.org 9671 : 0 : appendStringInfoString(buf, " or ");
9672 : : }
9673 : 0 : appendStringInfoChar(buf, ')');
9674 : : }
6414 tgl@sss.pgh.pa.us 9675 : 0 : break;
9676 : :
9350 tgl@sss.pgh.pa.us 9677 :CBC 772 : case T_FieldSelect:
9678 : : {
9679 : 772 : FieldSelect *fselect = (FieldSelect *) node;
7593 9680 : 772 : Node *arg = (Node *) fselect->arg;
9681 : 772 : int fno = fselect->fieldnum;
9682 : : const char *fieldname;
9683 : : bool need_parens;
9684 : :
9685 : : /*
9686 : : * Parenthesize the argument unless it's a SubscriptingRef or
9687 : : * another FieldSelect. Note in particular that it would be
9688 : : * WRONG to not parenthesize a Var argument; simplicity is not
9689 : : * the issue here, having the right number of names is.
9690 : : */
2599 alvherre@alvh.no-ip. 9691 [ + + ]: 1526 : need_parens = !IsA(arg, SubscriptingRef) &&
9692 [ + - ]: 754 : !IsA(arg, FieldSelect);
7949 tgl@sss.pgh.pa.us 9693 [ + + ]: 772 : if (need_parens)
9694 : 754 : appendStringInfoChar(buf, '(');
7593 9695 : 772 : get_rule_expr(arg, context, true);
7949 9696 [ + + ]: 772 : if (need_parens)
9697 : 754 : appendStringInfoChar(buf, ')');
9698 : :
9699 : : /*
9700 : : * Get and print the field name.
9701 : : */
6960 9702 : 772 : fieldname = get_name_for_var_field((Var *) arg, fno,
9703 : : 0, context);
8264 9704 : 772 : appendStringInfo(buf, ".%s", quote_identifier(fieldname));
9705 : : }
9350 9706 : 772 : break;
9707 : :
7949 9708 : 3 : case T_FieldStore:
9709 : : {
5869 9710 : 3 : FieldStore *fstore = (FieldStore *) node;
9711 : : bool need_parens;
9712 : :
9713 : : /*
9714 : : * There is no good way to represent a FieldStore as real SQL,
9715 : : * so decompilation of INSERT or UPDATE statements should
9716 : : * always use processIndirection as part of the
9717 : : * statement-level syntax. We should only get here when
9718 : : * EXPLAIN tries to print the targetlist of a plan resulting
9719 : : * from such a statement. The plan case is even harder than
9720 : : * ordinary rules would be, because the planner tries to
9721 : : * collapse multiple assignments to the same field or subfield
9722 : : * into one FieldStore; so we can see a list of target fields
9723 : : * not just one, and the arguments could be FieldStores
9724 : : * themselves. We don't bother to try to print the target
9725 : : * field names; we just print the source arguments, with a
9726 : : * ROW() around them if there's more than one. This isn't
9727 : : * terribly complete, but it's probably good enough for
9728 : : * EXPLAIN's purposes; especially since anything more would be
9729 : : * either hopelessly confusing or an even poorer
9730 : : * representation of what the plan is actually doing.
9731 : : */
9732 : 3 : need_parens = (list_length(fstore->newvals) != 1);
9733 [ + - ]: 3 : if (need_parens)
9734 : 3 : appendStringInfoString(buf, "ROW(");
9735 : 3 : get_rule_expr((Node *) fstore->newvals, context, showimplicit);
9736 [ + - ]: 3 : if (need_parens)
9737 : 3 : appendStringInfoChar(buf, ')');
9738 : : }
7949 9739 : 3 : break;
9740 : :
9520 9741 : 1395 : case T_RelabelType:
9742 : : {
9743 : 1395 : RelabelType *relabel = (RelabelType *) node;
8259 bruce@momjian.us 9744 : 1395 : Node *arg = (Node *) relabel->arg;
9745 : :
8578 tgl@sss.pgh.pa.us 9746 [ + + ]: 1395 : if (relabel->relabelformat == COERCE_IMPLICIT_CAST &&
9747 [ + + ]: 1280 : !showimplicit)
9748 : : {
9749 : : /* don't show the implicit cast */
7942 9750 : 37 : get_rule_expr_paren(arg, context, false, node);
9751 : : }
9752 : : else
9753 : : {
6938 9754 : 1358 : get_coercion_expr(arg, context,
9755 : : relabel->resulttype,
9756 : : relabel->resulttypmod,
9757 : : node);
9758 : : }
9759 : : }
9520 9760 : 1395 : break;
9761 : :
6858 9762 : 358 : case T_CoerceViaIO:
9763 : : {
9764 : 358 : CoerceViaIO *iocoerce = (CoerceViaIO *) node;
9765 : 358 : Node *arg = (Node *) iocoerce->arg;
9766 : :
9767 [ + + ]: 358 : if (iocoerce->coerceformat == COERCE_IMPLICIT_CAST &&
9768 [ + - ]: 12 : !showimplicit)
9769 : : {
9770 : : /* don't show the implicit cast */
9771 : 12 : get_rule_expr_paren(arg, context, false, node);
9772 : : }
9773 : : else
9774 : : {
9775 : 346 : get_coercion_expr(arg, context,
9776 : : iocoerce->resulttype,
9777 : : -1,
9778 : : node);
9779 : : }
9780 : : }
9781 : 358 : break;
9782 : :
6928 9783 : 26 : case T_ArrayCoerceExpr:
9784 : : {
9785 : 26 : ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
9786 : 26 : Node *arg = (Node *) acoerce->arg;
9787 : :
9788 [ + - ]: 26 : if (acoerce->coerceformat == COERCE_IMPLICIT_CAST &&
9789 [ - + ]: 26 : !showimplicit)
9790 : : {
9791 : : /* don't show the implicit cast */
6928 tgl@sss.pgh.pa.us 9792 :UBC 0 : get_rule_expr_paren(arg, context, false, node);
9793 : : }
9794 : : else
9795 : : {
6928 tgl@sss.pgh.pa.us 9796 :CBC 26 : get_coercion_expr(arg, context,
9797 : : acoerce->resulttype,
9798 : : acoerce->resulttypmod,
9799 : : node);
9800 : : }
9801 : : }
9802 : 26 : break;
9803 : :
7764 9804 : 44 : case T_ConvertRowtypeExpr:
9805 : : {
9806 : 44 : ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node;
9807 : 44 : Node *arg = (Node *) convert->arg;
9808 : :
9809 [ + + ]: 44 : if (convert->convertformat == COERCE_IMPLICIT_CAST &&
9810 [ + + ]: 41 : !showimplicit)
9811 : : {
9812 : : /* don't show the implicit cast */
9813 : 12 : get_rule_expr_paren(arg, context, false, node);
9814 : : }
9815 : : else
9816 : : {
6938 9817 : 32 : get_coercion_expr(arg, context,
9818 : : convert->resulttype, -1,
9819 : : node);
9820 : : }
9821 : : }
7764 9822 : 44 : break;
9823 : :
5483 9824 : 45 : case T_CollateExpr:
9825 : : {
9826 : 45 : CollateExpr *collate = (CollateExpr *) node;
9827 : 45 : Node *arg = (Node *) collate->arg;
9828 : :
9829 [ + + ]: 45 : if (!PRETTY_PAREN(context))
9830 : 42 : appendStringInfoChar(buf, '(');
9831 : 45 : get_rule_expr_paren(arg, context, showimplicit, node);
9832 : 45 : appendStringInfo(buf, " COLLATE %s",
9833 : : generate_collation_name(collate->collOid));
9834 [ + + ]: 45 : if (!PRETTY_PAREN(context))
9835 : 42 : appendStringInfoChar(buf, ')');
9836 : : }
9837 : 45 : break;
9838 : :
9696 9839 : 351 : case T_CaseExpr:
9840 : : {
9841 : 351 : CaseExpr *caseexpr = (CaseExpr *) node;
9842 : : ListCell *temp;
9843 : :
8264 9844 : 351 : appendContextKeyword(context, "CASE",
9845 : : 0, PRETTYINDENT_VAR, 0);
8033 9846 [ + + ]: 351 : if (caseexpr->arg)
9847 : : {
9848 : 111 : appendStringInfoChar(buf, ' ');
9849 : 111 : get_rule_expr((Node *) caseexpr->arg, context, true);
9850 : : }
9696 9851 [ + - + + : 1541 : foreach(temp, caseexpr->args)
+ + ]
9852 : : {
9853 : 1190 : CaseWhen *when = (CaseWhen *) lfirst(temp);
7400 9854 : 1190 : Node *w = (Node *) when->expr;
9855 : :
8033 9856 [ + + ]: 1190 : if (caseexpr->arg)
9857 : : {
9858 : : /*
9859 : : * The parser should have produced WHEN clauses of the
9860 : : * form "CaseTestExpr = RHS", possibly with an
9861 : : * implicit coercion inserted above the CaseTestExpr.
9862 : : * For accurate decompilation of rules it's essential
9863 : : * that we show just the RHS. However in an
9864 : : * expression that's been through the optimizer, the
9865 : : * WHEN clause could be almost anything (since the
9866 : : * equality operator could have been expanded into an
9867 : : * inline function). If we don't recognize the form
9868 : : * of the WHEN clause, just punt and display it as-is.
9869 : : */
7400 9870 [ + - ]: 440 : if (IsA(w, OpExpr))
9871 : : {
6227 9872 : 440 : List *args = ((OpExpr *) w)->args;
9873 : :
5407 9874 [ + - ]: 440 : if (list_length(args) == 2 &&
9875 [ + - ]: 440 : IsA(strip_implicit_coercions(linitial(args)),
9876 : : CaseTestExpr))
9877 : 440 : w = (Node *) lsecond(args);
9878 : : }
9879 : : }
9880 : :
9881 [ + + ]: 1190 : if (!PRETTY_INDENT(context))
9882 : 65 : appendStringInfoChar(buf, ' ');
9883 : 1190 : appendContextKeyword(context, "WHEN ",
9884 : : 0, 0, 0);
9885 : 1190 : get_rule_expr(w, context, false);
4518 rhaas@postgresql.org 9886 : 1190 : appendStringInfoString(buf, " THEN ");
8494 tgl@sss.pgh.pa.us 9887 : 1190 : get_rule_expr((Node *) when->result, context, true);
9888 : : }
8264 9889 [ + + ]: 351 : if (!PRETTY_INDENT(context))
8259 bruce@momjian.us 9890 : 60 : appendStringInfoChar(buf, ' ');
8264 tgl@sss.pgh.pa.us 9891 : 351 : appendContextKeyword(context, "ELSE ",
9892 : : 0, 0, 0);
8494 9893 : 351 : get_rule_expr((Node *) caseexpr->defresult, context, true);
8264 9894 [ + + ]: 351 : if (!PRETTY_INDENT(context))
8259 bruce@momjian.us 9895 : 60 : appendStringInfoChar(buf, ' ');
8264 tgl@sss.pgh.pa.us 9896 : 351 : appendContextKeyword(context, "END",
9897 : : -PRETTYINDENT_VAR, 0, 0);
9898 : : }
10057 bruce@momjian.us 9899 : 351 : break;
9900 : :
5407 tgl@sss.pgh.pa.us 9901 :UBC 0 : case T_CaseTestExpr:
9902 : : {
9903 : : /*
9904 : : * Normally we should never get here, since for expressions
9905 : : * that can contain this node type we attempt to avoid
9906 : : * recursing to it. But in an optimized expression we might
9907 : : * be unable to avoid that (see comments for CaseExpr). If we
9908 : : * do see one, print it as CASE_TEST_EXPR.
9909 : : */
4518 rhaas@postgresql.org 9910 : 0 : appendStringInfoString(buf, "CASE_TEST_EXPR");
9911 : : }
5407 tgl@sss.pgh.pa.us 9912 : 0 : break;
9913 : :
8377 tgl@sss.pgh.pa.us 9914 :CBC 292 : case T_ArrayExpr:
9915 : : {
8259 bruce@momjian.us 9916 : 292 : ArrayExpr *arrayexpr = (ArrayExpr *) node;
9917 : :
4518 rhaas@postgresql.org 9918 : 292 : appendStringInfoString(buf, "ARRAY[");
7829 tgl@sss.pgh.pa.us 9919 : 292 : get_rule_expr((Node *) arrayexpr->elements, context, true);
9920 : 292 : appendStringInfoChar(buf, ']');
9921 : :
9922 : : /*
9923 : : * If the array isn't empty, we assume its elements are
9924 : : * coerced to the desired type. If it's empty, though, we
9925 : : * need an explicit coercion to the array type.
9926 : : */
6295 9927 [ + + ]: 292 : if (arrayexpr->elements == NIL)
9928 : 3 : appendStringInfo(buf, "::%s",
9929 : : format_type_with_typemod(arrayexpr->array_typeid, -1));
9930 : : }
8377 9931 : 292 : break;
9932 : :
7979 9933 : 108 : case T_RowExpr:
9934 : : {
7868 bruce@momjian.us 9935 : 108 : RowExpr *rowexpr = (RowExpr *) node;
7880 tgl@sss.pgh.pa.us 9936 : 108 : TupleDesc tupdesc = NULL;
9937 : : ListCell *arg;
9938 : : int i;
9939 : : char *sep;
9940 : :
9941 : : /*
9942 : : * If it's a named type and not RECORD, we may have to skip
9943 : : * dropped columns and/or claim there are NULLs for added
9944 : : * columns.
9945 : : */
9946 [ + + ]: 108 : if (rowexpr->row_typeid != RECORDOID)
9947 : : {
9948 : 33 : tupdesc = lookup_rowtype_tupdesc(rowexpr->row_typeid, -1);
9949 [ - + ]: 33 : Assert(list_length(rowexpr->args) <= tupdesc->natts);
9950 : : }
9951 : :
9952 : : /*
9953 : : * SQL99 allows "ROW" to be omitted when there is more than
9954 : : * one column, but for simplicity we always print it.
9955 : : */
4518 rhaas@postgresql.org 9956 : 108 : appendStringInfoString(buf, "ROW(");
7979 tgl@sss.pgh.pa.us 9957 : 108 : sep = "";
7880 9958 : 108 : i = 0;
7979 9959 [ + - + + : 327 : foreach(arg, rowexpr->args)
+ + ]
9960 : : {
9961 : 219 : Node *e = (Node *) lfirst(arg);
9962 : :
7880 9963 [ + + ]: 219 : if (tupdesc == NULL ||
144 drowley@postgresql.o 9964 [ + - ]:GNC 78 : !TupleDescCompactAttr(tupdesc, i)->attisdropped)
9965 : : {
7624 neilc@samurai.com 9966 :CBC 219 : appendStringInfoString(buf, sep);
9967 : : /* Whole-row Vars need special treatment here */
3773 tgl@sss.pgh.pa.us 9968 : 219 : get_rule_expr_toplevel(e, context, true);
7880 9969 : 219 : sep = ", ";
9970 : : }
9971 : 219 : i++;
9972 : : }
9973 [ + + ]: 108 : if (tupdesc != NULL)
9974 : : {
9975 [ - + ]: 33 : while (i < tupdesc->natts)
9976 : : {
144 drowley@postgresql.o 9977 [ # # ]:UNC 0 : if (!TupleDescCompactAttr(tupdesc, i)->attisdropped)
9978 : : {
7624 neilc@samurai.com 9979 :UBC 0 : appendStringInfoString(buf, sep);
4518 rhaas@postgresql.org 9980 : 0 : appendStringInfoString(buf, "NULL");
7880 tgl@sss.pgh.pa.us 9981 : 0 : sep = ", ";
9982 : : }
9983 : 0 : i++;
9984 : : }
9985 : :
7212 tgl@sss.pgh.pa.us 9986 [ + - ]:CBC 33 : ReleaseTupleDesc(tupdesc);
9987 : : }
4518 rhaas@postgresql.org 9988 : 108 : appendStringInfoChar(buf, ')');
7979 tgl@sss.pgh.pa.us 9989 [ + + ]: 108 : if (rowexpr->row_format == COERCE_EXPLICIT_CAST)
9990 : 18 : appendStringInfo(buf, "::%s",
9991 : : format_type_with_typemod(rowexpr->row_typeid, -1));
9992 : : }
9993 : 108 : break;
9994 : :
7382 9995 : 60 : case T_RowCompareExpr:
9996 : : {
9997 : 60 : RowCompareExpr *rcexpr = (RowCompareExpr *) node;
9998 : :
9999 : : /*
10000 : : * SQL99 allows "ROW" to be omitted when there is more than
10001 : : * one column, but for simplicity we always print it. Within
10002 : : * a ROW expression, whole-row Vars need special treatment, so
10003 : : * use get_rule_list_toplevel.
10004 : : */
4518 rhaas@postgresql.org 10005 : 60 : appendStringInfoString(buf, "(ROW(");
1522 tgl@sss.pgh.pa.us 10006 : 60 : get_rule_list_toplevel(rcexpr->largs, context, true);
10007 : :
10008 : : /*
10009 : : * We assume that the name of the first-column operator will
10010 : : * do for all the rest too. This is definitely open to
10011 : : * failure, eg if some but not all operators were renamed
10012 : : * since the construct was parsed, but there seems no way to
10013 : : * be perfect.
10014 : : */
7382 10015 : 60 : appendStringInfo(buf, ") %s ROW(",
3189 10016 : 60 : generate_operator_name(linitial_oid(rcexpr->opnos),
10017 : 60 : exprType(linitial(rcexpr->largs)),
10018 : 60 : exprType(linitial(rcexpr->rargs))));
1522 10019 : 60 : get_rule_list_toplevel(rcexpr->rargs, context, true);
4518 rhaas@postgresql.org 10020 : 60 : appendStringInfoString(buf, "))");
10021 : : }
7382 tgl@sss.pgh.pa.us 10022 : 60 : break;
10023 : :
8428 10024 : 615 : case T_CoalesceExpr:
10025 : : {
10026 : 615 : CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
10027 : :
4518 rhaas@postgresql.org 10028 : 615 : appendStringInfoString(buf, "COALESCE(");
7829 tgl@sss.pgh.pa.us 10029 : 615 : get_rule_expr((Node *) coalesceexpr->args, context, true);
10030 : 615 : appendStringInfoChar(buf, ')');
10031 : : }
8428 10032 : 615 : break;
10033 : :
7567 10034 : 21 : case T_MinMaxExpr:
10035 : : {
10036 : 21 : MinMaxExpr *minmaxexpr = (MinMaxExpr *) node;
10037 : :
10038 [ + + - ]: 21 : switch (minmaxexpr->op)
10039 : : {
10040 : 6 : case IS_GREATEST:
4518 rhaas@postgresql.org 10041 : 6 : appendStringInfoString(buf, "GREATEST(");
7567 tgl@sss.pgh.pa.us 10042 : 6 : break;
10043 : 15 : case IS_LEAST:
4518 rhaas@postgresql.org 10044 : 15 : appendStringInfoString(buf, "LEAST(");
7567 tgl@sss.pgh.pa.us 10045 : 15 : break;
10046 : : }
10047 : 21 : get_rule_expr((Node *) minmaxexpr->args, context, true);
10048 : 21 : appendStringInfoChar(buf, ')');
10049 : : }
10050 : 21 : break;
10051 : :
1033 michael@paquier.xyz 10052 : 359 : case T_SQLValueFunction:
10053 : : {
10054 : 359 : SQLValueFunction *svf = (SQLValueFunction *) node;
10055 : :
10056 : : /*
10057 : : * Note: this code knows that typmod for time, timestamp, and
10058 : : * timestamptz just prints as integer.
10059 : : */
10060 [ + + + + : 359 : switch (svf->op)
+ + + + +
+ + + + +
+ - ]
10061 : : {
10062 : 52 : case SVFOP_CURRENT_DATE:
10063 : 52 : appendStringInfoString(buf, "CURRENT_DATE");
10064 : 52 : break;
10065 : 6 : case SVFOP_CURRENT_TIME:
10066 : 6 : appendStringInfoString(buf, "CURRENT_TIME");
10067 : 6 : break;
10068 : 6 : case SVFOP_CURRENT_TIME_N:
10069 : 6 : appendStringInfo(buf, "CURRENT_TIME(%d)", svf->typmod);
10070 : 6 : break;
10071 : 6 : case SVFOP_CURRENT_TIMESTAMP:
10072 : 6 : appendStringInfoString(buf, "CURRENT_TIMESTAMP");
10073 : 6 : break;
10074 : 64 : case SVFOP_CURRENT_TIMESTAMP_N:
10075 : 64 : appendStringInfo(buf, "CURRENT_TIMESTAMP(%d)",
10076 : : svf->typmod);
10077 : 64 : break;
10078 : 6 : case SVFOP_LOCALTIME:
10079 : 6 : appendStringInfoString(buf, "LOCALTIME");
10080 : 6 : break;
10081 : 6 : case SVFOP_LOCALTIME_N:
10082 : 6 : appendStringInfo(buf, "LOCALTIME(%d)", svf->typmod);
10083 : 6 : break;
10084 : 15 : case SVFOP_LOCALTIMESTAMP:
10085 : 15 : appendStringInfoString(buf, "LOCALTIMESTAMP");
10086 : 15 : break;
10087 : 9 : case SVFOP_LOCALTIMESTAMP_N:
10088 : 9 : appendStringInfo(buf, "LOCALTIMESTAMP(%d)",
10089 : : svf->typmod);
10090 : 9 : break;
10091 : 6 : case SVFOP_CURRENT_ROLE:
10092 : 6 : appendStringInfoString(buf, "CURRENT_ROLE");
10093 : 6 : break;
10094 : 148 : case SVFOP_CURRENT_USER:
10095 : 148 : appendStringInfoString(buf, "CURRENT_USER");
10096 : 148 : break;
10097 : 6 : case SVFOP_USER:
10098 : 6 : appendStringInfoString(buf, "USER");
10099 : 6 : break;
10100 : 17 : case SVFOP_SESSION_USER:
10101 : 17 : appendStringInfoString(buf, "SESSION_USER");
10102 : 17 : break;
10103 : 6 : case SVFOP_CURRENT_CATALOG:
10104 : 6 : appendStringInfoString(buf, "CURRENT_CATALOG");
10105 : 6 : break;
10106 : 6 : case SVFOP_CURRENT_SCHEMA:
10107 : 6 : appendStringInfoString(buf, "CURRENT_SCHEMA");
10108 : 6 : break;
10109 : : }
10110 : : }
10111 : 359 : break;
10112 : :
7021 tgl@sss.pgh.pa.us 10113 : 88 : case T_XmlExpr:
10114 : : {
6695 bruce@momjian.us 10115 : 88 : XmlExpr *xexpr = (XmlExpr *) node;
10116 : 88 : bool needcomma = false;
10117 : : ListCell *arg;
10118 : : ListCell *narg;
10119 : : Const *con;
10120 : :
7021 tgl@sss.pgh.pa.us 10121 [ + + + + : 88 : switch (xexpr->op)
+ + + -
- ]
10122 : : {
10123 : 8 : case IS_XMLCONCAT:
10124 : 8 : appendStringInfoString(buf, "XMLCONCAT(");
10125 : 8 : break;
10126 : 16 : case IS_XMLELEMENT:
10127 : 16 : appendStringInfoString(buf, "XMLELEMENT(");
10128 : 16 : break;
10129 : 8 : case IS_XMLFOREST:
10130 : 8 : appendStringInfoString(buf, "XMLFOREST(");
10131 : 8 : break;
10132 : 8 : case IS_XMLPARSE:
10133 : 8 : appendStringInfoString(buf, "XMLPARSE(");
10134 : 8 : break;
10135 : 8 : case IS_XMLPI:
10136 : 8 : appendStringInfoString(buf, "XMLPI(");
10137 : 8 : break;
10138 : 8 : case IS_XMLROOT:
10139 : 8 : appendStringInfoString(buf, "XMLROOT(");
10140 : 8 : break;
6980 peter_e@gmx.net 10141 : 32 : case IS_XMLSERIALIZE:
10142 : 32 : appendStringInfoString(buf, "XMLSERIALIZE(");
10143 : 32 : break;
7000 peter_e@gmx.net 10144 :UBC 0 : case IS_DOCUMENT:
10145 : 0 : break;
10146 : : }
6980 peter_e@gmx.net 10147 [ + + + + ]:CBC 88 : if (xexpr->op == IS_XMLPARSE || xexpr->op == IS_XMLSERIALIZE)
10148 : : {
10149 [ + + ]: 40 : if (xexpr->xmloption == XMLOPTION_DOCUMENT)
10150 : 16 : appendStringInfoString(buf, "DOCUMENT ");
10151 : : else
10152 : 24 : appendStringInfoString(buf, "CONTENT ");
10153 : : }
7021 tgl@sss.pgh.pa.us 10154 [ + + ]: 88 : if (xexpr->name)
10155 : : {
10156 : 24 : appendStringInfo(buf, "NAME %s",
7016 peter_e@gmx.net 10157 : 24 : quote_identifier(map_xml_name_to_sql_identifier(xexpr->name)));
7021 tgl@sss.pgh.pa.us 10158 : 24 : needcomma = true;
10159 : : }
10160 [ + + ]: 88 : if (xexpr->named_args)
10161 : : {
10162 [ + + ]: 16 : if (xexpr->op != IS_XMLFOREST)
10163 : : {
10164 [ + - ]: 8 : if (needcomma)
10165 : 8 : appendStringInfoString(buf, ", ");
10166 : 8 : appendStringInfoString(buf, "XMLATTRIBUTES(");
10167 : 8 : needcomma = false;
10168 : : }
10169 [ + - + + : 56 : forboth(arg, xexpr->named_args, narg, xexpr->arg_names)
+ - + + +
+ + - +
+ ]
10170 : : {
6695 bruce@momjian.us 10171 : 40 : Node *e = (Node *) lfirst(arg);
10172 : 40 : char *argname = strVal(lfirst(narg));
10173 : :
7021 tgl@sss.pgh.pa.us 10174 [ + + ]: 40 : if (needcomma)
10175 : 24 : appendStringInfoString(buf, ", ");
103 peter@eisentraut.org 10176 :GNC 40 : get_rule_expr(e, context, true);
7021 tgl@sss.pgh.pa.us 10177 :CBC 40 : appendStringInfo(buf, " AS %s",
7016 peter_e@gmx.net 10178 : 40 : quote_identifier(map_xml_name_to_sql_identifier(argname)));
7021 tgl@sss.pgh.pa.us 10179 : 40 : needcomma = true;
10180 : : }
10181 [ + + ]: 16 : if (xexpr->op != IS_XMLFOREST)
10182 : 8 : appendStringInfoChar(buf, ')');
10183 : : }
10184 [ + + ]: 88 : if (xexpr->args)
10185 : : {
10186 [ + + ]: 80 : if (needcomma)
10187 : 24 : appendStringInfoString(buf, ", ");
10188 [ + + + - : 80 : switch (xexpr->op)
- ]
10189 : : {
10190 : 64 : case IS_XMLCONCAT:
10191 : : case IS_XMLELEMENT:
10192 : : case IS_XMLFOREST:
10193 : : case IS_XMLPI:
10194 : : case IS_XMLSERIALIZE:
10195 : : /* no extra decoration needed */
10196 : 64 : get_rule_expr((Node *) xexpr->args, context, true);
10197 : 64 : break;
10198 : 8 : case IS_XMLPARSE:
6980 peter_e@gmx.net 10199 [ - + ]: 8 : Assert(list_length(xexpr->args) == 2);
10200 : :
7021 tgl@sss.pgh.pa.us 10201 : 8 : get_rule_expr((Node *) linitial(xexpr->args),
10202 : : context, true);
10203 : :
3261 10204 : 8 : con = lsecond_node(Const, xexpr->args);
7021 10205 [ - + ]: 8 : Assert(!con->constisnull);
10206 [ - + ]: 8 : if (DatumGetBool(con->constvalue))
7021 tgl@sss.pgh.pa.us 10207 :UBC 0 : appendStringInfoString(buf,
10208 : : " PRESERVE WHITESPACE");
10209 : : else
7021 tgl@sss.pgh.pa.us 10210 :CBC 8 : appendStringInfoString(buf,
10211 : : " STRIP WHITESPACE");
10212 : 8 : break;
10213 : 8 : case IS_XMLROOT:
10214 [ - + ]: 8 : Assert(list_length(xexpr->args) == 3);
10215 : :
10216 : 8 : get_rule_expr((Node *) linitial(xexpr->args),
10217 : : context, true);
10218 : :
10219 : 8 : appendStringInfoString(buf, ", VERSION ");
10220 : 8 : con = (Const *) lsecond(xexpr->args);
10221 [ + - ]: 8 : if (IsA(con, Const) &&
10222 [ + - ]: 8 : con->constisnull)
10223 : 8 : appendStringInfoString(buf, "NO VALUE");
10224 : : else
7021 tgl@sss.pgh.pa.us 10225 :UBC 0 : get_rule_expr((Node *) con, context, false);
10226 : :
3261 tgl@sss.pgh.pa.us 10227 :CBC 8 : con = lthird_node(Const, xexpr->args);
7021 10228 [ + - ]: 8 : if (con->constisnull)
10229 : : /* suppress STANDALONE NO VALUE */ ;
10230 : : else
10231 : : {
6980 peter_e@gmx.net 10232 [ + - - - ]: 8 : switch (DatumGetInt32(con->constvalue))
10233 : : {
10234 : 8 : case XML_STANDALONE_YES:
10235 : 8 : appendStringInfoString(buf,
10236 : : ", STANDALONE YES");
10237 : 8 : break;
6980 peter_e@gmx.net 10238 :UBC 0 : case XML_STANDALONE_NO:
10239 : 0 : appendStringInfoString(buf,
10240 : : ", STANDALONE NO");
10241 : 0 : break;
10242 : 0 : case XML_STANDALONE_NO_VALUE:
10243 : 0 : appendStringInfoString(buf,
10244 : : ", STANDALONE NO VALUE");
10245 : 0 : break;
10246 : 0 : default:
10247 : 0 : break;
10248 : : }
10249 : : }
7021 tgl@sss.pgh.pa.us 10250 :CBC 8 : break;
7000 peter_e@gmx.net 10251 :UBC 0 : case IS_DOCUMENT:
10252 : 0 : get_rule_expr_paren((Node *) xexpr->args, context, false, node);
10253 : 0 : break;
10254 : : }
10255 : : }
6980 peter_e@gmx.net 10256 [ + + ]:CBC 88 : if (xexpr->op == IS_XMLSERIALIZE)
10257 : : {
5475 tgl@sss.pgh.pa.us 10258 : 32 : appendStringInfo(buf, " AS %s",
10259 : : format_type_with_typemod(xexpr->type,
10260 : : xexpr->typmod));
387 michael@paquier.xyz 10261 [ + + ]: 32 : if (xexpr->indent)
10262 : 8 : appendStringInfoString(buf, " INDENT");
10263 : : else
10264 : 24 : appendStringInfoString(buf, " NO INDENT");
10265 : : }
10266 : :
7000 peter_e@gmx.net 10267 [ - + ]: 88 : if (xexpr->op == IS_DOCUMENT)
7000 peter_e@gmx.net 10268 :UBC 0 : appendStringInfoString(buf, " IS DOCUMENT");
10269 : : else
7000 peter_e@gmx.net 10270 :CBC 88 : appendStringInfoChar(buf, ')');
10271 : : }
7021 tgl@sss.pgh.pa.us 10272 : 88 : break;
10273 : :
9035 10274 : 1385 : case T_NullTest:
10275 : : {
8907 bruce@momjian.us 10276 : 1385 : NullTest *ntest = (NullTest *) node;
10277 : :
8264 tgl@sss.pgh.pa.us 10278 [ + + ]: 1385 : if (!PRETTY_PAREN(context))
8259 bruce@momjian.us 10279 : 1355 : appendStringInfoChar(buf, '(');
8264 tgl@sss.pgh.pa.us 10280 : 1385 : get_rule_expr_paren((Node *) ntest->arg, context, true, node);
10281 : :
10282 : : /*
10283 : : * For scalar inputs, we prefer to print as IS [NOT] NULL,
10284 : : * which is shorter and traditional. If it's a rowtype input
10285 : : * but we're applying a scalar test, must print IS [NOT]
10286 : : * DISTINCT FROM NULL to be semantically correct.
10287 : : */
3517 10288 [ + + ]: 1385 : if (ntest->argisrow ||
10289 [ + + ]: 1354 : !type_is_rowtype(exprType((Node *) ntest->arg)))
10290 : : {
10291 [ + + - ]: 1370 : switch (ntest->nulltesttype)
10292 : : {
10293 : 435 : case IS_NULL:
10294 : 435 : appendStringInfoString(buf, " IS NULL");
10295 : 435 : break;
10296 : 935 : case IS_NOT_NULL:
10297 : 935 : appendStringInfoString(buf, " IS NOT NULL");
10298 : 935 : break;
3517 tgl@sss.pgh.pa.us 10299 :UBC 0 : default:
10300 [ # # ]: 0 : elog(ERROR, "unrecognized nulltesttype: %d",
10301 : : (int) ntest->nulltesttype);
10302 : : }
10303 : : }
10304 : : else
10305 : : {
3517 tgl@sss.pgh.pa.us 10306 [ + + - ]:CBC 15 : switch (ntest->nulltesttype)
10307 : : {
10308 : 6 : case IS_NULL:
10309 : 6 : appendStringInfoString(buf, " IS NOT DISTINCT FROM NULL");
10310 : 6 : break;
10311 : 9 : case IS_NOT_NULL:
10312 : 9 : appendStringInfoString(buf, " IS DISTINCT FROM NULL");
10313 : 9 : break;
3517 tgl@sss.pgh.pa.us 10314 :UBC 0 : default:
10315 [ # # ]: 0 : elog(ERROR, "unrecognized nulltesttype: %d",
10316 : : (int) ntest->nulltesttype);
10317 : : }
10318 : : }
8264 tgl@sss.pgh.pa.us 10319 [ + + ]:CBC 1385 : if (!PRETTY_PAREN(context))
8259 bruce@momjian.us 10320 : 1355 : appendStringInfoChar(buf, ')');
10321 : : }
9035 tgl@sss.pgh.pa.us 10322 : 1385 : break;
10323 : :
10324 : 156 : case T_BooleanTest:
10325 : : {
8907 bruce@momjian.us 10326 : 156 : BooleanTest *btest = (BooleanTest *) node;
10327 : :
8264 tgl@sss.pgh.pa.us 10328 [ + - ]: 156 : if (!PRETTY_PAREN(context))
8259 bruce@momjian.us 10329 : 156 : appendStringInfoChar(buf, '(');
8264 tgl@sss.pgh.pa.us 10330 : 156 : get_rule_expr_paren((Node *) btest->arg, context, false, node);
8907 bruce@momjian.us 10331 [ + + - + : 156 : switch (btest->booltesttype)
+ + - ]
10332 : : {
10333 : 18 : case IS_TRUE:
4518 rhaas@postgresql.org 10334 : 18 : appendStringInfoString(buf, " IS TRUE");
9035 tgl@sss.pgh.pa.us 10335 : 18 : break;
8907 bruce@momjian.us 10336 : 69 : case IS_NOT_TRUE:
4518 rhaas@postgresql.org 10337 : 69 : appendStringInfoString(buf, " IS NOT TRUE");
9035 tgl@sss.pgh.pa.us 10338 : 69 : break;
8907 bruce@momjian.us 10339 :UBC 0 : case IS_FALSE:
4518 rhaas@postgresql.org 10340 : 0 : appendStringInfoString(buf, " IS FALSE");
9035 tgl@sss.pgh.pa.us 10341 : 0 : break;
8907 bruce@momjian.us 10342 :CBC 27 : case IS_NOT_FALSE:
4518 rhaas@postgresql.org 10343 : 27 : appendStringInfoString(buf, " IS NOT FALSE");
9035 tgl@sss.pgh.pa.us 10344 : 27 : break;
8907 bruce@momjian.us 10345 : 15 : case IS_UNKNOWN:
4518 rhaas@postgresql.org 10346 : 15 : appendStringInfoString(buf, " IS UNKNOWN");
9035 tgl@sss.pgh.pa.us 10347 : 15 : break;
8907 bruce@momjian.us 10348 : 27 : case IS_NOT_UNKNOWN:
4518 rhaas@postgresql.org 10349 : 27 : appendStringInfoString(buf, " IS NOT UNKNOWN");
9035 tgl@sss.pgh.pa.us 10350 : 27 : break;
8907 bruce@momjian.us 10351 :UBC 0 : default:
8267 tgl@sss.pgh.pa.us 10352 [ # # ]: 0 : elog(ERROR, "unrecognized booltesttype: %d",
10353 : : (int) btest->booltesttype);
10354 : : }
8264 tgl@sss.pgh.pa.us 10355 [ + - ]:CBC 156 : if (!PRETTY_PAREN(context))
8259 bruce@momjian.us 10356 : 156 : appendStringInfoChar(buf, ')');
10357 : : }
9035 tgl@sss.pgh.pa.us 10358 : 156 : break;
10359 : :
8441 10360 : 49 : case T_CoerceToDomain:
10361 : : {
10362 : 49 : CoerceToDomain *ctest = (CoerceToDomain *) node;
8259 bruce@momjian.us 10363 : 49 : Node *arg = (Node *) ctest->arg;
10364 : :
8441 tgl@sss.pgh.pa.us 10365 [ + + ]: 49 : if (ctest->coercionformat == COERCE_IMPLICIT_CAST &&
10366 [ + + ]: 24 : !showimplicit)
10367 : : {
10368 : : /* don't show the implicit cast */
10369 : 16 : get_rule_expr(arg, context, false);
10370 : : }
10371 : : else
10372 : : {
6938 10373 : 33 : get_coercion_expr(arg, context,
10374 : : ctest->resulttype,
10375 : : ctest->resulttypmod,
10376 : : node);
10377 : : }
10378 : : }
8597 10379 : 49 : break;
10380 : :
8441 10381 : 219 : case T_CoerceToDomainValue:
4518 rhaas@postgresql.org 10382 : 219 : appendStringInfoString(buf, "VALUE");
8769 tgl@sss.pgh.pa.us 10383 : 219 : break;
10384 : :
8291 10385 : 38 : case T_SetToDefault:
4518 rhaas@postgresql.org 10386 : 38 : appendStringInfoString(buf, "DEFAULT");
8291 tgl@sss.pgh.pa.us 10387 : 38 : break;
10388 : :
6852 10389 : 12 : case T_CurrentOfExpr:
10390 : : {
10391 : 12 : CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
10392 : :
10393 [ + - ]: 12 : if (cexpr->cursor_name)
10394 : 12 : appendStringInfo(buf, "CURRENT OF %s",
10395 : 12 : quote_identifier(cexpr->cursor_name));
10396 : : else
6852 tgl@sss.pgh.pa.us 10397 :UBC 0 : appendStringInfo(buf, "CURRENT OF $%d",
10398 : : cexpr->cursor_param);
10399 : : }
6852 tgl@sss.pgh.pa.us 10400 :CBC 12 : break;
10401 : :
3166 tgl@sss.pgh.pa.us 10402 :UBC 0 : case T_NextValueExpr:
10403 : : {
10404 : 0 : NextValueExpr *nvexpr = (NextValueExpr *) node;
10405 : :
10406 : : /*
10407 : : * This isn't exactly nextval(), but that seems close enough
10408 : : * for EXPLAIN's purposes.
10409 : : */
10410 : 0 : appendStringInfoString(buf, "nextval(");
10411 : 0 : simple_quote_literal(buf,
10412 : 0 : generate_relation_name(nvexpr->seqid,
10413 : : NIL));
10414 : 0 : appendStringInfoChar(buf, ')');
10415 : : }
10416 : 0 : break;
10417 : :
3953 andres@anarazel.de 10418 :CBC 15 : case T_InferenceElem:
10419 : : {
3949 bruce@momjian.us 10420 : 15 : InferenceElem *iexpr = (InferenceElem *) node;
10421 : : bool save_varprefix;
10422 : : bool need_parens;
10423 : :
10424 : : /*
10425 : : * InferenceElem can only refer to target relation, so a
10426 : : * prefix is not useful, and indeed would cause parse errors.
10427 : : */
3689 tgl@sss.pgh.pa.us 10428 : 15 : save_varprefix = context->varprefix;
3953 andres@anarazel.de 10429 : 15 : context->varprefix = false;
10430 : :
10431 : : /*
10432 : : * Parenthesize the element unless it's a simple Var or a bare
10433 : : * function call. Follows pg_get_indexdef_worker().
10434 : : */
10435 : 15 : need_parens = !IsA(iexpr->expr, Var);
10436 [ - + ]: 15 : if (IsA(iexpr->expr, FuncExpr) &&
3953 andres@anarazel.de 10437 [ # # ]:UBC 0 : ((FuncExpr *) iexpr->expr)->funcformat ==
10438 : : COERCE_EXPLICIT_CALL)
10439 : 0 : need_parens = false;
10440 : :
3953 andres@anarazel.de 10441 [ - + ]:CBC 15 : if (need_parens)
3953 andres@anarazel.de 10442 :UBC 0 : appendStringInfoChar(buf, '(');
3953 andres@anarazel.de 10443 :CBC 15 : get_rule_expr((Node *) iexpr->expr,
10444 : : context, false);
10445 [ - + ]: 15 : if (need_parens)
3953 andres@anarazel.de 10446 :UBC 0 : appendStringInfoChar(buf, ')');
10447 : :
3689 tgl@sss.pgh.pa.us 10448 :CBC 15 : context->varprefix = save_varprefix;
10449 : :
3953 andres@anarazel.de 10450 [ + + ]: 15 : if (iexpr->infercollid)
10451 : 6 : appendStringInfo(buf, " COLLATE %s",
10452 : : generate_collation_name(iexpr->infercollid));
10453 : :
10454 : : /* Add the operator class name, if not default */
10455 [ + + ]: 15 : if (iexpr->inferopclass)
10456 : : {
3949 bruce@momjian.us 10457 : 6 : Oid inferopclass = iexpr->inferopclass;
10458 : 6 : Oid inferopcinputtype = get_opclass_input_type(iexpr->inferopclass);
10459 : :
3953 andres@anarazel.de 10460 : 6 : get_opclass_name(inferopclass, inferopcinputtype, buf);
10461 : : }
10462 : : }
10463 : 15 : break;
10464 : :
423 dean.a.rasheed@gmail 10465 : 6 : case T_ReturningExpr:
10466 : : {
10467 : 6 : ReturningExpr *retExpr = (ReturningExpr *) node;
10468 : :
10469 : : /*
10470 : : * We cannot see a ReturningExpr in rule deparsing, only while
10471 : : * EXPLAINing a query plan (ReturningExpr nodes are only ever
10472 : : * adding during query rewriting). Just display the expression
10473 : : * returned (an expanded view column).
10474 : : */
10475 : 6 : get_rule_expr((Node *) retExpr->retexpr, context, showimplicit);
10476 : : }
10477 : 6 : break;
10478 : :
3385 rhaas@postgresql.org 10479 : 2365 : case T_PartitionBoundSpec:
10480 : : {
10481 : 2365 : PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
10482 : : ListCell *cell;
10483 : : char *sep;
10484 : :
3110 10485 [ + + ]: 2365 : if (spec->is_default)
10486 : : {
10487 : 132 : appendStringInfoString(buf, "DEFAULT");
10488 : 132 : break;
10489 : : }
10490 : :
3385 10491 [ + + + - ]: 2233 : switch (spec->strategy)
10492 : : {
3048 10493 : 153 : case PARTITION_STRATEGY_HASH:
10494 [ + - - + ]: 153 : Assert(spec->modulus > 0 && spec->remainder >= 0);
10495 [ - + ]: 153 : Assert(spec->modulus > spec->remainder);
10496 : :
10497 : 153 : appendStringInfoString(buf, "FOR VALUES");
10498 : 153 : appendStringInfo(buf, " WITH (modulus %d, remainder %d)",
10499 : : spec->modulus, spec->remainder);
10500 : 153 : break;
10501 : :
3385 10502 : 726 : case PARTITION_STRATEGY_LIST:
10503 [ - + ]: 726 : Assert(spec->listdatums != NIL);
10504 : :
3213 tgl@sss.pgh.pa.us 10505 : 726 : appendStringInfoString(buf, "FOR VALUES IN (");
3385 rhaas@postgresql.org 10506 : 726 : sep = "";
3337 10507 [ + - + + : 2013 : foreach(cell, spec->listdatums)
+ + ]
10508 : : {
1700 peter@eisentraut.org 10509 : 1287 : Const *val = lfirst_node(Const, cell);
10510 : :
3385 rhaas@postgresql.org 10511 : 1287 : appendStringInfoString(buf, sep);
10512 : 1287 : get_const_expr(val, context, -1);
10513 : 1287 : sep = ", ";
10514 : : }
10515 : :
3134 peter_e@gmx.net 10516 : 726 : appendStringInfoChar(buf, ')');
3385 rhaas@postgresql.org 10517 : 726 : break;
10518 : :
10519 : 1354 : case PARTITION_STRATEGY_RANGE:
10520 [ + - + - : 1354 : Assert(spec->lowerdatums != NIL &&
- + ]
10521 : : spec->upperdatums != NIL &&
10522 : : list_length(spec->lowerdatums) ==
10523 : : list_length(spec->upperdatums));
10524 : :
3139 10525 : 1354 : appendStringInfo(buf, "FOR VALUES FROM %s TO %s",
10526 : : get_range_partbound_string(spec->lowerdatums),
10527 : : get_range_partbound_string(spec->upperdatums));
3385 10528 : 1354 : break;
10529 : :
3385 rhaas@postgresql.org 10530 :UBC 0 : default:
10531 [ # # ]: 0 : elog(ERROR, "unrecognized partition strategy: %d",
10532 : : (int) spec->strategy);
10533 : : break;
10534 : : }
10535 : : }
3385 rhaas@postgresql.org 10536 :CBC 2233 : break;
10537 : :
1082 alvherre@alvh.no-ip. 10538 : 75 : case T_JsonValueExpr:
10539 : : {
10540 : 75 : JsonValueExpr *jve = (JsonValueExpr *) node;
10541 : :
10542 : 75 : get_rule_expr((Node *) jve->raw_expr, context, false);
10543 : 75 : get_json_format(jve->format, context->buf);
10544 : : }
10545 : 75 : break;
10546 : :
10547 : 93 : case T_JsonConstructorExpr:
10548 : 93 : get_json_constructor((JsonConstructorExpr *) node, context, false);
10549 : 93 : break;
10550 : :
1080 10551 : 30 : case T_JsonIsPredicate:
10552 : : {
10553 : 30 : JsonIsPredicate *pred = (JsonIsPredicate *) node;
10554 : :
10555 [ + + ]: 30 : if (!PRETTY_PAREN(context))
10556 : 15 : appendStringInfoChar(context->buf, '(');
10557 : :
10558 : 30 : get_rule_expr_paren(pred->expr, context, true, node);
10559 : :
10560 : 30 : appendStringInfoString(context->buf, " IS JSON");
10561 : :
10562 : : /* TODO: handle FORMAT clause */
10563 : :
10564 [ + + + + ]: 30 : switch (pred->item_type)
10565 : : {
10566 : 6 : case JS_TYPE_SCALAR:
10567 : 6 : appendStringInfoString(context->buf, " SCALAR");
10568 : 6 : break;
10569 : 6 : case JS_TYPE_ARRAY:
10570 : 6 : appendStringInfoString(context->buf, " ARRAY");
10571 : 6 : break;
10572 : 6 : case JS_TYPE_OBJECT:
10573 : 6 : appendStringInfoString(context->buf, " OBJECT");
10574 : 6 : break;
10575 : 12 : default:
10576 : 12 : break;
10577 : : }
10578 : :
10579 [ + + ]: 30 : if (pred->unique_keys)
10580 : 6 : appendStringInfoString(context->buf, " WITH UNIQUE KEYS");
10581 : :
10582 [ + + ]: 30 : if (!PRETTY_PAREN(context))
10583 : 15 : appendStringInfoChar(context->buf, ')');
10584 : : }
10585 : 30 : break;
10586 : :
724 amitlan@postgresql.o 10587 : 30 : case T_JsonExpr:
10588 : : {
10589 : 30 : JsonExpr *jexpr = (JsonExpr *) node;
10590 : :
10591 [ + + + - ]: 30 : switch (jexpr->op)
10592 : : {
10593 : 6 : case JSON_EXISTS_OP:
10594 : 6 : appendStringInfoString(buf, "JSON_EXISTS(");
10595 : 6 : break;
10596 : 18 : case JSON_QUERY_OP:
10597 : 18 : appendStringInfoString(buf, "JSON_QUERY(");
10598 : 18 : break;
10599 : 6 : case JSON_VALUE_OP:
10600 : 6 : appendStringInfoString(buf, "JSON_VALUE(");
10601 : 6 : break;
724 amitlan@postgresql.o 10602 :UBC 0 : default:
10603 [ # # ]: 0 : elog(ERROR, "unrecognized JsonExpr op: %d",
10604 : : (int) jexpr->op);
10605 : : }
10606 : :
724 amitlan@postgresql.o 10607 :CBC 30 : get_rule_expr(jexpr->formatted_expr, context, showimplicit);
10608 : :
10609 : 30 : appendStringInfoString(buf, ", ");
10610 : :
10611 : 30 : get_json_path_spec(jexpr->path_spec, context, showimplicit);
10612 : :
10613 [ + + ]: 30 : if (jexpr->passing_values)
10614 : : {
10615 : : ListCell *lc1,
10616 : : *lc2;
10617 : 6 : bool needcomma = false;
10618 : :
10619 : 6 : appendStringInfoString(buf, " PASSING ");
10620 : :
10621 [ + - + + : 24 : forboth(lc1, jexpr->passing_names,
+ - + + +
+ + - +
+ ]
10622 : : lc2, jexpr->passing_values)
10623 : : {
10624 [ + + ]: 18 : if (needcomma)
10625 : 12 : appendStringInfoString(buf, ", ");
10626 : 18 : needcomma = true;
10627 : :
10628 : 18 : get_rule_expr((Node *) lfirst(lc2), context, showimplicit);
10629 : 18 : appendStringInfo(buf, " AS %s",
427 dean.a.rasheed@gmail 10630 : 18 : quote_identifier(lfirst_node(String, lc1)->sval));
10631 : : }
10632 : : }
10633 : :
724 amitlan@postgresql.o 10634 [ + + ]: 30 : if (jexpr->op != JSON_EXISTS_OP ||
10635 [ - + ]: 6 : jexpr->returning->typid != BOOLOID)
10636 : 24 : get_json_returning(jexpr->returning, context->buf,
10637 : 24 : jexpr->op == JSON_QUERY_OP);
10638 : :
10639 : 30 : get_json_expr_options(jexpr, context,
10640 [ + + ]: 30 : jexpr->op != JSON_EXISTS_OP ?
10641 : : JSON_BEHAVIOR_NULL :
10642 : : JSON_BEHAVIOR_FALSE);
10643 : :
704 drowley@postgresql.o 10644 : 30 : appendStringInfoChar(buf, ')');
10645 : : }
724 amitlan@postgresql.o 10646 : 30 : break;
10647 : :
7829 tgl@sss.pgh.pa.us 10648 : 1377 : case T_List:
10649 : : {
10650 : : char *sep;
10651 : : ListCell *l;
10652 : :
10653 : 1377 : sep = "";
10654 [ + - + + : 3892 : foreach(l, (List *) node)
+ + ]
10655 : : {
7624 neilc@samurai.com 10656 : 2515 : appendStringInfoString(buf, sep);
7829 tgl@sss.pgh.pa.us 10657 : 2515 : get_rule_expr((Node *) lfirst(l), context, showimplicit);
10658 : 2515 : sep = ", ";
10659 : : }
10660 : : }
10661 : 1377 : break;
10662 : :
3294 alvherre@alvh.no-ip. 10663 : 36 : case T_TableFunc:
10664 : 36 : get_tablefunc((TableFunc *) node, context, showimplicit);
10665 : 36 : break;
10666 : :
10057 bruce@momjian.us 10667 :UBC 0 : default:
8267 tgl@sss.pgh.pa.us 10668 [ # # ]: 0 : elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
10669 : : break;
10670 : : }
10671 : : }
10672 : :
10673 : : /*
10674 : : * get_rule_expr_toplevel - Parse back a toplevel expression
10675 : : *
10676 : : * Same as get_rule_expr(), except that if the expr is just a Var, we pass
10677 : : * istoplevel = true not false to get_variable(). This causes whole-row Vars
10678 : : * to get printed with decoration that will prevent expansion of "*".
10679 : : * We need to use this in contexts such as ROW() and VALUES(), where the
10680 : : * parser would expand "foo.*" appearing at top level. (In principle we'd
10681 : : * use this in get_target_list() too, but that has additional worries about
10682 : : * whether to print AS, so it needs to invoke get_variable() directly anyway.)
10683 : : */
10684 : : static void
3773 tgl@sss.pgh.pa.us 10685 :CBC 1570 : get_rule_expr_toplevel(Node *node, deparse_context *context,
10686 : : bool showimplicit)
10687 : : {
10688 [ + - + + ]: 1570 : if (node && IsA(node, Var))
10689 : 649 : (void) get_variable((Var *) node, 0, true, context);
10690 : : else
10691 : 921 : get_rule_expr(node, context, showimplicit);
10692 : 1570 : }
10693 : :
10694 : : /*
10695 : : * get_rule_list_toplevel - Parse back a list of toplevel expressions
10696 : : *
10697 : : * Apply get_rule_expr_toplevel() to each element of a List.
10698 : : *
10699 : : * This adds commas between the expressions, but caller is responsible
10700 : : * for printing surrounding decoration.
10701 : : */
10702 : : static void
1522 10703 : 261 : get_rule_list_toplevel(List *lst, deparse_context *context,
10704 : : bool showimplicit)
10705 : : {
10706 : : const char *sep;
10707 : : ListCell *lc;
10708 : :
10709 : 261 : sep = "";
10710 [ + - + + : 886 : foreach(lc, lst)
+ + ]
10711 : : {
10712 : 625 : Node *e = (Node *) lfirst(lc);
10713 : :
10714 : 625 : appendStringInfoString(context->buf, sep);
10715 : 625 : get_rule_expr_toplevel(e, context, showimplicit);
10716 : 625 : sep = ", ";
10717 : : }
10718 : 261 : }
10719 : :
10720 : : /*
10721 : : * get_rule_expr_funccall - Parse back a function-call expression
10722 : : *
10723 : : * Same as get_rule_expr(), except that we guarantee that the output will
10724 : : * look like a function call, or like one of the things the grammar treats as
10725 : : * equivalent to a function call (see the func_expr_windowless production).
10726 : : * This is needed in places where the grammar uses func_expr_windowless and
10727 : : * you can't substitute a parenthesized a_expr. If what we have isn't going
10728 : : * to look like a function call, wrap it in a dummy CAST() expression, which
10729 : : * will satisfy the grammar --- and, indeed, is likely what the user wrote to
10730 : : * produce such a thing.
10731 : : */
10732 : : static void
3167 10733 : 444 : get_rule_expr_funccall(Node *node, deparse_context *context,
10734 : : bool showimplicit)
10735 : : {
10736 [ + + ]: 444 : if (looks_like_function(node))
10737 : 438 : get_rule_expr(node, context, showimplicit);
10738 : : else
10739 : : {
10740 : 6 : StringInfo buf = context->buf;
10741 : :
10742 : 6 : appendStringInfoString(buf, "CAST(");
10743 : : /* no point in showing any top-level implicit cast */
10744 : 6 : get_rule_expr(node, context, false);
10745 : 6 : appendStringInfo(buf, " AS %s)",
10746 : : format_type_with_typemod(exprType(node),
10747 : : exprTypmod(node)));
10748 : : }
10749 : 444 : }
10750 : :
10751 : : /*
10752 : : * Helper function to identify node types that satisfy func_expr_windowless.
10753 : : * If in doubt, "false" is always a safe answer.
10754 : : */
10755 : : static bool
10756 : 1077 : looks_like_function(Node *node)
10757 : : {
10758 [ - + ]: 1077 : if (node == NULL)
3167 tgl@sss.pgh.pa.us 10759 :UBC 0 : return false; /* probably shouldn't happen */
3167 tgl@sss.pgh.pa.us 10760 [ + + + ]:CBC 1077 : switch (nodeTag(node))
10761 : : {
10762 : 458 : case T_FuncExpr:
10763 : : /* OK, unless it's going to deparse as a cast */
1957 10764 [ + + ]: 467 : return (((FuncExpr *) node)->funcformat == COERCE_EXPLICIT_CALL ||
10765 [ + + ]: 9 : ((FuncExpr *) node)->funcformat == COERCE_SQL_SYNTAX);
3167 10766 : 54 : case T_NullIfExpr:
10767 : : case T_CoalesceExpr:
10768 : : case T_MinMaxExpr:
10769 : : case T_SQLValueFunction:
10770 : : case T_XmlExpr:
10771 : : case T_JsonExpr:
10772 : : /* these are all accepted by func_expr_common_subexpr */
10773 : 54 : return true;
10774 : 565 : default:
10775 : 565 : break;
10776 : : }
10777 : 565 : return false;
10778 : : }
10779 : :
10780 : :
10781 : : /*
10782 : : * get_oper_expr - Parse back an OpExpr node
10783 : : */
10784 : : static void
8255 bruce@momjian.us 10785 : 31643 : get_oper_expr(OpExpr *expr, deparse_context *context)
10786 : : {
8717 tgl@sss.pgh.pa.us 10787 : 31643 : StringInfo buf = context->buf;
8494 10788 : 31643 : Oid opno = expr->opno;
8717 10789 : 31643 : List *args = expr->args;
10790 : :
8264 10791 [ + + ]: 31643 : if (!PRETTY_PAREN(context))
8259 bruce@momjian.us 10792 : 30414 : appendStringInfoChar(buf, '(');
7959 neilc@samurai.com 10793 [ + + ]: 31643 : if (list_length(args) == 2)
10794 : : {
10795 : : /* binary operator */
7963 10796 : 31628 : Node *arg1 = (Node *) linitial(args);
8593 bruce@momjian.us 10797 : 31628 : Node *arg2 = (Node *) lsecond(args);
10798 : :
8259 10799 : 31628 : get_rule_expr_paren(arg1, context, true, (Node *) expr);
8717 tgl@sss.pgh.pa.us 10800 : 31628 : appendStringInfo(buf, " %s ",
10801 : : generate_operator_name(opno,
10802 : : exprType(arg1),
10803 : : exprType(arg2)));
8259 bruce@momjian.us 10804 : 31628 : get_rule_expr_paren(arg2, context, true, (Node *) expr);
10805 : : }
10806 : : else
10807 : : {
10808 : : /* prefix operator */
7963 neilc@samurai.com 10809 : 15 : Node *arg = (Node *) linitial(args);
10810 : :
2005 tgl@sss.pgh.pa.us 10811 : 15 : appendStringInfo(buf, "%s ",
10812 : : generate_operator_name(opno,
10813 : : InvalidOid,
10814 : : exprType(arg)));
10815 : 15 : get_rule_expr_paren(arg, context, true, (Node *) expr);
10816 : : }
8264 10817 [ + + ]: 31643 : if (!PRETTY_PAREN(context))
8259 bruce@momjian.us 10818 : 30414 : appendStringInfoChar(buf, ')');
8717 tgl@sss.pgh.pa.us 10819 : 31643 : }
10820 : :
10821 : : /*
10822 : : * get_func_expr - Parse back a FuncExpr node
10823 : : */
10824 : : static void
8255 bruce@momjian.us 10825 : 6513 : get_func_expr(FuncExpr *expr, deparse_context *context,
10826 : : bool showimplicit)
10827 : : {
9660 tgl@sss.pgh.pa.us 10828 : 6513 : StringInfo buf = context->buf;
8494 10829 : 6513 : Oid funcoid = expr->funcid;
10830 : : Oid argtypes[FUNC_MAX_ARGS];
10831 : : int nargs;
10832 : : List *argnames;
10833 : : bool use_variadic;
10834 : : ListCell *l;
10835 : :
10836 : : /*
10837 : : * If the function call came from an implicit coercion, then just show the
10838 : : * first argument --- unless caller wants to see implicit coercions.
10839 : : */
10840 [ + + + + ]: 6513 : if (expr->funcformat == COERCE_IMPLICIT_CAST && !showimplicit)
10841 : : {
7963 neilc@samurai.com 10842 : 672 : get_rule_expr_paren((Node *) linitial(expr->args), context,
10843 : : false, (Node *) expr);
8579 tgl@sss.pgh.pa.us 10844 : 1655 : return;
10845 : : }
10846 : :
10847 : : /*
10848 : : * If the function call came from a cast, then show the first argument
10849 : : * plus an explicit cast operation.
10850 : : */
8494 10851 [ + + ]: 5841 : if (expr->funcformat == COERCE_EXPLICIT_CAST ||
10852 [ + + ]: 5492 : expr->funcformat == COERCE_IMPLICIT_CAST)
10853 : : {
7963 neilc@samurai.com 10854 : 896 : Node *arg = linitial(expr->args);
8494 tgl@sss.pgh.pa.us 10855 : 896 : Oid rettype = expr->funcresulttype;
10856 : : int32 coercedTypmod;
10857 : :
10858 : : /* Get the typmod if this is a length-coercion function */
8579 10859 : 896 : (void) exprIsLengthCoercion((Node *) expr, &coercedTypmod);
10860 : :
6938 10861 : 896 : get_coercion_expr(arg, context,
10862 : : rettype, coercedTypmod,
10863 : : (Node *) expr);
10864 : :
9514 10865 : 896 : return;
10866 : : }
10867 : :
10868 : : /*
10869 : : * If the function was called using one of the SQL spec's random special
10870 : : * syntaxes, try to reproduce that. If we don't recognize the function,
10871 : : * fall through.
10872 : : */
1957 10873 [ + + ]: 4945 : if (expr->funcformat == COERCE_SQL_SYNTAX)
10874 : : {
10875 [ + + ]: 90 : if (get_func_sql_syntax(expr, context))
10876 : 87 : return;
10877 : : }
10878 : :
10879 : : /*
10880 : : * Normal function: display as proname(args). First we need to extract
10881 : : * the argument datatypes.
10882 : : */
6286 10883 [ - + ]: 4858 : if (list_length(expr->args) > FUNC_MAX_ARGS)
6286 tgl@sss.pgh.pa.us 10884 [ # # ]:UBC 0 : ereport(ERROR,
10885 : : (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
10886 : : errmsg("too many arguments")));
8717 tgl@sss.pgh.pa.us 10887 :CBC 4858 : nargs = 0;
6002 10888 : 4858 : argnames = NIL;
8717 10889 [ + + + + : 10131 : foreach(l, expr->args)
+ + ]
10890 : : {
5861 bruce@momjian.us 10891 : 5273 : Node *arg = (Node *) lfirst(l);
10892 : :
6002 tgl@sss.pgh.pa.us 10893 [ + + ]: 5273 : if (IsA(arg, NamedArgExpr))
10894 : 21 : argnames = lappend(argnames, ((NamedArgExpr *) arg)->name);
10895 : 5273 : argtypes[nargs] = exprType(arg);
8717 10896 : 5273 : nargs++;
10897 : : }
10898 : :
10899 : 4858 : appendStringInfo(buf, "%s(",
10900 : : generate_function_name(funcoid, nargs,
10901 : : argnames, argtypes,
4801 10902 : 4858 : expr->funcvariadic,
10903 : : &use_variadic,
563 10904 : 4858 : context->inGroupBy));
6451 10905 : 4858 : nargs = 0;
10906 [ + + + + : 10131 : foreach(l, expr->args)
+ + ]
10907 : : {
10908 [ + + ]: 5273 : if (nargs++ > 0)
10909 : 1014 : appendStringInfoString(buf, ", ");
2435 10910 [ + + + - ]: 5273 : if (use_variadic && lnext(expr->args, l) == NULL)
6451 10911 : 6 : appendStringInfoString(buf, "VARIADIC ");
10912 : 5273 : get_rule_expr((Node *) lfirst(l), context, true);
10913 : : }
9520 10914 : 4858 : appendStringInfoChar(buf, ')');
10915 : : }
10916 : :
10917 : : /*
10918 : : * get_agg_expr - Parse back an Aggref node
10919 : : */
10920 : : static void
1291 andrew@dunslane.net 10921 : 2385 : get_agg_expr(Aggref *aggref, deparse_context *context,
10922 : : Aggref *original_aggref)
10923 : : {
1082 alvherre@alvh.no-ip. 10924 : 2385 : get_agg_expr_helper(aggref, context, original_aggref, NULL, NULL,
10925 : : false);
10926 : 2385 : }
10927 : :
10928 : : /*
10929 : : * get_agg_expr_helper - subroutine for get_agg_expr and
10930 : : * get_json_agg_constructor
10931 : : */
10932 : : static void
10933 : 2412 : get_agg_expr_helper(Aggref *aggref, deparse_context *context,
10934 : : Aggref *original_aggref, const char *funcname,
10935 : : const char *options, bool is_json_objectagg)
10936 : : {
8739 tgl@sss.pgh.pa.us 10937 : 2412 : StringInfo buf = context->buf;
10938 : : Oid argtypes[FUNC_MAX_ARGS];
10939 : : int nargs;
1082 alvherre@alvh.no-ip. 10940 : 2412 : bool use_variadic = false;
10941 : :
10942 : : /*
10943 : : * For a combining aggregate, we look up and deparse the corresponding
10944 : : * partial aggregate instead. This is necessary because our input
10945 : : * argument list has been replaced; the new argument list always has just
10946 : : * one element, which will point to a partial Aggref that supplies us with
10947 : : * transition states to combine.
10948 : : */
3549 tgl@sss.pgh.pa.us 10949 [ + + ]: 2412 : if (DO_AGGSPLIT_COMBINE(aggref->aggsplit))
10950 : : {
10951 : : TargetEntry *tle;
10952 : :
3609 rhaas@postgresql.org 10953 [ - + ]: 391 : Assert(list_length(aggref->args) == 1);
2286 tgl@sss.pgh.pa.us 10954 : 391 : tle = linitial_node(TargetEntry, aggref->args);
10955 : 391 : resolve_special_varno((Node *) tle->expr, context,
10956 : : get_agg_combine_expr, original_aggref);
3609 rhaas@postgresql.org 10957 : 391 : return;
10958 : : }
10959 : :
10960 : : /*
10961 : : * Mark as PARTIAL, if appropriate. We look to the original aggref so as
10962 : : * to avoid printing this when recursing from the code just above.
10963 : : */
3549 tgl@sss.pgh.pa.us 10964 [ + + ]: 2021 : if (DO_AGGSPLIT_SKIPFINAL(original_aggref->aggsplit))
3609 rhaas@postgresql.org 10965 : 862 : appendStringInfoString(buf, "PARTIAL ");
10966 : :
10967 : : /* Extract the argument types as seen by the parser */
4465 tgl@sss.pgh.pa.us 10968 : 2021 : nargs = get_aggregate_argtypes(aggref, argtypes);
10969 : :
1082 alvherre@alvh.no-ip. 10970 [ + + ]: 2021 : if (!funcname)
10971 : 1994 : funcname = generate_function_name(aggref->aggfnoid, nargs, NIL,
10972 : 1994 : argtypes, aggref->aggvariadic,
10973 : : &use_variadic,
563 tgl@sss.pgh.pa.us 10974 : 1994 : context->inGroupBy);
10975 : :
10976 : : /* Print the aggregate name, schema-qualified if needed */
1082 alvherre@alvh.no-ip. 10977 : 2021 : appendStringInfo(buf, "%s(%s", funcname,
5934 tgl@sss.pgh.pa.us 10978 [ + + ]: 2021 : (aggref->aggdistinct != NIL) ? "DISTINCT " : "");
10979 : :
4465 10980 [ + + ]: 2021 : if (AGGKIND_IS_ORDERED_SET(aggref->aggkind))
10981 : : {
10982 : : /*
10983 : : * Ordered-set aggregates do not use "*" syntax. Also, we needn't
10984 : : * worry about inserting VARIADIC. So we can just dump the direct
10985 : : * args as-is.
10986 : : */
10987 [ - + ]: 14 : Assert(!aggref->aggvariadic);
10988 : 14 : get_rule_expr((Node *) aggref->aggdirectargs, context, true);
10989 [ - + ]: 14 : Assert(aggref->aggorder != NIL);
10990 : 14 : appendStringInfoString(buf, ") WITHIN GROUP (ORDER BY ");
10991 : 14 : get_rule_orderby(aggref->aggorder, aggref->args, false, context);
10992 : : }
10993 : : else
10994 : : {
10995 : : /* aggstar can be set only in zero-argument aggregates */
10996 [ + + ]: 2007 : if (aggref->aggstar)
10997 : 597 : appendStringInfoChar(buf, '*');
10998 : : else
10999 : : {
11000 : : ListCell *l;
11001 : : int i;
11002 : :
11003 : 1410 : i = 0;
11004 [ + - + + : 2914 : foreach(l, aggref->args)
+ + ]
11005 : : {
11006 : 1504 : TargetEntry *tle = (TargetEntry *) lfirst(l);
11007 : 1504 : Node *arg = (Node *) tle->expr;
11008 : :
11009 [ - + ]: 1504 : Assert(!IsA(arg, NamedArgExpr));
11010 [ + + ]: 1504 : if (tle->resjunk)
11011 : 25 : continue;
11012 [ + + ]: 1479 : if (i++ > 0)
11013 : : {
1082 alvherre@alvh.no-ip. 11014 [ + + ]: 69 : if (is_json_objectagg)
11015 : : {
11016 : : /*
11017 : : * the ABSENT ON NULL and WITH UNIQUE args are printed
11018 : : * separately, so ignore them here
11019 : : */
11020 [ - + ]: 15 : if (i > 2)
1082 alvherre@alvh.no-ip. 11021 :UBC 0 : break;
11022 : :
1082 alvherre@alvh.no-ip. 11023 :CBC 15 : appendStringInfoString(buf, " : ");
11024 : : }
11025 : : else
11026 : 54 : appendStringInfoString(buf, ", ");
11027 : : }
4465 tgl@sss.pgh.pa.us 11028 [ + + + - ]: 1479 : if (use_variadic && i == nargs)
11029 : 4 : appendStringInfoString(buf, "VARIADIC ");
11030 : 1479 : get_rule_expr(arg, context, true);
11031 : : }
11032 : : }
11033 : :
11034 [ + + ]: 2007 : if (aggref->aggorder != NIL)
11035 : : {
11036 : 44 : appendStringInfoString(buf, " ORDER BY ");
11037 : 44 : get_rule_orderby(aggref->aggorder, aggref->args, false, context);
11038 : : }
11039 : : }
11040 : :
1082 alvherre@alvh.no-ip. 11041 [ + + ]: 2021 : if (options)
11042 : 27 : appendStringInfoString(buf, options);
11043 : :
4625 noah@leadboat.com 11044 [ + + ]: 2021 : if (aggref->aggfilter != NULL)
11045 : : {
11046 : 23 : appendStringInfoString(buf, ") FILTER (WHERE ");
11047 : 23 : get_rule_expr((Node *) aggref->aggfilter, context, false);
11048 : : }
11049 : :
8739 tgl@sss.pgh.pa.us 11050 : 2021 : appendStringInfoChar(buf, ')');
11051 : : }
11052 : :
11053 : : /*
11054 : : * This is a helper function for get_agg_expr(). It's used when we deparse
11055 : : * a combining Aggref; resolve_special_varno locates the corresponding partial
11056 : : * Aggref and then calls this.
11057 : : */
11058 : : static void
2286 11059 : 391 : get_agg_combine_expr(Node *node, deparse_context *context, void *callback_arg)
11060 : : {
11061 : : Aggref *aggref;
11062 : 391 : Aggref *original_aggref = callback_arg;
11063 : :
3609 rhaas@postgresql.org 11064 [ - + ]: 391 : if (!IsA(node, Aggref))
3609 rhaas@postgresql.org 11065 [ # # ]:UBC 0 : elog(ERROR, "combining Aggref does not point to an Aggref");
11066 : :
3609 rhaas@postgresql.org 11067 :CBC 391 : aggref = (Aggref *) node;
11068 : 391 : get_agg_expr(aggref, context, original_aggref);
11069 : 391 : }
11070 : :
11071 : : /*
11072 : : * get_windowfunc_expr - Parse back a WindowFunc node
11073 : : */
11074 : : static void
1291 andrew@dunslane.net 11075 : 162 : get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
11076 : : {
1082 alvherre@alvh.no-ip. 11077 : 162 : get_windowfunc_expr_helper(wfunc, context, NULL, NULL, false);
11078 : 162 : }
11079 : :
11080 : :
11081 : : /*
11082 : : * get_windowfunc_expr_helper - subroutine for get_windowfunc_expr and
11083 : : * get_json_agg_constructor
11084 : : */
11085 : : static void
11086 : 168 : get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context,
11087 : : const char *funcname, const char *options,
11088 : : bool is_json_objectagg)
11089 : : {
6286 tgl@sss.pgh.pa.us 11090 : 168 : StringInfo buf = context->buf;
11091 : : Oid argtypes[FUNC_MAX_ARGS];
11092 : : int nargs;
11093 : : List *argnames;
11094 : : ListCell *l;
11095 : :
11096 [ - + ]: 168 : if (list_length(wfunc->args) > FUNC_MAX_ARGS)
6286 tgl@sss.pgh.pa.us 11097 [ # # ]:UBC 0 : ereport(ERROR,
11098 : : (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
11099 : : errmsg("too many arguments")));
6286 tgl@sss.pgh.pa.us 11100 :CBC 168 : nargs = 0;
4512 11101 : 168 : argnames = NIL;
6286 11102 [ + + + + : 285 : foreach(l, wfunc->args)
+ + ]
11103 : : {
5861 bruce@momjian.us 11104 : 117 : Node *arg = (Node *) lfirst(l);
11105 : :
4512 tgl@sss.pgh.pa.us 11106 [ - + ]: 117 : if (IsA(arg, NamedArgExpr))
4512 tgl@sss.pgh.pa.us 11107 :UBC 0 : argnames = lappend(argnames, ((NamedArgExpr *) arg)->name);
6002 tgl@sss.pgh.pa.us 11108 :CBC 117 : argtypes[nargs] = exprType(arg);
6286 11109 : 117 : nargs++;
11110 : : }
11111 : :
1082 alvherre@alvh.no-ip. 11112 [ + + ]: 168 : if (!funcname)
11113 : 162 : funcname = generate_function_name(wfunc->winfnoid, nargs, argnames,
11114 : : argtypes, false, NULL,
563 tgl@sss.pgh.pa.us 11115 : 162 : context->inGroupBy);
11116 : :
1082 alvherre@alvh.no-ip. 11117 : 168 : appendStringInfo(buf, "%s(", funcname);
11118 : :
11119 : : /* winstar can be set only in zero-argument aggregates */
6286 tgl@sss.pgh.pa.us 11120 [ + + ]: 168 : if (wfunc->winstar)
11121 : 12 : appendStringInfoChar(buf, '*');
11122 : : else
11123 : : {
1082 alvherre@alvh.no-ip. 11124 [ + + ]: 156 : if (is_json_objectagg)
11125 : : {
11126 : 3 : get_rule_expr((Node *) linitial(wfunc->args), context, false);
11127 : 3 : appendStringInfoString(buf, " : ");
11128 : 3 : get_rule_expr((Node *) lsecond(wfunc->args), context, false);
11129 : : }
11130 : : else
11131 : 153 : get_rule_expr((Node *) wfunc->args, context, true);
11132 : : }
11133 : :
11134 [ + + ]: 168 : if (options)
11135 : 6 : appendStringInfoString(buf, options);
11136 : :
4625 noah@leadboat.com 11137 [ - + ]: 168 : if (wfunc->aggfilter != NULL)
11138 : : {
4625 noah@leadboat.com 11139 :UBC 0 : appendStringInfoString(buf, ") FILTER (WHERE ");
11140 : 0 : get_rule_expr((Node *) wfunc->aggfilter, context, false);
11141 : : }
11142 : :
163 ishii@postgresql.org 11143 :GNC 168 : appendStringInfoString(buf, ") ");
11144 : :
11145 [ + + ]: 168 : if (wfunc->ignore_nulls == PARSER_IGNORE_NULLS)
11146 : 3 : appendStringInfoString(buf, "IGNORE NULLS ");
11147 : :
11148 : 168 : appendStringInfoString(buf, "OVER ");
11149 : :
369 tgl@sss.pgh.pa.us 11150 [ + + ]:CBC 168 : if (context->windowClause)
11151 : : {
11152 : : /* Query-decompilation case: search the windowClause list */
11153 [ + - + - : 30 : foreach(l, context->windowClause)
+ - ]
11154 : : {
11155 : 30 : WindowClause *wc = (WindowClause *) lfirst(l);
11156 : :
11157 [ + - ]: 30 : if (wc->winref == wfunc->winref)
11158 : : {
11159 [ + + ]: 30 : if (wc->name)
369 tgl@sss.pgh.pa.us 11160 :GBC 9 : appendStringInfoString(buf, quote_identifier(wc->name));
11161 : : else
369 tgl@sss.pgh.pa.us 11162 :CBC 21 : get_rule_windowspec(wc, context->targetList, context);
11163 : 30 : break;
11164 : : }
11165 : : }
11166 [ - + ]: 30 : if (l == NULL)
6286 tgl@sss.pgh.pa.us 11167 [ # # ]:UBC 0 : elog(ERROR, "could not find window clause for winref %u",
11168 : : wfunc->winref);
11169 : : }
11170 : : else
11171 : : {
11172 : : /*
11173 : : * In EXPLAIN, search the namespace stack for a matching WindowAgg
11174 : : * node (probably it's always the first entry), and print winname.
11175 : : */
369 tgl@sss.pgh.pa.us 11176 [ + - + - :CBC 138 : foreach(l, context->namespaces)
+ - ]
11177 : : {
11178 : 138 : deparse_namespace *dpns = (deparse_namespace *) lfirst(l);
11179 : :
11180 [ + - + - ]: 138 : if (dpns->plan && IsA(dpns->plan, WindowAgg))
11181 : : {
11182 : 138 : WindowAgg *wagg = (WindowAgg *) dpns->plan;
11183 : :
11184 [ + - ]: 138 : if (wagg->winref == wfunc->winref)
11185 : : {
11186 : 138 : appendStringInfoString(buf, quote_identifier(wagg->winname));
11187 : 138 : break;
11188 : : }
11189 : : }
11190 : : }
11191 [ - + ]: 138 : if (l == NULL)
369 tgl@sss.pgh.pa.us 11192 [ # # ]:UBC 0 : elog(ERROR, "could not find window clause for winref %u",
11193 : : wfunc->winref);
11194 : : }
6286 tgl@sss.pgh.pa.us 11195 :CBC 168 : }
11196 : :
11197 : : /*
11198 : : * get_func_sql_syntax - Parse back a SQL-syntax function call
11199 : : *
11200 : : * Returns true if we successfully deparsed, false if we did not
11201 : : * recognize the function.
11202 : : */
11203 : : static bool
1957 11204 : 90 : get_func_sql_syntax(FuncExpr *expr, deparse_context *context)
11205 : : {
11206 : 90 : StringInfo buf = context->buf;
11207 : 90 : Oid funcoid = expr->funcid;
11208 : :
11209 [ + + + + : 90 : switch (funcoid)
+ + + + +
+ + + + +
+ - + ]
11210 : : {
11211 : 12 : case F_TIMEZONE_INTERVAL_TIMESTAMP:
11212 : : case F_TIMEZONE_INTERVAL_TIMESTAMPTZ:
11213 : : case F_TIMEZONE_INTERVAL_TIMETZ:
11214 : : case F_TIMEZONE_TEXT_TIMESTAMP:
11215 : : case F_TIMEZONE_TEXT_TIMESTAMPTZ:
11216 : : case F_TIMEZONE_TEXT_TIMETZ:
11217 : : /* AT TIME ZONE ... note reversed argument order */
11218 : 12 : appendStringInfoChar(buf, '(');
1200 11219 : 12 : get_rule_expr_paren((Node *) lsecond(expr->args), context, false,
11220 : : (Node *) expr);
1957 11221 : 12 : appendStringInfoString(buf, " AT TIME ZONE ");
1200 11222 : 12 : get_rule_expr_paren((Node *) linitial(expr->args), context, false,
11223 : : (Node *) expr);
1957 11224 : 12 : appendStringInfoChar(buf, ')');
11225 : 12 : return true;
11226 : :
884 michael@paquier.xyz 11227 : 9 : case F_TIMEZONE_TIMESTAMP:
11228 : : case F_TIMEZONE_TIMESTAMPTZ:
11229 : : case F_TIMEZONE_TIMETZ:
11230 : : /* AT LOCAL */
11231 : 9 : appendStringInfoChar(buf, '(');
11232 : 9 : get_rule_expr_paren((Node *) linitial(expr->args), context, false,
11233 : : (Node *) expr);
11234 : 9 : appendStringInfoString(buf, " AT LOCAL)");
11235 : 9 : return true;
11236 : :
1957 tgl@sss.pgh.pa.us 11237 : 3 : case F_OVERLAPS_TIMESTAMPTZ_INTERVAL_TIMESTAMPTZ_INTERVAL:
11238 : : case F_OVERLAPS_TIMESTAMPTZ_INTERVAL_TIMESTAMPTZ_TIMESTAMPTZ:
11239 : : case F_OVERLAPS_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ_INTERVAL:
11240 : : case F_OVERLAPS_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ:
11241 : : case F_OVERLAPS_TIMESTAMP_INTERVAL_TIMESTAMP_INTERVAL:
11242 : : case F_OVERLAPS_TIMESTAMP_INTERVAL_TIMESTAMP_TIMESTAMP:
11243 : : case F_OVERLAPS_TIMESTAMP_TIMESTAMP_TIMESTAMP_INTERVAL:
11244 : : case F_OVERLAPS_TIMESTAMP_TIMESTAMP_TIMESTAMP_TIMESTAMP:
11245 : : case F_OVERLAPS_TIMETZ_TIMETZ_TIMETZ_TIMETZ:
11246 : : case F_OVERLAPS_TIME_INTERVAL_TIME_INTERVAL:
11247 : : case F_OVERLAPS_TIME_INTERVAL_TIME_TIME:
11248 : : case F_OVERLAPS_TIME_TIME_TIME_INTERVAL:
11249 : : case F_OVERLAPS_TIME_TIME_TIME_TIME:
11250 : : /* (x1, x2) OVERLAPS (y1, y2) */
11251 : 3 : appendStringInfoString(buf, "((");
11252 : 3 : get_rule_expr((Node *) linitial(expr->args), context, false);
11253 : 3 : appendStringInfoString(buf, ", ");
11254 : 3 : get_rule_expr((Node *) lsecond(expr->args), context, false);
11255 : 3 : appendStringInfoString(buf, ") OVERLAPS (");
11256 : 3 : get_rule_expr((Node *) lthird(expr->args), context, false);
11257 : 3 : appendStringInfoString(buf, ", ");
11258 : 3 : get_rule_expr((Node *) lfourth(expr->args), context, false);
11259 : 3 : appendStringInfoString(buf, "))");
11260 : 3 : return true;
11261 : :
1804 peter@eisentraut.org 11262 : 9 : case F_EXTRACT_TEXT_DATE:
11263 : : case F_EXTRACT_TEXT_TIME:
11264 : : case F_EXTRACT_TEXT_TIMETZ:
11265 : : case F_EXTRACT_TEXT_TIMESTAMP:
11266 : : case F_EXTRACT_TEXT_TIMESTAMPTZ:
11267 : : case F_EXTRACT_TEXT_INTERVAL:
11268 : : /* EXTRACT (x FROM y) */
11269 : 9 : appendStringInfoString(buf, "EXTRACT(");
11270 : : {
11271 : 9 : Const *con = (Const *) linitial(expr->args);
11272 : :
11273 [ + - + - : 9 : Assert(IsA(con, Const) &&
- + ]
11274 : : con->consttype == TEXTOID &&
11275 : : !con->constisnull);
11276 : 9 : appendStringInfoString(buf, TextDatumGetCString(con->constvalue));
11277 : : }
11278 : 9 : appendStringInfoString(buf, " FROM ");
11279 : 9 : get_rule_expr((Node *) lsecond(expr->args), context, false);
11280 : 9 : appendStringInfoChar(buf, ')');
11281 : 9 : return true;
11282 : :
1957 tgl@sss.pgh.pa.us 11283 : 6 : case F_IS_NORMALIZED:
11284 : : /* IS xxx NORMALIZED */
894 drowley@postgresql.o 11285 : 6 : appendStringInfoChar(buf, '(');
1200 tgl@sss.pgh.pa.us 11286 : 6 : get_rule_expr_paren((Node *) linitial(expr->args), context, false,
11287 : : (Node *) expr);
11288 : 6 : appendStringInfoString(buf, " IS");
1957 11289 [ + + ]: 6 : if (list_length(expr->args) == 2)
11290 : : {
11291 : 3 : Const *con = (Const *) lsecond(expr->args);
11292 : :
11293 [ + - + - : 3 : Assert(IsA(con, Const) &&
- + ]
11294 : : con->consttype == TEXTOID &&
11295 : : !con->constisnull);
11296 : 3 : appendStringInfo(buf, " %s",
11297 : 3 : TextDatumGetCString(con->constvalue));
11298 : : }
11299 : 6 : appendStringInfoString(buf, " NORMALIZED)");
11300 : 6 : return true;
11301 : :
11302 : 3 : case F_PG_COLLATION_FOR:
11303 : : /* COLLATION FOR */
11304 : 3 : appendStringInfoString(buf, "COLLATION FOR (");
11305 : 3 : get_rule_expr((Node *) linitial(expr->args), context, false);
11306 : 3 : appendStringInfoChar(buf, ')');
11307 : 3 : return true;
11308 : :
11309 : 6 : case F_NORMALIZE:
11310 : : /* NORMALIZE() */
11311 : 6 : appendStringInfoString(buf, "NORMALIZE(");
11312 : 6 : get_rule_expr((Node *) linitial(expr->args), context, false);
11313 [ + + ]: 6 : if (list_length(expr->args) == 2)
11314 : : {
11315 : 3 : Const *con = (Const *) lsecond(expr->args);
11316 : :
11317 [ + - + - : 3 : Assert(IsA(con, Const) &&
- + ]
11318 : : con->consttype == TEXTOID &&
11319 : : !con->constisnull);
11320 : 3 : appendStringInfo(buf, ", %s",
11321 : 3 : TextDatumGetCString(con->constvalue));
11322 : : }
11323 : 6 : appendStringInfoChar(buf, ')');
11324 : 6 : return true;
11325 : :
11326 : 6 : case F_OVERLAY_BIT_BIT_INT4:
11327 : : case F_OVERLAY_BIT_BIT_INT4_INT4:
11328 : : case F_OVERLAY_BYTEA_BYTEA_INT4:
11329 : : case F_OVERLAY_BYTEA_BYTEA_INT4_INT4:
11330 : : case F_OVERLAY_TEXT_TEXT_INT4:
11331 : : case F_OVERLAY_TEXT_TEXT_INT4_INT4:
11332 : : /* OVERLAY() */
11333 : 6 : appendStringInfoString(buf, "OVERLAY(");
11334 : 6 : get_rule_expr((Node *) linitial(expr->args), context, false);
11335 : 6 : appendStringInfoString(buf, " PLACING ");
11336 : 6 : get_rule_expr((Node *) lsecond(expr->args), context, false);
11337 : 6 : appendStringInfoString(buf, " FROM ");
11338 : 6 : get_rule_expr((Node *) lthird(expr->args), context, false);
11339 [ + + ]: 6 : if (list_length(expr->args) == 4)
11340 : : {
11341 : 3 : appendStringInfoString(buf, " FOR ");
11342 : 3 : get_rule_expr((Node *) lfourth(expr->args), context, false);
11343 : : }
11344 : 6 : appendStringInfoChar(buf, ')');
11345 : 6 : return true;
11346 : :
11347 : 3 : case F_POSITION_BIT_BIT:
11348 : : case F_POSITION_BYTEA_BYTEA:
11349 : : case F_POSITION_TEXT_TEXT:
11350 : : /* POSITION() ... extra parens since args are b_expr not a_expr */
11351 : 3 : appendStringInfoString(buf, "POSITION((");
11352 : 3 : get_rule_expr((Node *) lsecond(expr->args), context, false);
11353 : 3 : appendStringInfoString(buf, ") IN (");
11354 : 3 : get_rule_expr((Node *) linitial(expr->args), context, false);
11355 : 3 : appendStringInfoString(buf, "))");
11356 : 3 : return true;
11357 : :
11358 : 3 : case F_SUBSTRING_BIT_INT4:
11359 : : case F_SUBSTRING_BIT_INT4_INT4:
11360 : : case F_SUBSTRING_BYTEA_INT4:
11361 : : case F_SUBSTRING_BYTEA_INT4_INT4:
11362 : : case F_SUBSTRING_TEXT_INT4:
11363 : : case F_SUBSTRING_TEXT_INT4_INT4:
11364 : : /* SUBSTRING FROM/FOR (i.e., integer-position variants) */
11365 : 3 : appendStringInfoString(buf, "SUBSTRING(");
11366 : 3 : get_rule_expr((Node *) linitial(expr->args), context, false);
11367 : 3 : appendStringInfoString(buf, " FROM ");
11368 : 3 : get_rule_expr((Node *) lsecond(expr->args), context, false);
11369 [ + - ]: 3 : if (list_length(expr->args) == 3)
11370 : : {
11371 : 3 : appendStringInfoString(buf, " FOR ");
11372 : 3 : get_rule_expr((Node *) lthird(expr->args), context, false);
11373 : : }
11374 : 3 : appendStringInfoChar(buf, ')');
11375 : 3 : return true;
11376 : :
11377 : 3 : case F_SUBSTRING_TEXT_TEXT_TEXT:
11378 : : /* SUBSTRING SIMILAR/ESCAPE */
11379 : 3 : appendStringInfoString(buf, "SUBSTRING(");
11380 : 3 : get_rule_expr((Node *) linitial(expr->args), context, false);
11381 : 3 : appendStringInfoString(buf, " SIMILAR ");
11382 : 3 : get_rule_expr((Node *) lsecond(expr->args), context, false);
11383 : 3 : appendStringInfoString(buf, " ESCAPE ");
11384 : 3 : get_rule_expr((Node *) lthird(expr->args), context, false);
11385 : 3 : appendStringInfoChar(buf, ')');
11386 : 3 : return true;
11387 : :
11388 : 6 : case F_BTRIM_BYTEA_BYTEA:
11389 : : case F_BTRIM_TEXT:
11390 : : case F_BTRIM_TEXT_TEXT:
11391 : : /* TRIM() */
11392 : 6 : appendStringInfoString(buf, "TRIM(BOTH");
11393 [ + - ]: 6 : if (list_length(expr->args) == 2)
11394 : : {
11395 : 6 : appendStringInfoChar(buf, ' ');
11396 : 6 : get_rule_expr((Node *) lsecond(expr->args), context, false);
11397 : : }
11398 : 6 : appendStringInfoString(buf, " FROM ");
11399 : 6 : get_rule_expr((Node *) linitial(expr->args), context, false);
11400 : 6 : appendStringInfoChar(buf, ')');
11401 : 6 : return true;
11402 : :
1882 11403 : 6 : case F_LTRIM_BYTEA_BYTEA:
11404 : : case F_LTRIM_TEXT:
11405 : : case F_LTRIM_TEXT_TEXT:
11406 : : /* TRIM() */
1957 11407 : 6 : appendStringInfoString(buf, "TRIM(LEADING");
11408 [ + - ]: 6 : if (list_length(expr->args) == 2)
11409 : : {
11410 : 6 : appendStringInfoChar(buf, ' ');
11411 : 6 : get_rule_expr((Node *) lsecond(expr->args), context, false);
11412 : : }
11413 : 6 : appendStringInfoString(buf, " FROM ");
11414 : 6 : get_rule_expr((Node *) linitial(expr->args), context, false);
11415 : 6 : appendStringInfoChar(buf, ')');
11416 : 6 : return true;
11417 : :
1882 11418 : 6 : case F_RTRIM_BYTEA_BYTEA:
11419 : : case F_RTRIM_TEXT:
11420 : : case F_RTRIM_TEXT_TEXT:
11421 : : /* TRIM() */
1957 11422 : 6 : appendStringInfoString(buf, "TRIM(TRAILING");
11423 [ + + ]: 6 : if (list_length(expr->args) == 2)
11424 : : {
11425 : 3 : appendStringInfoChar(buf, ' ');
11426 : 3 : get_rule_expr((Node *) lsecond(expr->args), context, false);
11427 : : }
11428 : 6 : appendStringInfoString(buf, " FROM ");
11429 : 6 : get_rule_expr((Node *) linitial(expr->args), context, false);
11430 : 6 : appendStringInfoChar(buf, ')');
11431 : 6 : return true;
11432 : :
1263 michael@paquier.xyz 11433 : 6 : case F_SYSTEM_USER:
11434 : 6 : appendStringInfoString(buf, "SYSTEM_USER");
11435 : 6 : return true;
11436 : :
1957 tgl@sss.pgh.pa.us 11437 :UBC 0 : case F_XMLEXISTS:
11438 : : /* XMLEXISTS ... extra parens because args are c_expr */
11439 : 0 : appendStringInfoString(buf, "XMLEXISTS((");
11440 : 0 : get_rule_expr((Node *) linitial(expr->args), context, false);
11441 : 0 : appendStringInfoString(buf, ") PASSING (");
11442 : 0 : get_rule_expr((Node *) lsecond(expr->args), context, false);
11443 : 0 : appendStringInfoString(buf, "))");
11444 : 0 : return true;
11445 : : }
1957 tgl@sss.pgh.pa.us 11446 :CBC 3 : return false;
11447 : : }
11448 : :
11449 : : /* ----------
11450 : : * get_coercion_expr
11451 : : *
11452 : : * Make a string representation of a value coerced to a specific type
11453 : : * ----------
11454 : : */
11455 : : static void
6938 11456 : 2691 : get_coercion_expr(Node *arg, deparse_context *context,
11457 : : Oid resulttype, int32 resulttypmod,
11458 : : Node *parentNode)
11459 : : {
11460 : 2691 : StringInfo buf = context->buf;
11461 : :
11462 : : /*
11463 : : * Since parse_coerce.c doesn't immediately collapse application of
11464 : : * length-coercion functions to constants, what we'll typically see in
11465 : : * such cases is a Const with typmod -1 and a length-coercion function
11466 : : * right above it. Avoid generating redundant output. However, beware of
11467 : : * suppressing casts when the user actually wrote something like
11468 : : * 'foo'::text::char(3).
11469 : : *
11470 : : * Note: it might seem that we are missing the possibility of needing to
11471 : : * print a COLLATE clause for such a Const. However, a Const could only
11472 : : * have nondefault collation in a post-constant-folding tree, in which the
11473 : : * length coercion would have been folded too. See also the special
11474 : : * handling of CollateExpr in coerce_to_target_type(): any collation
11475 : : * marking will be above the coercion node, not below it.
11476 : : */
11477 [ + - + + ]: 2691 : if (arg && IsA(arg, Const) &&
11478 [ + + ]: 328 : ((Const *) arg)->consttype == resulttype &&
11479 [ + - ]: 12 : ((Const *) arg)->consttypmod == -1)
11480 : : {
11481 : : /* Show the constant without normal ::typename decoration */
6643 11482 : 12 : get_const_expr((Const *) arg, context, -1);
11483 : : }
11484 : : else
11485 : : {
6938 11486 [ + + ]: 2679 : if (!PRETTY_PAREN(context))
11487 : 2488 : appendStringInfoChar(buf, '(');
11488 : 2679 : get_rule_expr_paren(arg, context, false, parentNode);
11489 [ + + ]: 2679 : if (!PRETTY_PAREN(context))
11490 : 2488 : appendStringInfoChar(buf, ')');
11491 : : }
11492 : :
11493 : : /*
11494 : : * Never emit resulttype(arg) functional notation. A pg_proc entry could
11495 : : * take precedence, and a resulttype in pg_temp would require schema
11496 : : * qualification that format_type_with_typemod() would usually omit. We've
11497 : : * standardized on arg::resulttype, but CAST(arg AS resulttype) notation
11498 : : * would work fine.
11499 : : */
11500 : 2691 : appendStringInfo(buf, "::%s",
11501 : : format_type_with_typemod(resulttype, resulttypmod));
11502 : 2691 : }
11503 : :
11504 : : /* ----------
11505 : : * get_const_expr
11506 : : *
11507 : : * Make a string representation of a Const
11508 : : *
11509 : : * showtype can be -1 to never show "::typename" decoration, or +1 to always
11510 : : * show it, or 0 to show it only if the constant wouldn't be assumed to be
11511 : : * the right type by default.
11512 : : *
11513 : : * If the Const's collation isn't default for its type, show that too.
11514 : : * We mustn't do this when showtype is -1 (since that means the caller will
11515 : : * print "::typename", and we can't put a COLLATE clause in between). It's
11516 : : * caller's responsibility that collation isn't missed in such cases.
11517 : : * ----------
11518 : : */
11519 : : static void
6643 11520 : 36633 : get_const_expr(Const *constval, deparse_context *context, int showtype)
11521 : : {
9660 11522 : 36633 : StringInfo buf = context->buf;
11523 : : Oid typoutput;
11524 : : bool typIsVarlena;
11525 : : char *extval;
4003 11526 : 36633 : bool needlabel = false;
11527 : :
9578 11528 [ + + ]: 36633 : if (constval->constisnull)
11529 : : {
11530 : : /*
11531 : : * Always label the type of a NULL constant to prevent misdecisions
11532 : : * about type when reparsing.
11533 : : */
4518 rhaas@postgresql.org 11534 : 626 : appendStringInfoString(buf, "NULL");
6643 tgl@sss.pgh.pa.us 11535 [ + + ]: 626 : if (showtype >= 0)
11536 : : {
6938 11537 : 599 : appendStringInfo(buf, "::%s",
11538 : : format_type_with_typemod(constval->consttype,
11539 : : constval->consttypmod));
5483 11540 : 599 : get_const_collation(constval, context);
11541 : : }
9578 11542 : 5167 : return;
11543 : : }
11544 : :
7952 11545 : 36007 : getTypeOutputInfo(constval->consttype,
11546 : : &typoutput, &typIsVarlena);
11547 : :
7285 11548 : 36007 : extval = OidOutputFunctionCall(typoutput, constval->constvalue);
11549 : :
9659 11550 [ + + + + ]: 36007 : switch (constval->consttype)
11551 : : {
11552 : 20782 : case INT4OID:
11553 : :
11554 : : /*
11555 : : * INT4 can be printed without any decoration, unless it is
11556 : : * negative; in that case print it as '-nnn'::integer to ensure
11557 : : * that the output will re-parse as a constant, not as a constant
11558 : : * plus operator. In most cases we could get away with printing
11559 : : * (-nnn) instead, because of the way that gram.y handles negative
11560 : : * literals; but that doesn't work for INT_MIN, and it doesn't
11561 : : * seem that much prettier anyway.
11562 : : */
4003 11563 [ + + ]: 20782 : if (extval[0] != '-')
11564 : 20524 : appendStringInfoString(buf, extval);
11565 : : else
11566 : : {
11567 : 258 : appendStringInfo(buf, "'%s'", extval);
3189 11568 : 258 : needlabel = true; /* we must attach a cast */
11569 : : }
4003 11570 : 20782 : break;
11571 : :
8610 peter_e@gmx.net 11572 : 551 : case NUMERICOID:
11573 : :
11574 : : /*
11575 : : * NUMERIC can be printed without quotes if it looks like a float
11576 : : * constant (not an integer, and not Infinity or NaN) and doesn't
11577 : : * have a leading sign (for the same reason as for INT4).
11578 : : */
4003 tgl@sss.pgh.pa.us 11579 [ + - ]: 551 : if (isdigit((unsigned char) extval[0]) &&
11580 [ + + ]: 551 : strcspn(extval, "eE.") != strlen(extval))
11581 : : {
11582 : 196 : appendStringInfoString(buf, extval);
11583 : : }
11584 : : else
11585 : : {
11586 : 355 : appendStringInfo(buf, "'%s'", extval);
3189 11587 : 355 : needlabel = true; /* we must attach a cast */
11588 : : }
8593 bruce@momjian.us 11589 : 551 : break;
11590 : :
8610 peter_e@gmx.net 11591 : 873 : case BOOLOID:
8593 bruce@momjian.us 11592 [ + + ]: 873 : if (strcmp(extval, "t") == 0)
4518 rhaas@postgresql.org 11593 : 376 : appendStringInfoString(buf, "true");
11594 : : else
11595 : 497 : appendStringInfoString(buf, "false");
8610 peter_e@gmx.net 11596 : 873 : break;
11597 : :
11598 : 13801 : default:
6399 tgl@sss.pgh.pa.us 11599 : 13801 : simple_quote_literal(buf, extval);
9659 11600 : 13801 : break;
11601 : : }
11602 : :
9661 11603 : 36007 : pfree(extval);
11604 : :
6643 11605 [ + + ]: 36007 : if (showtype < 0)
6938 11606 : 4541 : return;
11607 : :
11608 : : /*
11609 : : * For showtype == 0, append ::typename unless the constant will be
11610 : : * implicitly typed as the right type when it is read in.
11611 : : *
11612 : : * XXX this code has to be kept in sync with the behavior of the parser,
11613 : : * especially make_const.
11614 : : */
9659 11615 [ + + + + ]: 31466 : switch (constval->consttype)
11616 : : {
8610 peter_e@gmx.net 11617 : 907 : case BOOLOID:
11618 : : case UNKNOWNOID:
11619 : : /* These types can be left unlabeled */
8579 tgl@sss.pgh.pa.us 11620 : 907 : needlabel = false;
11621 : 907 : break;
4003 11622 : 18367 : case INT4OID:
11623 : : /* We determined above whether a label is needed */
11624 : 18367 : break;
8579 11625 : 551 : case NUMERICOID:
11626 : :
11627 : : /*
11628 : : * Float-looking constants will be typed as numeric, which we
11629 : : * checked above; but if there's a nondefault typmod we need to
11630 : : * show it.
11631 : : */
4003 11632 : 551 : needlabel |= (constval->consttypmod >= 0);
9659 11633 : 551 : break;
11634 : 11641 : default:
8579 11635 : 11641 : needlabel = true;
9659 11636 : 11641 : break;
11637 : : }
6643 11638 [ + + - + ]: 31466 : if (needlabel || showtype > 0)
8579 11639 : 12247 : appendStringInfo(buf, "::%s",
11640 : : format_type_with_typemod(constval->consttype,
11641 : : constval->consttypmod));
11642 : :
5483 11643 : 31466 : get_const_collation(constval, context);
11644 : : }
11645 : :
11646 : : /*
11647 : : * helper for get_const_expr: append COLLATE if needed
11648 : : */
11649 : : static void
11650 : 32065 : get_const_collation(Const *constval, deparse_context *context)
11651 : : {
11652 : 32065 : StringInfo buf = context->buf;
11653 : :
11654 [ + + ]: 32065 : if (OidIsValid(constval->constcollid))
11655 : : {
5453 bruce@momjian.us 11656 : 4609 : Oid typcollation = get_typcollation(constval->consttype);
11657 : :
5483 tgl@sss.pgh.pa.us 11658 [ + + ]: 4609 : if (constval->constcollid != typcollation)
11659 : : {
11660 : 37 : appendStringInfo(buf, " COLLATE %s",
11661 : : generate_collation_name(constval->constcollid));
11662 : : }
11663 : : }
10026 bruce@momjian.us 11664 : 32065 : }
11665 : :
11666 : : /*
11667 : : * get_json_path_spec - Parse back a JSON path specification
11668 : : */
11669 : : static void
724 amitlan@postgresql.o 11670 : 228 : get_json_path_spec(Node *path_spec, deparse_context *context, bool showimplicit)
11671 : : {
11672 [ + - ]: 228 : if (IsA(path_spec, Const))
11673 : 228 : get_const_expr((Const *) path_spec, context, -1);
11674 : : else
724 amitlan@postgresql.o 11675 :UBC 0 : get_rule_expr(path_spec, context, showimplicit);
724 amitlan@postgresql.o 11676 :CBC 228 : }
11677 : :
11678 : : /*
11679 : : * get_json_format - Parse back a JsonFormat node
11680 : : */
11681 : : static void
1082 alvherre@alvh.no-ip. 11682 : 93 : get_json_format(JsonFormat *format, StringInfo buf)
11683 : : {
11684 [ + + ]: 93 : if (format->format_type == JS_FORMAT_DEFAULT)
11685 : 54 : return;
11686 : :
11687 : 39 : appendStringInfoString(buf,
11688 [ - + ]: 39 : format->format_type == JS_FORMAT_JSONB ?
11689 : : " FORMAT JSONB" : " FORMAT JSON");
11690 : :
11691 [ + + ]: 39 : if (format->encoding != JS_ENC_DEFAULT)
11692 : : {
11693 : : const char *encoding;
11694 : :
11695 : 3 : encoding =
11696 [ + - ]: 6 : format->encoding == JS_ENC_UTF16 ? "UTF16" :
11697 [ - + ]: 3 : format->encoding == JS_ENC_UTF32 ? "UTF32" : "UTF8";
11698 : :
11699 : 3 : appendStringInfo(buf, " ENCODING %s", encoding);
11700 : : }
11701 : : }
11702 : :
11703 : : /*
11704 : : * get_json_returning - Parse back a JsonReturning structure
11705 : : */
11706 : : static void
11707 : 90 : get_json_returning(JsonReturning *returning, StringInfo buf,
11708 : : bool json_format_by_default)
11709 : : {
11710 [ - + ]: 90 : if (!OidIsValid(returning->typid))
1082 alvherre@alvh.no-ip. 11711 :UBC 0 : return;
11712 : :
1082 alvherre@alvh.no-ip. 11713 :CBC 90 : appendStringInfo(buf, " RETURNING %s",
11714 : : format_type_with_typemod(returning->typid,
11715 : : returning->typmod));
11716 : :
11717 [ + + + + ]: 174 : if (!json_format_by_default ||
11718 : 84 : returning->format->format_type !=
11719 [ + + ]: 84 : (returning->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON))
11720 : 18 : get_json_format(returning->format, buf);
11721 : : }
11722 : :
11723 : : /*
11724 : : * get_json_constructor - Parse back a JsonConstructorExpr node
11725 : : */
11726 : : static void
11727 : 93 : get_json_constructor(JsonConstructorExpr *ctor, deparse_context *context,
11728 : : bool showimplicit)
11729 : : {
11730 : 93 : StringInfo buf = context->buf;
11731 : : const char *funcname;
11732 : : bool is_json_object;
11733 : : int curridx;
11734 : : ListCell *lc;
11735 : :
11736 [ + + ]: 93 : if (ctor->type == JSCTOR_JSON_OBJECTAGG)
11737 : : {
11738 : 18 : get_json_agg_constructor(ctor, context, "JSON_OBJECTAGG", true);
11739 : 18 : return;
11740 : : }
11741 [ + + ]: 75 : else if (ctor->type == JSCTOR_JSON_ARRAYAGG)
11742 : : {
11743 : 15 : get_json_agg_constructor(ctor, context, "JSON_ARRAYAGG", false);
11744 : 15 : return;
11745 : : }
11746 : :
11747 [ + + + + : 60 : switch (ctor->type)
+ - ]
11748 : : {
11749 : 15 : case JSCTOR_JSON_OBJECT:
11750 : 15 : funcname = "JSON_OBJECT";
11751 : 15 : break;
11752 : 12 : case JSCTOR_JSON_ARRAY:
11753 : 12 : funcname = "JSON_ARRAY";
11754 : 12 : break;
969 amitlan@postgresql.o 11755 : 21 : case JSCTOR_JSON_PARSE:
11756 : 21 : funcname = "JSON";
11757 : 21 : break;
11758 : 6 : case JSCTOR_JSON_SCALAR:
11759 : 6 : funcname = "JSON_SCALAR";
11760 : 6 : break;
11761 : 6 : case JSCTOR_JSON_SERIALIZE:
11762 : 6 : funcname = "JSON_SERIALIZE";
11763 : 6 : break;
1082 alvherre@alvh.no-ip. 11764 :UBC 0 : default:
1081 11765 [ # # ]: 0 : elog(ERROR, "invalid JsonConstructorType %d", ctor->type);
11766 : : }
11767 : :
1082 alvherre@alvh.no-ip. 11768 :CBC 60 : appendStringInfo(buf, "%s(", funcname);
11769 : :
11770 : 60 : is_json_object = ctor->type == JSCTOR_JSON_OBJECT;
11771 [ + - + + : 159 : foreach(lc, ctor->args)
+ + ]
11772 : : {
11773 : 99 : curridx = foreach_current_index(lc);
11774 [ + + ]: 99 : if (curridx > 0)
11775 : : {
11776 : : const char *sep;
11777 : :
11778 [ + + + + ]: 39 : sep = (is_json_object && (curridx % 2) != 0) ? " : " : ", ";
11779 : 39 : appendStringInfoString(buf, sep);
11780 : : }
11781 : :
11782 : 99 : get_rule_expr((Node *) lfirst(lc), context, true);
11783 : : }
11784 : :
11785 : 60 : get_json_constructor_options(ctor, buf);
894 drowley@postgresql.o 11786 : 60 : appendStringInfoChar(buf, ')');
11787 : : }
11788 : :
11789 : : /*
11790 : : * Append options, if any, to the JSON constructor being deparsed
11791 : : */
11792 : : static void
1082 alvherre@alvh.no-ip. 11793 : 93 : get_json_constructor_options(JsonConstructorExpr *ctor, StringInfo buf)
11794 : : {
11795 [ + + ]: 93 : if (ctor->absent_on_null)
11796 : : {
11797 [ + - ]: 18 : if (ctor->type == JSCTOR_JSON_OBJECT ||
11798 [ - + ]: 18 : ctor->type == JSCTOR_JSON_OBJECTAGG)
1082 alvherre@alvh.no-ip. 11799 :UBC 0 : appendStringInfoString(buf, " ABSENT ON NULL");
11800 : : }
11801 : : else
11802 : : {
1082 alvherre@alvh.no-ip. 11803 [ + - ]:CBC 75 : if (ctor->type == JSCTOR_JSON_ARRAY ||
11804 [ + + ]: 75 : ctor->type == JSCTOR_JSON_ARRAYAGG)
11805 : 9 : appendStringInfoString(buf, " NULL ON NULL");
11806 : : }
11807 : :
11808 [ + + ]: 93 : if (ctor->unique)
11809 : 12 : appendStringInfoString(buf, " WITH UNIQUE KEYS");
11810 : :
11811 : : /*
11812 : : * Append RETURNING clause if needed; JSON() and JSON_SCALAR() don't
11813 : : * support one.
11814 : : */
969 amitlan@postgresql.o 11815 [ + + + + ]: 93 : if (ctor->type != JSCTOR_JSON_PARSE && ctor->type != JSCTOR_JSON_SCALAR)
11816 : 66 : get_json_returning(ctor->returning, buf, true);
1082 alvherre@alvh.no-ip. 11817 : 93 : }
11818 : :
11819 : : /*
11820 : : * get_json_agg_constructor - Parse back an aggregate JsonConstructorExpr node
11821 : : */
11822 : : static void
11823 : 33 : get_json_agg_constructor(JsonConstructorExpr *ctor, deparse_context *context,
11824 : : const char *funcname, bool is_json_objectagg)
11825 : : {
11826 : : StringInfoData options;
11827 : :
11828 : 33 : initStringInfo(&options);
11829 : 33 : get_json_constructor_options(ctor, &options);
11830 : :
11831 [ + + ]: 33 : if (IsA(ctor->func, Aggref))
11832 : 27 : get_agg_expr_helper((Aggref *) ctor->func, context,
11833 : 27 : (Aggref *) ctor->func,
11834 : 27 : funcname, options.data, is_json_objectagg);
11835 [ + - ]: 6 : else if (IsA(ctor->func, WindowFunc))
11836 : 6 : get_windowfunc_expr_helper((WindowFunc *) ctor->func, context,
11837 : 6 : funcname, options.data,
11838 : : is_json_objectagg);
11839 : : else
1082 alvherre@alvh.no-ip. 11840 [ # # ]:UBC 0 : elog(ERROR, "invalid JsonConstructorExpr underlying node type: %d",
11841 : : nodeTag(ctor->func));
1082 alvherre@alvh.no-ip. 11842 :CBC 33 : }
11843 : :
11844 : : /*
11845 : : * simple_quote_literal - Format a string as a SQL literal, append to buf
11846 : : */
11847 : : static void
6399 tgl@sss.pgh.pa.us 11848 : 14237 : simple_quote_literal(StringInfo buf, const char *val)
11849 : : {
11850 : : const char *valptr;
11851 : :
11852 : : /*
11853 : : * We always form the string literal according to standard SQL rules.
11854 : : */
11855 : 14237 : appendStringInfoChar(buf, '\'');
11856 [ + + ]: 145157 : for (valptr = val; *valptr; valptr++)
11857 : : {
11858 : 130920 : char ch = *valptr;
11859 : :
53 tgl@sss.pgh.pa.us 11860 [ + + ]:GNC 130920 : if (SQL_STR_DOUBLE(ch, false))
6399 tgl@sss.pgh.pa.us 11861 :CBC 153 : appendStringInfoChar(buf, ch);
11862 : 130920 : appendStringInfoChar(buf, ch);
11863 : : }
11864 : 14237 : appendStringInfoChar(buf, '\'');
11865 : 14237 : }
11866 : :
11867 : :
11868 : : /* ----------
11869 : : * get_sublink_expr - Parse back a sublink
11870 : : * ----------
11871 : : */
11872 : : static void
8494 11873 : 230 : get_sublink_expr(SubLink *sublink, deparse_context *context)
11874 : : {
9660 11875 : 230 : StringInfo buf = context->buf;
10026 bruce@momjian.us 11876 : 230 : Query *query = (Query *) (sublink->subselect);
7382 tgl@sss.pgh.pa.us 11877 : 230 : char *opname = NULL;
11878 : : bool need_paren;
11879 : :
8377 11880 [ + + ]: 230 : if (sublink->subLinkType == ARRAY_SUBLINK)
4518 rhaas@postgresql.org 11881 : 12 : appendStringInfoString(buf, "ARRAY(");
11882 : : else
8377 tgl@sss.pgh.pa.us 11883 : 218 : appendStringInfoChar(buf, '(');
11884 : :
11885 : : /*
11886 : : * Note that we print the name of only the first operator, when there are
11887 : : * multiple combining operators. This is an approximation that could go
11888 : : * wrong in various scenarios (operators in different schemas, renamed
11889 : : * operators, etc) but there is not a whole lot we can do about it, since
11890 : : * the syntax allows only one operator to be shown.
11891 : : */
7382 11892 [ + + ]: 230 : if (sublink->testexpr)
11893 : : {
11894 [ + + ]: 9 : if (IsA(sublink->testexpr, OpExpr))
11895 : : {
11896 : : /* single combining operator */
7102 bruce@momjian.us 11897 : 3 : OpExpr *opexpr = (OpExpr *) sublink->testexpr;
11898 : :
7382 tgl@sss.pgh.pa.us 11899 : 3 : get_rule_expr(linitial(opexpr->args), context, true);
11900 : 3 : opname = generate_operator_name(opexpr->opno,
11901 : 3 : exprType(linitial(opexpr->args)),
11902 : 3 : exprType(lsecond(opexpr->args)));
11903 : : }
11904 [ + + ]: 6 : else if (IsA(sublink->testexpr, BoolExpr))
11905 : : {
11906 : : /* multiple combining operators, = or <> cases */
11907 : : char *sep;
11908 : : ListCell *l;
11909 : :
9520 11910 : 3 : appendStringInfoChar(buf, '(');
7382 11911 : 3 : sep = "";
11912 [ + - + + : 9 : foreach(l, ((BoolExpr *) sublink->testexpr)->args)
+ + ]
11913 : : {
3261 11914 : 6 : OpExpr *opexpr = lfirst_node(OpExpr, l);
11915 : :
7382 11916 : 6 : appendStringInfoString(buf, sep);
11917 : 6 : get_rule_expr(linitial(opexpr->args), context, true);
11918 [ + + ]: 6 : if (!opname)
11919 : 3 : opname = generate_operator_name(opexpr->opno,
3189 11920 : 3 : exprType(linitial(opexpr->args)),
11921 : 3 : exprType(lsecond(opexpr->args)));
7382 11922 : 6 : sep = ", ";
11923 : : }
7829 11924 : 3 : appendStringInfoChar(buf, ')');
11925 : : }
7382 11926 [ + - ]: 3 : else if (IsA(sublink->testexpr, RowCompareExpr))
11927 : : {
11928 : : /* multiple combining operators, < <= > >= cases */
11929 : 3 : RowCompareExpr *rcexpr = (RowCompareExpr *) sublink->testexpr;
11930 : :
11931 : 3 : appendStringInfoChar(buf, '(');
11932 : 3 : get_rule_expr((Node *) rcexpr->largs, context, true);
11933 : 3 : opname = generate_operator_name(linitial_oid(rcexpr->opnos),
11934 : 3 : exprType(linitial(rcexpr->largs)),
3189 11935 : 3 : exprType(linitial(rcexpr->rargs)));
7382 11936 : 3 : appendStringInfoChar(buf, ')');
11937 : : }
11938 : : else
7382 tgl@sss.pgh.pa.us 11939 [ # # ]:UBC 0 : elog(ERROR, "unrecognized testexpr type: %d",
11940 : : (int) nodeTag(sublink->testexpr));
11941 : : }
11942 : :
9617 tgl@sss.pgh.pa.us 11943 :CBC 230 : need_paren = true;
11944 : :
9791 bruce@momjian.us 11945 [ + + + - : 230 : switch (sublink->subLinkType)
+ - ]
11946 : : {
10026 11947 : 88 : case EXISTS_SUBLINK:
4518 rhaas@postgresql.org 11948 : 88 : appendStringInfoString(buf, "EXISTS ");
10026 bruce@momjian.us 11949 : 88 : break;
11950 : :
11951 : 6 : case ANY_SUBLINK:
3189 tgl@sss.pgh.pa.us 11952 [ + + ]: 6 : if (strcmp(opname, "=") == 0) /* Represent = ANY as IN */
4518 rhaas@postgresql.org 11953 : 3 : appendStringInfoString(buf, " IN ");
11954 : : else
7382 tgl@sss.pgh.pa.us 11955 : 3 : appendStringInfo(buf, " %s ANY ", opname);
10026 bruce@momjian.us 11956 : 6 : break;
11957 : :
11958 : 3 : case ALL_SUBLINK:
7382 tgl@sss.pgh.pa.us 11959 : 3 : appendStringInfo(buf, " %s ALL ", opname);
10026 bruce@momjian.us 11960 : 3 : break;
11961 : :
7382 tgl@sss.pgh.pa.us 11962 :UBC 0 : case ROWCOMPARE_SUBLINK:
11963 : 0 : appendStringInfo(buf, " %s ", opname);
10026 bruce@momjian.us 11964 : 0 : break;
11965 : :
9617 tgl@sss.pgh.pa.us 11966 :CBC 133 : case EXPR_SUBLINK:
11967 : : case MULTIEXPR_SUBLINK:
11968 : : case ARRAY_SUBLINK:
11969 : 133 : need_paren = false;
11970 : 133 : break;
11971 : :
6371 tgl@sss.pgh.pa.us 11972 :UBC 0 : case CTE_SUBLINK: /* shouldn't occur in a SubLink */
11973 : : default:
8267 11974 [ # # ]: 0 : elog(ERROR, "unrecognized sublink type: %d",
11975 : : (int) sublink->subLinkType);
11976 : : break;
11977 : : }
11978 : :
9617 tgl@sss.pgh.pa.us 11979 [ + + ]:CBC 230 : if (need_paren)
9520 11980 : 97 : appendStringInfoChar(buf, '(');
11981 : :
1394 11982 : 230 : get_query_def(query, buf, context->namespaces, NULL, false,
11983 : : context->prettyFlags, context->wrapColumn,
11984 : : context->indentLevel);
11985 : :
9617 11986 [ + + ]: 230 : if (need_paren)
4518 rhaas@postgresql.org 11987 : 97 : appendStringInfoString(buf, "))");
11988 : : else
9520 tgl@sss.pgh.pa.us 11989 : 133 : appendStringInfoChar(buf, ')');
10065 bruce@momjian.us 11990 : 230 : }
11991 : :
11992 : :
11993 : : /* ----------
11994 : : * get_xmltable - Parse back a XMLTABLE function
11995 : : * ----------
11996 : : */
11997 : : static void
710 amitlan@postgresql.o 11998 : 31 : get_xmltable(TableFunc *tf, deparse_context *context, bool showimplicit)
11999 : : {
3294 alvherre@alvh.no-ip. 12000 : 31 : StringInfo buf = context->buf;
12001 : :
12002 : 31 : appendStringInfoString(buf, "XMLTABLE(");
12003 : :
12004 [ + + ]: 31 : if (tf->ns_uris != NIL)
12005 : : {
12006 : : ListCell *lc1,
12007 : : *lc2;
12008 : 8 : bool first = true;
12009 : :
12010 : 8 : appendStringInfoString(buf, "XMLNAMESPACES (");
12011 [ + - + + : 16 : forboth(lc1, tf->ns_uris, lc2, tf->ns_names)
+ - + + +
+ + - +
+ ]
12012 : : {
12013 : 8 : Node *expr = (Node *) lfirst(lc1);
1648 peter@eisentraut.org 12014 : 8 : String *ns_node = lfirst_node(String, lc2);
12015 : :
3294 alvherre@alvh.no-ip. 12016 [ - + ]: 8 : if (!first)
3294 alvherre@alvh.no-ip. 12017 :UBC 0 : appendStringInfoString(buf, ", ");
12018 : : else
3294 alvherre@alvh.no-ip. 12019 :CBC 8 : first = false;
12020 : :
2736 tgl@sss.pgh.pa.us 12021 [ + - ]: 8 : if (ns_node != NULL)
12022 : : {
3294 alvherre@alvh.no-ip. 12023 : 8 : get_rule_expr(expr, context, showimplicit);
427 dean.a.rasheed@gmail 12024 : 8 : appendStringInfo(buf, " AS %s",
12025 : 8 : quote_identifier(strVal(ns_node)));
12026 : : }
12027 : : else
12028 : : {
3294 alvherre@alvh.no-ip. 12029 :UBC 0 : appendStringInfoString(buf, "DEFAULT ");
12030 : 0 : get_rule_expr(expr, context, showimplicit);
12031 : : }
12032 : : }
3294 alvherre@alvh.no-ip. 12033 :CBC 8 : appendStringInfoString(buf, "), ");
12034 : : }
12035 : :
12036 : 31 : appendStringInfoChar(buf, '(');
12037 : 31 : get_rule_expr((Node *) tf->rowexpr, context, showimplicit);
12038 : 31 : appendStringInfoString(buf, ") PASSING (");
12039 : 31 : get_rule_expr((Node *) tf->docexpr, context, showimplicit);
12040 : 31 : appendStringInfoChar(buf, ')');
12041 : :
12042 [ + - ]: 31 : if (tf->colexprs != NIL)
12043 : : {
12044 : : ListCell *l1;
12045 : : ListCell *l2;
12046 : : ListCell *l3;
12047 : : ListCell *l4;
12048 : : ListCell *l5;
12049 : 31 : int colnum = 0;
12050 : :
12051 : 31 : appendStringInfoString(buf, " COLUMNS ");
2572 tgl@sss.pgh.pa.us 12052 [ + - + + : 187 : forfive(l1, tf->colnames, l2, tf->coltypes, l3, tf->coltypmods,
+ - + + +
- + + + -
+ + + - +
+ + + + -
+ - + - +
- + + ]
12053 : : l4, tf->colexprs, l5, tf->coldefexprs)
12054 : : {
3294 alvherre@alvh.no-ip. 12055 : 156 : char *colname = strVal(lfirst(l1));
2572 tgl@sss.pgh.pa.us 12056 : 156 : Oid typid = lfirst_oid(l2);
12057 : 156 : int32 typmod = lfirst_int(l3);
12058 : 156 : Node *colexpr = (Node *) lfirst(l4);
12059 : 156 : Node *coldefexpr = (Node *) lfirst(l5);
12060 : 156 : bool ordinality = (tf->ordinalitycol == colnum);
3294 alvherre@alvh.no-ip. 12061 : 156 : bool notnull = bms_is_member(colnum, tf->notnulls);
12062 : :
12063 [ + + ]: 156 : if (colnum > 0)
12064 : 125 : appendStringInfoString(buf, ", ");
12065 : 156 : colnum++;
12066 : :
12067 [ + + ]: 295 : appendStringInfo(buf, "%s %s", quote_identifier(colname),
12068 : : ordinality ? "FOR ORDINALITY" :
12069 : 139 : format_type_with_typemod(typid, typmod));
12070 [ + + ]: 156 : if (ordinality)
12071 : 17 : continue;
12072 : :
12073 [ + + ]: 139 : if (coldefexpr != NULL)
12074 : : {
12075 : 17 : appendStringInfoString(buf, " DEFAULT (");
12076 : 17 : get_rule_expr((Node *) coldefexpr, context, showimplicit);
12077 : 17 : appendStringInfoChar(buf, ')');
12078 : : }
12079 [ + + ]: 139 : if (colexpr != NULL)
12080 : : {
12081 : 127 : appendStringInfoString(buf, " PATH (");
12082 : 127 : get_rule_expr((Node *) colexpr, context, showimplicit);
12083 : 127 : appendStringInfoChar(buf, ')');
12084 : : }
12085 [ + + ]: 139 : if (notnull)
12086 : 17 : appendStringInfoString(buf, " NOT NULL");
12087 : : }
12088 : : }
12089 : :
12090 : 31 : appendStringInfoChar(buf, ')');
12091 : 31 : }
12092 : :
12093 : : /*
12094 : : * get_json_table_nested_columns - Parse back nested JSON_TABLE columns
12095 : : */
12096 : : static void
706 amitlan@postgresql.o 12097 : 51 : get_json_table_nested_columns(TableFunc *tf, JsonTablePlan *plan,
12098 : : deparse_context *context, bool showimplicit,
12099 : : bool needcomma)
12100 : : {
12101 [ + + ]: 51 : if (IsA(plan, JsonTablePathScan))
12102 : : {
12103 : 36 : JsonTablePathScan *scan = castNode(JsonTablePathScan, plan);
12104 : :
12105 [ + + ]: 36 : if (needcomma)
12106 : 24 : appendStringInfoChar(context->buf, ',');
12107 : :
12108 : 36 : appendStringInfoChar(context->buf, ' ');
12109 : 36 : appendContextKeyword(context, "NESTED PATH ", 0, 0, 0);
12110 : 36 : get_const_expr(scan->path->value, context, -1);
12111 : 36 : appendStringInfo(context->buf, " AS %s", quote_identifier(scan->path->name));
12112 : 36 : get_json_table_columns(tf, scan, context, showimplicit);
12113 : : }
12114 [ + - ]: 15 : else if (IsA(plan, JsonTableSiblingJoin))
12115 : : {
12116 : 15 : JsonTableSiblingJoin *join = (JsonTableSiblingJoin *) plan;
12117 : :
12118 : 15 : get_json_table_nested_columns(tf, join->lplan, context, showimplicit,
12119 : : needcomma);
12120 : 15 : get_json_table_nested_columns(tf, join->rplan, context, showimplicit,
12121 : : true);
12122 : : }
12123 : 51 : }
12124 : :
12125 : : /*
12126 : : * get_json_table_columns - Parse back JSON_TABLE columns
12127 : : */
12128 : : static void
12129 : 90 : get_json_table_columns(TableFunc *tf, JsonTablePathScan *scan,
12130 : : deparse_context *context,
12131 : : bool showimplicit)
12132 : : {
710 12133 : 90 : StringInfo buf = context->buf;
12134 : : ListCell *lc_colname;
12135 : : ListCell *lc_coltype;
12136 : : ListCell *lc_coltypmod;
12137 : : ListCell *lc_colvalexpr;
12138 : 90 : int colnum = 0;
12139 : :
12140 : 90 : appendStringInfoChar(buf, ' ');
12141 : 90 : appendContextKeyword(context, "COLUMNS (", 0, 0, 0);
12142 : :
12143 [ + + ]: 90 : if (PRETTY_INDENT(context))
12144 : 69 : context->indentLevel += PRETTYINDENT_VAR;
12145 : :
12146 [ + - + + : 429 : forfour(lc_colname, tf->colnames,
+ - + + +
- + + + -
+ + + + +
- + - + -
+ + ]
12147 : : lc_coltype, tf->coltypes,
12148 : : lc_coltypmod, tf->coltypmods,
12149 : : lc_colvalexpr, tf->colvalexprs)
12150 : : {
12151 : 363 : char *colname = strVal(lfirst(lc_colname));
12152 : : JsonExpr *colexpr;
12153 : : Oid typid;
12154 : : int32 typmod;
12155 : : bool ordinality;
12156 : : JsonBehaviorType default_behavior;
12157 : :
12158 : 363 : typid = lfirst_oid(lc_coltype);
12159 : 363 : typmod = lfirst_int(lc_coltypmod);
12160 : 363 : colexpr = castNode(JsonExpr, lfirst(lc_colvalexpr));
12161 : :
12162 : : /* Skip columns that don't belong to this scan. */
706 12163 [ + + + + ]: 363 : if (scan->colMin < 0 || colnum < scan->colMin)
12164 : : {
12165 : 132 : colnum++;
12166 : 132 : continue;
12167 : : }
12168 [ + + ]: 231 : if (colnum > scan->colMax)
12169 : 24 : break;
12170 : :
12171 [ + + ]: 207 : if (colnum > scan->colMin)
710 12172 : 129 : appendStringInfoString(buf, ", ");
12173 : :
12174 : 207 : colnum++;
12175 : :
12176 : 207 : ordinality = !colexpr;
12177 : :
12178 : 207 : appendContextKeyword(context, "", 0, 0, 0);
12179 : :
12180 [ + + ]: 405 : appendStringInfo(buf, "%s %s", quote_identifier(colname),
12181 : : ordinality ? "FOR ORDINALITY" :
12182 : 198 : format_type_with_typemod(typid, typmod));
12183 [ + + ]: 207 : if (ordinality)
12184 : 9 : continue;
12185 : :
12186 : : /*
12187 : : * Set default_behavior to guide get_json_expr_options() on whether to
12188 : : * emit the ON ERROR / EMPTY clauses.
12189 : : */
12190 [ + + ]: 198 : if (colexpr->op == JSON_EXISTS_OP)
12191 : : {
12192 : 18 : appendStringInfoString(buf, " EXISTS");
12193 : 18 : default_behavior = JSON_BEHAVIOR_FALSE;
12194 : : }
12195 : : else
12196 : : {
12197 [ + + ]: 180 : if (colexpr->op == JSON_QUERY_OP)
12198 : : {
12199 : : char typcategory;
12200 : : bool typispreferred;
12201 : :
12202 : 87 : get_type_category_preferred(typid, &typcategory, &typispreferred);
12203 : :
12204 [ + + ]: 87 : if (typcategory == TYPCATEGORY_STRING)
12205 : 18 : appendStringInfoString(buf,
12206 [ - + ]: 18 : colexpr->format->format_type == JS_FORMAT_JSONB ?
12207 : : " FORMAT JSONB" : " FORMAT JSON");
12208 : : }
12209 : :
12210 : 180 : default_behavior = JSON_BEHAVIOR_NULL;
12211 : : }
12212 : :
12213 : 198 : appendStringInfoString(buf, " PATH ");
12214 : :
12215 : 198 : get_json_path_spec(colexpr->path_spec, context, showimplicit);
12216 : :
12217 : 198 : get_json_expr_options(colexpr, context, default_behavior);
12218 : : }
12219 : :
706 12220 [ + + ]: 90 : if (scan->child)
12221 : 21 : get_json_table_nested_columns(tf, scan->child, context, showimplicit,
12222 : 21 : scan->colMin >= 0);
12223 : :
710 12224 [ + + ]: 90 : if (PRETTY_INDENT(context))
12225 : 69 : context->indentLevel -= PRETTYINDENT_VAR;
12226 : :
12227 : 90 : appendContextKeyword(context, ")", 0, 0, 0);
12228 : 90 : }
12229 : :
12230 : : /* ----------
12231 : : * get_json_table - Parse back a JSON_TABLE function
12232 : : * ----------
12233 : : */
12234 : : static void
12235 : 54 : get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit)
12236 : : {
12237 : 54 : StringInfo buf = context->buf;
12238 : 54 : JsonExpr *jexpr = castNode(JsonExpr, tf->docexpr);
12239 : 54 : JsonTablePathScan *root = castNode(JsonTablePathScan, tf->plan);
12240 : :
12241 : 54 : appendStringInfoString(buf, "JSON_TABLE(");
12242 : :
12243 [ + + ]: 54 : if (PRETTY_INDENT(context))
12244 : 33 : context->indentLevel += PRETTYINDENT_VAR;
12245 : :
12246 : 54 : appendContextKeyword(context, "", 0, 0, 0);
12247 : :
12248 : 54 : get_rule_expr(jexpr->formatted_expr, context, showimplicit);
12249 : :
12250 : 54 : appendStringInfoString(buf, ", ");
12251 : :
12252 : 54 : get_const_expr(root->path->value, context, -1);
12253 : :
12254 : 54 : appendStringInfo(buf, " AS %s", quote_identifier(root->path->name));
12255 : :
12256 [ + + ]: 54 : if (jexpr->passing_values)
12257 : : {
12258 : : ListCell *lc1,
12259 : : *lc2;
12260 : 42 : bool needcomma = false;
12261 : :
12262 : 42 : appendStringInfoChar(buf, ' ');
12263 : 42 : appendContextKeyword(context, "PASSING ", 0, 0, 0);
12264 : :
12265 [ + + ]: 42 : if (PRETTY_INDENT(context))
12266 : 21 : context->indentLevel += PRETTYINDENT_VAR;
12267 : :
12268 [ + - + + : 126 : forboth(lc1, jexpr->passing_names,
+ - + + +
+ + - +
+ ]
12269 : : lc2, jexpr->passing_values)
12270 : : {
12271 [ + + ]: 84 : if (needcomma)
12272 : 42 : appendStringInfoString(buf, ", ");
12273 : 84 : needcomma = true;
12274 : :
12275 : 84 : appendContextKeyword(context, "", 0, 0, 0);
12276 : :
12277 : 84 : get_rule_expr((Node *) lfirst(lc2), context, false);
12278 : 84 : appendStringInfo(buf, " AS %s",
12279 : 84 : quote_identifier((lfirst_node(String, lc1))->sval)
12280 : : );
12281 : : }
12282 : :
12283 [ + + ]: 42 : if (PRETTY_INDENT(context))
12284 : 21 : context->indentLevel -= PRETTYINDENT_VAR;
12285 : : }
12286 : :
706 12287 : 54 : get_json_table_columns(tf, castNode(JsonTablePathScan, tf->plan), context,
12288 : : showimplicit);
12289 : :
555 12290 [ + + ]: 54 : if (jexpr->on_error->btype != JSON_BEHAVIOR_EMPTY_ARRAY)
710 12291 : 3 : get_json_behavior(jexpr->on_error, context, "ERROR");
12292 : :
12293 [ + + ]: 54 : if (PRETTY_INDENT(context))
12294 : 33 : context->indentLevel -= PRETTYINDENT_VAR;
12295 : :
12296 : 54 : appendContextKeyword(context, ")", 0, 0, 0);
12297 : 54 : }
12298 : :
12299 : : /* ----------
12300 : : * get_tablefunc - Parse back a table function
12301 : : * ----------
12302 : : */
12303 : : static void
12304 : 85 : get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit)
12305 : : {
12306 : : /* XMLTABLE and JSON_TABLE are the only existing implementations. */
12307 : :
12308 [ + + ]: 85 : if (tf->functype == TFT_XMLTABLE)
12309 : 31 : get_xmltable(tf, context, showimplicit);
12310 [ + - ]: 54 : else if (tf->functype == TFT_JSON_TABLE)
12311 : 54 : get_json_table(tf, context, showimplicit);
12312 : 85 : }
12313 : :
12314 : : /* ----------
12315 : : * get_from_clause - Parse back a FROM clause
12316 : : *
12317 : : * "prefix" is the keyword that denotes the start of the list of FROM
12318 : : * elements. It is FROM when used to parse back SELECT and UPDATE, but
12319 : : * is USING when parsing back DELETE.
12320 : : * ----------
12321 : : */
12322 : : static void
7647 neilc@samurai.com 12323 : 2478 : get_from_clause(Query *query, const char *prefix, deparse_context *context)
12324 : : {
9315 tgl@sss.pgh.pa.us 12325 : 2478 : StringInfo buf = context->buf;
8264 12326 : 2478 : bool first = true;
12327 : : ListCell *l;
12328 : :
12329 : : /*
12330 : : * We use the query's jointree as a guide to what to print. However, we
12331 : : * must ignore auto-added RTEs that are marked not inFromCl. (These can
12332 : : * only appear at the top level of the jointree, so it's sufficient to
12333 : : * check here.) This check also ensures we ignore the rule pseudo-RTEs
12334 : : * for NEW and OLD.
12335 : : */
9298 12336 [ + + + + : 4930 : foreach(l, query->jointree->fromlist)
+ + ]
12337 : : {
9124 bruce@momjian.us 12338 : 2452 : Node *jtnode = (Node *) lfirst(l);
12339 : :
9315 tgl@sss.pgh.pa.us 12340 [ + + ]: 2452 : if (IsA(jtnode, RangeTblRef))
12341 : : {
12342 : 1967 : int varno = ((RangeTblRef *) jtnode)->rtindex;
12343 : 1967 : RangeTblEntry *rte = rt_fetch(varno, query->rtable);
12344 : :
12345 [ + + ]: 1967 : if (!rte->inFromCl)
12346 : 200 : continue;
12347 : : }
12348 : :
8264 12349 [ + + ]: 2252 : if (first)
12350 : : {
7647 neilc@samurai.com 12351 : 2067 : appendContextKeyword(context, prefix,
12352 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
8264 tgl@sss.pgh.pa.us 12353 : 2067 : first = false;
12354 : :
5138 andrew@dunslane.net 12355 : 2067 : get_from_clause_item(jtnode, query, context);
12356 : : }
12357 : : else
12358 : : {
12359 : : StringInfoData itembuf;
12360 : :
8259 bruce@momjian.us 12361 : 185 : appendStringInfoString(buf, ", ");
12362 : :
12363 : : /*
12364 : : * Put the new FROM item's text into itembuf so we can decide
12365 : : * after we've got it whether or not it needs to go on a new line.
12366 : : */
4829 tgl@sss.pgh.pa.us 12367 : 185 : initStringInfo(&itembuf);
12368 : 185 : context->buf = &itembuf;
12369 : :
5138 andrew@dunslane.net 12370 : 185 : get_from_clause_item(jtnode, query, context);
12371 : :
12372 : : /* Restore context's output buffer */
12373 : 185 : context->buf = buf;
12374 : :
12375 : : /* Consider line-wrapping if enabled */
4829 tgl@sss.pgh.pa.us 12376 [ + - + - ]: 185 : if (PRETTY_INDENT(context) && context->wrapColumn >= 0)
12377 : : {
12378 : : /* Does the new item start with a new line? */
4507 12379 [ + - - + ]: 185 : if (itembuf.len > 0 && itembuf.data[0] == '\n')
12380 : : {
12381 : : /* If so, we shouldn't add anything */
12382 : : /* instead, remove any trailing spaces currently in buf */
4507 tgl@sss.pgh.pa.us 12383 :UBC 0 : removeStringInfoSpaces(buf);
12384 : : }
12385 : : else
12386 : : {
12387 : : char *trailing_nl;
12388 : :
12389 : : /* Locate the start of the current line in the buffer */
4507 tgl@sss.pgh.pa.us 12390 :CBC 185 : trailing_nl = strrchr(buf->data, '\n');
12391 [ - + ]: 185 : if (trailing_nl == NULL)
4507 tgl@sss.pgh.pa.us 12392 :UBC 0 : trailing_nl = buf->data;
12393 : : else
4507 tgl@sss.pgh.pa.us 12394 :CBC 185 : trailing_nl++;
12395 : :
12396 : : /*
12397 : : * Add a newline, plus some indentation, if the new item
12398 : : * would cause an overflow.
12399 : : */
12400 [ + - ]: 185 : if (strlen(trailing_nl) + itembuf.len > context->wrapColumn)
12401 : 185 : appendContextKeyword(context, "", -PRETTYINDENT_STD,
12402 : : PRETTYINDENT_STD,
12403 : : PRETTYINDENT_VAR);
12404 : : }
12405 : : }
12406 : :
12407 : : /* Add the new item */
2427 drowley@postgresql.o 12408 : 185 : appendBinaryStringInfo(buf, itembuf.data, itembuf.len);
12409 : :
12410 : : /* clean up */
4829 tgl@sss.pgh.pa.us 12411 : 185 : pfree(itembuf.data);
12412 : : }
12413 : : }
9315 12414 : 2478 : }
12415 : :
12416 : : static void
12417 : 3768 : get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
12418 : : {
12419 : 3768 : StringInfo buf = context->buf;
4822 12420 : 3768 : deparse_namespace *dpns = (deparse_namespace *) linitial(context->namespaces);
12421 : :
9315 12422 [ + + ]: 3768 : if (IsA(jtnode, RangeTblRef))
12423 : : {
12424 : 3010 : int varno = ((RangeTblRef *) jtnode)->rtindex;
12425 : 3010 : RangeTblEntry *rte = rt_fetch(varno, query->rtable);
4822 12426 : 3010 : deparse_columns *colinfo = deparse_columns_fetch(varno, dpns);
4497 12427 : 3010 : RangeTblFunction *rtfunc1 = NULL;
12428 : :
4968 12429 [ + + ]: 3010 : if (rte->lateral)
12430 : 62 : appendStringInfoString(buf, "LATERAL ");
12431 : :
12432 : : /* Print the FROM item proper */
8759 12433 [ + + + + : 3010 : switch (rte->rtekind)
+ + - ]
12434 : : {
12435 : 2282 : case RTE_RELATION:
12436 : : /* Normal relation RTE */
12437 : 4564 : appendStringInfo(buf, "%s%s",
12438 [ + + ]: 2282 : only_marker(rte),
12439 : : generate_relation_name(rte->relid,
12440 : : context->namespaces));
12441 : 2282 : break;
12442 : 146 : case RTE_SUBQUERY:
12443 : : /* Subquery RTE */
12444 : 146 : appendStringInfoChar(buf, '(');
8259 bruce@momjian.us 12445 : 146 : get_query_def(rte->subquery, buf, context->namespaces, NULL,
12446 : : true,
12447 : : context->prettyFlags, context->wrapColumn,
12448 : : context->indentLevel);
8759 tgl@sss.pgh.pa.us 12449 : 146 : appendStringInfoChar(buf, ')');
12450 : 146 : break;
8708 12451 : 435 : case RTE_FUNCTION:
12452 : : /* Function RTE */
4497 12453 : 435 : rtfunc1 = (RangeTblFunction *) linitial(rte->functions);
12454 : :
12455 : : /*
12456 : : * Omit ROWS FROM() syntax for just one function, unless it
12457 : : * has both a coldeflist and WITH ORDINALITY. If it has both,
12458 : : * we must use ROWS FROM() syntax to avoid ambiguity about
12459 : : * whether the coldeflist includes the ordinality column.
12460 : : */
12461 [ + + ]: 435 : if (list_length(rte->functions) == 1 &&
12462 [ - + - - ]: 420 : (rtfunc1->funccolnames == NIL || !rte->funcordinality))
12463 : : {
3167 12464 : 420 : get_rule_expr_funccall(rtfunc1->funcexpr, context, true);
12465 : : /* we'll print the coldeflist below, if it has one */
12466 : : }
12467 : : else
12468 : : {
12469 : : bool all_unnest;
12470 : : ListCell *lc;
12471 : :
12472 : : /*
12473 : : * If all the function calls in the list are to unnest,
12474 : : * and none need a coldeflist, then collapse the list back
12475 : : * down to UNNEST(args). (If we had more than one
12476 : : * built-in unnest function, this would get more
12477 : : * difficult.)
12478 : : *
12479 : : * XXX This is pretty ugly, since it makes not-terribly-
12480 : : * future-proof assumptions about what the parser would do
12481 : : * with the output; but the alternative is to emit our
12482 : : * nonstandard ROWS FROM() notation for what might have
12483 : : * been a perfectly spec-compliant multi-argument
12484 : : * UNNEST().
12485 : : */
4497 12486 : 15 : all_unnest = true;
12487 [ + - + + : 39 : foreach(lc, rte->functions)
+ + ]
12488 : : {
12489 : 33 : RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
12490 : :
12491 [ + - ]: 33 : if (!IsA(rtfunc->funcexpr, FuncExpr) ||
1959 12492 [ + + ]: 33 : ((FuncExpr *) rtfunc->funcexpr)->funcid != F_UNNEST_ANYARRAY ||
4497 12493 [ - + ]: 24 : rtfunc->funccolnames != NIL)
12494 : : {
12495 : 9 : all_unnest = false;
12496 : 9 : break;
12497 : : }
12498 : : }
12499 : :
12500 [ + + ]: 15 : if (all_unnest)
12501 : : {
12502 : 6 : List *allargs = NIL;
12503 : :
12504 [ + - + + : 24 : foreach(lc, rte->functions)
+ + ]
12505 : : {
12506 : 18 : RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
12507 : 18 : List *args = ((FuncExpr *) rtfunc->funcexpr)->args;
12508 : :
2407 12509 : 18 : allargs = list_concat(allargs, args);
12510 : : }
12511 : :
4497 12512 : 6 : appendStringInfoString(buf, "UNNEST(");
12513 : 6 : get_rule_expr((Node *) allargs, context, true);
12514 : 6 : appendStringInfoChar(buf, ')');
12515 : : }
12516 : : else
12517 : : {
12518 : 9 : int funcno = 0;
12519 : :
4478 noah@leadboat.com 12520 : 9 : appendStringInfoString(buf, "ROWS FROM(");
4497 tgl@sss.pgh.pa.us 12521 [ + - + + : 33 : foreach(lc, rte->functions)
+ + ]
12522 : : {
12523 : 24 : RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
12524 : :
12525 [ + + ]: 24 : if (funcno > 0)
12526 : 15 : appendStringInfoString(buf, ", ");
3167 12527 : 24 : get_rule_expr_funccall(rtfunc->funcexpr, context, true);
4497 12528 [ + + ]: 24 : if (rtfunc->funccolnames != NIL)
12529 : : {
12530 : : /* Reconstruct the column definition list */
12531 : 3 : appendStringInfoString(buf, " AS ");
12532 : 3 : get_from_clause_coldeflist(rtfunc,
12533 : : NULL,
12534 : : context);
12535 : : }
12536 : 24 : funcno++;
12537 : : }
12538 : 9 : appendStringInfoChar(buf, ')');
12539 : : }
12540 : : /* prevent printing duplicate coldeflist below */
12541 : 15 : rtfunc1 = NULL;
12542 : : }
4612 stark@mit.edu 12543 [ + + ]: 435 : if (rte->funcordinality)
12544 : 9 : appendStringInfoString(buf, " WITH ORDINALITY");
8708 tgl@sss.pgh.pa.us 12545 : 435 : break;
3294 alvherre@alvh.no-ip. 12546 : 49 : case RTE_TABLEFUNC:
12547 : 49 : get_tablefunc(rte->tablefunc, context, true);
12548 : 49 : break;
7165 mail@joeconway.com 12549 : 6 : case RTE_VALUES:
12550 : : /* Values list RTE */
4036 tgl@sss.pgh.pa.us 12551 : 6 : appendStringInfoChar(buf, '(');
7165 mail@joeconway.com 12552 : 6 : get_values_def(rte->values_lists, context);
4036 tgl@sss.pgh.pa.us 12553 : 6 : appendStringInfoChar(buf, ')');
7165 mail@joeconway.com 12554 : 6 : break;
6371 tgl@sss.pgh.pa.us 12555 : 92 : case RTE_CTE:
12556 : 92 : appendStringInfoString(buf, quote_identifier(rte->ctename));
12557 : 92 : break;
8759 tgl@sss.pgh.pa.us 12558 :UBC 0 : default:
8267 12559 [ # # ]: 0 : elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
12560 : : break;
12561 : : }
12562 : :
12563 : : /* Print the relation alias, if needed */
1122 tgl@sss.pgh.pa.us 12564 :CBC 3010 : get_rte_alias(rte, varno, false, context);
12565 : :
12566 : : /* Print the column definitions or aliases, if needed */
4497 12567 [ + + - + ]: 3010 : if (rtfunc1 && rtfunc1->funccolnames != NIL)
12568 : : {
12569 : : /* Reconstruct the columndef list, which is also the aliases */
4497 tgl@sss.pgh.pa.us 12570 :UBC 0 : get_from_clause_coldeflist(rtfunc1, colinfo, context);
12571 : : }
12572 : : else
12573 : : {
12574 : : /* Else print column aliases as needed */
4822 tgl@sss.pgh.pa.us 12575 :CBC 3010 : get_column_alias_list(colinfo, context);
12576 : : }
12577 : :
12578 : : /* Tablesample clause must go after any alias */
3886 12579 [ + + + + ]: 3010 : if (rte->rtekind == RTE_RELATION && rte->tablesample)
12580 : 16 : get_tablesample_def(rte->tablesample, context);
12581 : : }
9315 12582 [ + - ]: 758 : else if (IsA(jtnode, JoinExpr))
12583 : : {
12584 : 758 : JoinExpr *j = (JoinExpr *) jtnode;
4822 12585 : 758 : deparse_columns *colinfo = deparse_columns_fetch(j->rtindex, dpns);
12586 : : bool need_paren_on_right;
12587 : :
8264 12588 : 1735 : need_paren_on_right = PRETTY_PAREN(context) &&
7567 12589 [ + + - + ]: 758 : !IsA(j->rarg, RangeTblRef) &&
2129 tgl@sss.pgh.pa.us 12590 [ # # # # ]:UBC 0 : !(IsA(j->rarg, JoinExpr) && ((JoinExpr *) j->rarg)->alias != NULL);
12591 : :
8264 tgl@sss.pgh.pa.us 12592 [ + + + + ]:CBC 758 : if (!PRETTY_PAREN(context) || j->alias != NULL)
8259 bruce@momjian.us 12593 : 593 : appendStringInfoChar(buf, '(');
12594 : :
9315 tgl@sss.pgh.pa.us 12595 : 758 : get_from_clause_item(j->larg, query, context);
12596 : :
4822 12597 [ + + + - : 758 : switch (j->jointype)
- ]
12598 : : {
12599 : 416 : case JOIN_INNER:
12600 [ + + ]: 416 : if (j->quals)
12601 : 395 : appendContextKeyword(context, " JOIN ",
12602 : : -PRETTYINDENT_STD,
12603 : : PRETTYINDENT_STD,
12604 : : PRETTYINDENT_JOIN);
12605 : : else
12606 : 21 : appendContextKeyword(context, " CROSS JOIN ",
12607 : : -PRETTYINDENT_STD,
12608 : : PRETTYINDENT_STD,
12609 : : PRETTYINDENT_JOIN);
12610 : 416 : break;
12611 : 291 : case JOIN_LEFT:
12612 : 291 : appendContextKeyword(context, " LEFT JOIN ",
12613 : : -PRETTYINDENT_STD,
12614 : : PRETTYINDENT_STD,
12615 : : PRETTYINDENT_JOIN);
12616 : 291 : break;
12617 : 51 : case JOIN_FULL:
12618 : 51 : appendContextKeyword(context, " FULL JOIN ",
12619 : : -PRETTYINDENT_STD,
12620 : : PRETTYINDENT_STD,
12621 : : PRETTYINDENT_JOIN);
12622 : 51 : break;
4822 tgl@sss.pgh.pa.us 12623 :UBC 0 : case JOIN_RIGHT:
12624 : 0 : appendContextKeyword(context, " RIGHT JOIN ",
12625 : : -PRETTYINDENT_STD,
12626 : : PRETTYINDENT_STD,
12627 : : PRETTYINDENT_JOIN);
12628 : 0 : break;
12629 : 0 : default:
12630 [ # # ]: 0 : elog(ERROR, "unrecognized join type: %d",
12631 : : (int) j->jointype);
12632 : : }
12633 : :
8264 tgl@sss.pgh.pa.us 12634 [ - + ]:CBC 758 : if (need_paren_on_right)
8259 bruce@momjian.us 12635 :UBC 0 : appendStringInfoChar(buf, '(');
9315 tgl@sss.pgh.pa.us 12636 :CBC 758 : get_from_clause_item(j->rarg, query, context);
8264 12637 [ - + ]: 758 : if (need_paren_on_right)
8259 bruce@momjian.us 12638 :UBC 0 : appendStringInfoChar(buf, ')');
12639 : :
4822 tgl@sss.pgh.pa.us 12640 [ + + ]:CBC 758 : if (j->usingClause)
12641 : : {
12642 : : ListCell *lc;
12643 : 215 : bool first = true;
12644 : :
4518 rhaas@postgresql.org 12645 : 215 : appendStringInfoString(buf, " USING (");
12646 : : /* Use the assigned names, not what's in usingClause */
4822 tgl@sss.pgh.pa.us 12647 [ + - + + : 508 : foreach(lc, colinfo->usingNames)
+ + ]
12648 : : {
12649 : 293 : char *colname = (char *) lfirst(lc);
12650 : :
12651 [ + + ]: 293 : if (first)
12652 : 215 : first = false;
12653 : : else
4518 rhaas@postgresql.org 12654 : 78 : appendStringInfoString(buf, ", ");
4822 tgl@sss.pgh.pa.us 12655 : 293 : appendStringInfoString(buf, quote_identifier(colname));
12656 : : }
12657 : 215 : appendStringInfoChar(buf, ')');
12658 : :
1810 peter@eisentraut.org 12659 [ + + ]: 215 : if (j->join_using_alias)
12660 : 6 : appendStringInfo(buf, " AS %s",
12661 : 6 : quote_identifier(j->join_using_alias->aliasname));
12662 : : }
4822 tgl@sss.pgh.pa.us 12663 [ + + ]: 543 : else if (j->quals)
12664 : : {
4518 rhaas@postgresql.org 12665 : 519 : appendStringInfoString(buf, " ON ");
4822 tgl@sss.pgh.pa.us 12666 [ + + ]: 519 : if (!PRETTY_PAREN(context))
12667 : 516 : appendStringInfoChar(buf, '(');
12668 : 519 : get_rule_expr(j->quals, context, false);
12669 [ + + ]: 519 : if (!PRETTY_PAREN(context))
12670 : 516 : appendStringInfoChar(buf, ')');
12671 : : }
3160 12672 [ + + ]: 24 : else if (j->jointype != JOIN_INNER)
12673 : : {
12674 : : /* If we didn't say CROSS JOIN above, we must provide an ON */
12675 : 3 : appendStringInfoString(buf, " ON TRUE");
12676 : : }
12677 : :
8264 12678 [ + + + + ]: 758 : if (!PRETTY_PAREN(context) || j->alias != NULL)
8259 bruce@momjian.us 12679 : 593 : appendStringInfoChar(buf, ')');
12680 : :
12681 : : /* Yes, it's correct to put alias after the right paren ... */
9315 tgl@sss.pgh.pa.us 12682 [ + + ]: 758 : if (j->alias != NULL)
12683 : : {
12684 : : /*
12685 : : * Note that it's correct to emit an alias clause if and only if
12686 : : * there was one originally. Otherwise we'd be converting a named
12687 : : * join to unnamed or vice versa, which creates semantic
12688 : : * subtleties we don't want. However, we might print a different
12689 : : * alias name than was there originally.
12690 : : */
12691 : 54 : appendStringInfo(buf, " %s",
2468 12692 : 54 : quote_identifier(get_rtable_name(j->rtindex,
12693 : : context)));
4822 12694 : 54 : get_column_alias_list(colinfo, context);
12695 : : }
12696 : : }
12697 : : else
8267 tgl@sss.pgh.pa.us 12698 [ # # ]:UBC 0 : elog(ERROR, "unrecognized node type: %d",
12699 : : (int) nodeTag(jtnode));
9315 tgl@sss.pgh.pa.us 12700 :CBC 3768 : }
12701 : :
12702 : : /*
12703 : : * get_rte_alias - print the relation's alias, if needed
12704 : : *
12705 : : * If printed, the alias is preceded by a space, or by " AS " if use_as is true.
12706 : : */
12707 : : static void
1122 12708 : 3304 : get_rte_alias(RangeTblEntry *rte, int varno, bool use_as,
12709 : : deparse_context *context)
12710 : : {
12711 : 3304 : deparse_namespace *dpns = (deparse_namespace *) linitial(context->namespaces);
12712 : 3304 : char *refname = get_rtable_name(varno, context);
12713 : 3304 : deparse_columns *colinfo = deparse_columns_fetch(varno, dpns);
12714 : 3304 : bool printalias = false;
12715 : :
12716 [ + + ]: 3304 : if (rte->alias != NULL)
12717 : : {
12718 : : /* Always print alias if user provided one */
12719 : 1547 : printalias = true;
12720 : : }
12721 [ + + ]: 1757 : else if (colinfo->printaliases)
12722 : : {
12723 : : /* Always print alias if we need to print column aliases */
12724 : 165 : printalias = true;
12725 : : }
12726 [ + + ]: 1592 : else if (rte->rtekind == RTE_RELATION)
12727 : : {
12728 : : /*
12729 : : * No need to print alias if it's same as relation name (this would
12730 : : * normally be the case, but not if set_rtable_names had to resolve a
12731 : : * conflict).
12732 : : */
12733 [ + + ]: 1456 : if (strcmp(refname, get_relation_name(rte->relid)) != 0)
12734 : 40 : printalias = true;
12735 : : }
12736 [ - + ]: 136 : else if (rte->rtekind == RTE_FUNCTION)
12737 : : {
12738 : : /*
12739 : : * For a function RTE, always print alias. This covers possible
12740 : : * renaming of the function and/or instability of the FigureColname
12741 : : * rules for things that aren't simple functions. Note we'd need to
12742 : : * force it anyway for the columndef list case.
12743 : : */
1122 tgl@sss.pgh.pa.us 12744 :UBC 0 : printalias = true;
12745 : : }
1122 tgl@sss.pgh.pa.us 12746 [ + + ]:CBC 136 : else if (rte->rtekind == RTE_SUBQUERY ||
12747 [ + + ]: 124 : rte->rtekind == RTE_VALUES)
12748 : : {
12749 : : /*
12750 : : * For a subquery, always print alias. This makes the output
12751 : : * SQL-spec-compliant, even though we allow such aliases to be omitted
12752 : : * on input.
12753 : : */
12754 : 18 : printalias = true;
12755 : : }
12756 [ + + ]: 118 : else if (rte->rtekind == RTE_CTE)
12757 : : {
12758 : : /*
12759 : : * No need to print alias if it's same as CTE name (this would
12760 : : * normally be the case, but not if set_rtable_names had to resolve a
12761 : : * conflict).
12762 : : */
12763 [ + + ]: 72 : if (strcmp(refname, rte->ctename) != 0)
12764 : 11 : printalias = true;
12765 : : }
12766 : :
12767 [ + + ]: 3304 : if (printalias)
12768 [ + + ]: 1781 : appendStringInfo(context->buf, "%s%s",
12769 : : use_as ? " AS " : " ",
12770 : : quote_identifier(refname));
12771 : 3304 : }
12772 : :
12773 : : /*
12774 : : * get_column_alias_list - print column alias list for an RTE
12775 : : *
12776 : : * Caller must already have printed the relation's alias name.
12777 : : */
12778 : : static void
4822 12779 : 3064 : get_column_alias_list(deparse_columns *colinfo, deparse_context *context)
12780 : : {
7878 12781 : 3064 : StringInfo buf = context->buf;
12782 : : int i;
12783 : 3064 : bool first = true;
12784 : :
12785 : : /* Don't print aliases if not needed */
4822 12786 [ + + ]: 3064 : if (!colinfo->printaliases)
12787 : 2449 : return;
12788 : :
12789 [ + + ]: 4968 : for (i = 0; i < colinfo->num_new_cols; i++)
12790 : : {
12791 : 4353 : char *colname = colinfo->new_colnames[i];
12792 : :
7878 12793 [ + + ]: 4353 : if (first)
12794 : : {
12795 : 615 : appendStringInfoChar(buf, '(');
12796 : 615 : first = false;
12797 : : }
12798 : : else
4518 rhaas@postgresql.org 12799 : 3738 : appendStringInfoString(buf, ", ");
4822 tgl@sss.pgh.pa.us 12800 : 4353 : appendStringInfoString(buf, quote_identifier(colname));
12801 : : }
7878 12802 [ + - ]: 615 : if (!first)
12803 : 615 : appendStringInfoChar(buf, ')');
12804 : : }
12805 : :
12806 : : /*
12807 : : * get_from_clause_coldeflist - reproduce FROM clause coldeflist
12808 : : *
12809 : : * When printing a top-level coldeflist (which is syntactically also the
12810 : : * relation's column alias list), use column names from colinfo. But when
12811 : : * printing a coldeflist embedded inside ROWS FROM(), we prefer to use the
12812 : : * original coldeflist's names, which are available in rtfunc->funccolnames.
12813 : : * Pass NULL for colinfo to select the latter behavior.
12814 : : *
12815 : : * The coldeflist is appended immediately (no space) to buf. Caller is
12816 : : * responsible for ensuring that an alias or AS is present before it.
12817 : : */
12818 : : static void
4497 12819 : 3 : get_from_clause_coldeflist(RangeTblFunction *rtfunc,
12820 : : deparse_columns *colinfo,
12821 : : deparse_context *context)
12822 : : {
8599 12823 : 3 : StringInfo buf = context->buf;
12824 : : ListCell *l1;
12825 : : ListCell *l2;
12826 : : ListCell *l3;
12827 : : ListCell *l4;
12828 : : int i;
12829 : :
12830 : 3 : appendStringInfoChar(buf, '(');
12831 : :
4822 12832 : 3 : i = 0;
2572 12833 [ + - + + : 12 : forfour(l1, rtfunc->funccoltypes,
+ - + + +
- + + + -
+ + + + +
- + - + -
+ + ]
12834 : : l2, rtfunc->funccoltypmods,
12835 : : l3, rtfunc->funccolcollations,
12836 : : l4, rtfunc->funccolnames)
12837 : : {
4822 12838 : 9 : Oid atttypid = lfirst_oid(l1);
12839 : 9 : int32 atttypmod = lfirst_int(l2);
12840 : 9 : Oid attcollation = lfirst_oid(l3);
12841 : : char *attname;
12842 : :
4497 12843 [ - + ]: 9 : if (colinfo)
4497 tgl@sss.pgh.pa.us 12844 :UBC 0 : attname = colinfo->colnames[i];
12845 : : else
4497 tgl@sss.pgh.pa.us 12846 :CBC 9 : attname = strVal(lfirst(l4));
12847 : :
4822 12848 [ - + ]: 9 : Assert(attname); /* shouldn't be any dropped columns here */
12849 : :
8599 12850 [ + + ]: 9 : if (i > 0)
4518 rhaas@postgresql.org 12851 : 6 : appendStringInfoString(buf, ", ");
8599 tgl@sss.pgh.pa.us 12852 : 9 : appendStringInfo(buf, "%s %s",
12853 : : quote_identifier(attname),
12854 : : format_type_with_typemod(atttypid, atttypmod));
5441 12855 [ + + - + ]: 12 : if (OidIsValid(attcollation) &&
12856 : 3 : attcollation != get_typcollation(atttypid))
5470 tgl@sss.pgh.pa.us 12857 :UBC 0 : appendStringInfo(buf, " COLLATE %s",
12858 : : generate_collation_name(attcollation));
12859 : :
8599 tgl@sss.pgh.pa.us 12860 :CBC 9 : i++;
12861 : : }
12862 : :
12863 : 3 : appendStringInfoChar(buf, ')');
12864 : 3 : }
12865 : :
12866 : : /*
12867 : : * get_tablesample_def - print a TableSampleClause
12868 : : */
12869 : : static void
3886 12870 : 16 : get_tablesample_def(TableSampleClause *tablesample, deparse_context *context)
12871 : : {
12872 : 16 : StringInfo buf = context->buf;
12873 : : Oid argtypes[1];
12874 : : int nargs;
12875 : : ListCell *l;
12876 : :
12877 : : /*
12878 : : * We should qualify the handler's function name if it wouldn't be
12879 : : * resolved by lookup in the current search path.
12880 : : */
12881 : 16 : argtypes[0] = INTERNALOID;
12882 : 16 : appendStringInfo(buf, " TABLESAMPLE %s (",
12883 : : generate_function_name(tablesample->tsmhandler, 1,
12884 : : NIL, argtypes,
12885 : : false, NULL, false));
12886 : :
12887 : 16 : nargs = 0;
12888 [ + - + + : 32 : foreach(l, tablesample->args)
+ + ]
12889 : : {
12890 [ - + ]: 16 : if (nargs++ > 0)
3886 tgl@sss.pgh.pa.us 12891 :UBC 0 : appendStringInfoString(buf, ", ");
3886 tgl@sss.pgh.pa.us 12892 :CBC 16 : get_rule_expr((Node *) lfirst(l), context, false);
12893 : : }
12894 : 16 : appendStringInfoChar(buf, ')');
12895 : :
12896 [ + + ]: 16 : if (tablesample->repeatable != NULL)
12897 : : {
12898 : 8 : appendStringInfoString(buf, " REPEATABLE (");
12899 : 8 : get_rule_expr((Node *) tablesample->repeatable, context, false);
12900 : 8 : appendStringInfoChar(buf, ')');
12901 : : }
12902 : 16 : }
12903 : :
12904 : : /*
12905 : : * get_opclass_name - fetch name of an index operator class
12906 : : *
12907 : : * The opclass name is appended (after a space) to buf.
12908 : : *
12909 : : * Output is suppressed if the opclass is the default for the given
12910 : : * actual_datatype. (If you don't want this behavior, just pass
12911 : : * InvalidOid for actual_datatype.)
12912 : : */
12913 : : static void
8928 12914 : 6399 : get_opclass_name(Oid opclass, Oid actual_datatype,
12915 : : StringInfo buf)
12916 : : {
12917 : : HeapTuple ht_opc;
12918 : : Form_pg_opclass opcrec;
12919 : : char *opcname;
12920 : : char *nspname;
12921 : :
5873 rhaas@postgresql.org 12922 : 6399 : ht_opc = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
8931 tgl@sss.pgh.pa.us 12923 [ - + ]: 6399 : if (!HeapTupleIsValid(ht_opc))
8931 tgl@sss.pgh.pa.us 12924 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for opclass %u", opclass);
8931 tgl@sss.pgh.pa.us 12925 :CBC 6399 : opcrec = (Form_pg_opclass) GETSTRUCT(ht_opc);
12926 : :
7022 12927 [ + + + + ]: 12778 : if (!OidIsValid(actual_datatype) ||
12928 : 6379 : GetDefaultOpClass(actual_datatype, opcrec->opcmethod) != opclass)
12929 : : {
12930 : : /* Okay, we need the opclass name. Do we need to qualify it? */
8717 12931 : 279 : opcname = NameStr(opcrec->opcname);
7022 12932 [ + - ]: 279 : if (OpclassIsVisible(opclass))
8717 12933 : 279 : appendStringInfo(buf, " %s", quote_identifier(opcname));
12934 : : else
12935 : : {
1692 tgl@sss.pgh.pa.us 12936 :UBC 0 : nspname = get_namespace_name_or_temp(opcrec->opcnamespace);
8717 12937 : 0 : appendStringInfo(buf, " %s.%s",
12938 : : quote_identifier(nspname),
12939 : : quote_identifier(opcname));
12940 : : }
12941 : : }
8931 tgl@sss.pgh.pa.us 12942 :CBC 6399 : ReleaseSysCache(ht_opc);
12943 : 6399 : }
12944 : :
12945 : : /*
12946 : : * generate_opclass_name
12947 : : * Compute the name to display for an opclass specified by OID
12948 : : *
12949 : : * The result includes all necessary quoting and schema-prefixing.
12950 : : */
12951 : : char *
2176 akorotkov@postgresql 12952 : 3 : generate_opclass_name(Oid opclass)
12953 : : {
12954 : : StringInfoData buf;
12955 : :
12956 : 3 : initStringInfo(&buf);
12957 : 3 : get_opclass_name(opclass, InvalidOid, &buf);
12958 : :
2131 tgl@sss.pgh.pa.us 12959 : 3 : return &buf.data[1]; /* get_opclass_name() prepends space */
12960 : : }
12961 : :
12962 : : /*
12963 : : * processIndirection - take care of array and subfield assignment
12964 : : *
12965 : : * We strip any top-level FieldStore or assignment SubscriptingRef nodes that
12966 : : * appear in the input, printing them as decoration for the base column
12967 : : * name (which we assume the caller just printed). We might also need to
12968 : : * strip CoerceToDomain nodes, but only ones that appear above assignment
12969 : : * nodes.
12970 : : *
12971 : : * Returns the subexpression that's to be assigned.
12972 : : */
12973 : : static Node *
3511 12974 : 642 : processIndirection(Node *node, deparse_context *context)
12975 : : {
7949 12976 : 642 : StringInfo buf = context->buf;
3168 12977 : 642 : CoerceToDomain *cdomain = NULL;
12978 : :
12979 : : for (;;)
12980 : : {
7949 12981 [ - + ]: 795 : if (node == NULL)
7949 tgl@sss.pgh.pa.us 12982 :UBC 0 : break;
7949 tgl@sss.pgh.pa.us 12983 [ + + ]:CBC 795 : if (IsA(node, FieldStore))
12984 : : {
12985 : 54 : FieldStore *fstore = (FieldStore *) node;
12986 : : Oid typrelid;
12987 : : char *fieldname;
12988 : :
12989 : : /* lookup tuple type */
12990 : 54 : typrelid = get_typ_typrelid(fstore->resulttype);
12991 [ - + ]: 54 : if (!OidIsValid(typrelid))
7949 tgl@sss.pgh.pa.us 12992 [ # # ]:UBC 0 : elog(ERROR, "argument type %s of FieldStore is not a tuple type",
12993 : : format_type_be(fstore->resulttype));
12994 : :
12995 : : /*
12996 : : * Print the field name. There should only be one target field in
12997 : : * stored rules. There could be more than that in executable
12998 : : * target lists, but this function cannot be used for that case.
12999 : : */
5869 tgl@sss.pgh.pa.us 13000 [ - + ]:CBC 54 : Assert(list_length(fstore->fieldnums) == 1);
2953 alvherre@alvh.no-ip. 13001 : 54 : fieldname = get_attname(typrelid,
13002 : 54 : linitial_int(fstore->fieldnums), false);
3511 tgl@sss.pgh.pa.us 13003 : 54 : appendStringInfo(buf, ".%s", quote_identifier(fieldname));
13004 : :
13005 : : /*
13006 : : * We ignore arg since it should be an uninteresting reference to
13007 : : * the target column or subcolumn.
13008 : : */
7949 13009 : 54 : node = (Node *) linitial(fstore->newvals);
13010 : : }
2599 alvherre@alvh.no-ip. 13011 [ + + ]: 741 : else if (IsA(node, SubscriptingRef))
13012 : : {
13013 : 69 : SubscriptingRef *sbsref = (SubscriptingRef *) node;
13014 : :
13015 [ - + ]: 69 : if (sbsref->refassgnexpr == NULL)
7949 tgl@sss.pgh.pa.us 13016 :UBC 0 : break;
13017 : :
2599 alvherre@alvh.no-ip. 13018 :CBC 69 : printSubscripts(sbsref, context);
13019 : :
13020 : : /*
13021 : : * We ignore refexpr since it should be an uninteresting reference
13022 : : * to the target column or subcolumn.
13023 : : */
13024 : 69 : node = (Node *) sbsref->refassgnexpr;
13025 : : }
3168 tgl@sss.pgh.pa.us 13026 [ + + ]: 672 : else if (IsA(node, CoerceToDomain))
13027 : : {
13028 : 30 : cdomain = (CoerceToDomain *) node;
13029 : : /* If it's an explicit domain coercion, we're done */
13030 [ - + ]: 30 : if (cdomain->coercionformat != COERCE_IMPLICIT_CAST)
3168 tgl@sss.pgh.pa.us 13031 :UBC 0 : break;
13032 : : /* Tentatively descend past the CoerceToDomain */
3168 tgl@sss.pgh.pa.us 13033 :CBC 30 : node = (Node *) cdomain->arg;
13034 : : }
13035 : : else
7949 13036 : 642 : break;
13037 : : }
13038 : :
13039 : : /*
13040 : : * If we descended past a CoerceToDomain whose argument turned out not to
13041 : : * be a FieldStore or array assignment, back up to the CoerceToDomain.
13042 : : * (This is not enough to be fully correct if there are nested implicit
13043 : : * CoerceToDomains, but such cases shouldn't ever occur.)
13044 : : */
3168 13045 [ + + - + ]: 642 : if (cdomain && node == (Node *) cdomain->arg)
3168 tgl@sss.pgh.pa.us 13046 :UBC 0 : node = (Node *) cdomain;
13047 : :
7949 tgl@sss.pgh.pa.us 13048 :CBC 642 : return node;
13049 : : }
13050 : :
13051 : : static void
2599 alvherre@alvh.no-ip. 13052 : 257 : printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
13053 : : {
7949 tgl@sss.pgh.pa.us 13054 : 257 : StringInfo buf = context->buf;
13055 : : ListCell *lowlist_item;
13056 : : ListCell *uplist_item;
13057 : :
2599 alvherre@alvh.no-ip. 13058 : 257 : lowlist_item = list_head(sbsref->reflowerindexpr); /* could be NULL */
13059 [ + - + + : 514 : foreach(uplist_item, sbsref->refupperindexpr)
+ + ]
13060 : : {
7949 tgl@sss.pgh.pa.us 13061 : 257 : appendStringInfoChar(buf, '[');
13062 [ - + ]: 257 : if (lowlist_item)
13063 : : {
13064 : : /* If subexpression is NULL, get_rule_expr prints nothing */
7949 tgl@sss.pgh.pa.us 13065 :UBC 0 : get_rule_expr((Node *) lfirst(lowlist_item), context, false);
13066 : 0 : appendStringInfoChar(buf, ':');
2435 13067 : 0 : lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
13068 : : }
13069 : : /* If subexpression is NULL, get_rule_expr prints nothing */
7949 tgl@sss.pgh.pa.us 13070 :CBC 257 : get_rule_expr((Node *) lfirst(uplist_item), context, false);
13071 : 257 : appendStringInfoChar(buf, ']');
13072 : : }
9346 13073 : 257 : }
13074 : :
13075 : : /*
13076 : : * quote_identifier - Quote an identifier only if needed
13077 : : *
13078 : : * When quotes are needed, we palloc the required space; slightly
13079 : : * space-wasteful but well worth it for notational simplicity.
13080 : : */
13081 : : const char *
8725 13082 : 1307504 : quote_identifier(const char *ident)
13083 : : {
13084 : : /*
13085 : : * Can avoid quoting if ident starts with a lowercase letter or underscore
13086 : : * and contains only lowercase letters, digits, and underscores, *and* is
13087 : : * not any SQL keyword. Otherwise, supply quotes.
13088 : : */
8703 13089 : 1307504 : int nquotes = 0;
13090 : : bool safe;
13091 : : const char *ptr;
13092 : : char *result;
13093 : : char *optr;
13094 : :
13095 : : /*
13096 : : * would like to use <ctype.h> macros here, but they might yield unwanted
13097 : : * locale-specific results...
13098 : : */
8722 13099 [ + + - + : 1307504 : safe = ((ident[0] >= 'a' && ident[0] <= 'z') || ident[0] == '_');
+ + ]
13100 : :
8703 13101 [ + + ]: 11331933 : for (ptr = ident; *ptr; ptr++)
13102 : : {
13103 : 10024429 : char ch = *ptr;
13104 : :
13105 [ + + + - : 10024429 : if ((ch >= 'a' && ch <= 'z') ||
+ + ]
13106 [ + + + + ]: 1226628 : (ch >= '0' && ch <= '9') ||
13107 : : (ch == '_'))
13108 : : {
13109 : : /* okay */
13110 : : }
13111 : : else
13112 : : {
13113 : 34245 : safe = false;
13114 [ + + ]: 34245 : if (ch == '"')
13115 : 82 : nquotes++;
13116 : : }
13117 : : }
13118 : :
5715 rhaas@postgresql.org 13119 [ + + ]: 1307504 : if (quote_all_identifiers)
13120 : 6634 : safe = false;
13121 : :
9596 tgl@sss.pgh.pa.us 13122 [ + + ]: 1307504 : if (safe)
13123 : : {
13124 : : /*
13125 : : * Check for keyword. We quote keywords except for unreserved ones.
13126 : : * (In some cases we could avoid quoting a col_name or type_func_name
13127 : : * keyword, but it seems much harder than it's worth to tell that.)
13128 : : *
13129 : : * Note: ScanKeywordLookup() does case-insensitive comparison, but
13130 : : * that's fine, since we already know we have all-lower-case.
13131 : : */
2625 13132 : 1287369 : int kwnum = ScanKeywordLookup(ident, &ScanKeywords);
13133 : :
13134 [ + + + + ]: 1287369 : if (kwnum >= 0 && ScanKeywordCategories[kwnum] != UNRESERVED_KEYWORD)
9596 13135 : 1737 : safe = false;
13136 : : }
13137 : :
9659 13138 [ + + ]: 1307504 : if (safe)
13139 : 1285632 : return ident; /* no change needed */
13140 : :
8703 13141 : 21872 : result = (char *) palloc(strlen(ident) + nquotes + 2 + 1);
13142 : :
13143 : 21872 : optr = result;
13144 : 21872 : *optr++ = '"';
13145 [ + + ]: 132136 : for (ptr = ident; *ptr; ptr++)
13146 : : {
13147 : 110264 : char ch = *ptr;
13148 : :
13149 [ + + ]: 110264 : if (ch == '"')
13150 : 82 : *optr++ = '"';
13151 : 110264 : *optr++ = ch;
13152 : : }
13153 : 21872 : *optr++ = '"';
13154 : 21872 : *optr = '\0';
13155 : :
9659 13156 : 21872 : return result;
13157 : : }
13158 : :
13159 : : /*
13160 : : * quote_qualified_identifier - Quote a possibly-qualified identifier
13161 : : *
13162 : : * Return a name of the form qualifier.ident, or just ident if qualifier
13163 : : * is NULL, quoting each component if necessary. The result is palloc'd.
13164 : : */
13165 : : char *
6086 peter_e@gmx.net 13166 : 647244 : quote_qualified_identifier(const char *qualifier,
13167 : : const char *ident)
13168 : : {
13169 : : StringInfoData buf;
13170 : :
8725 tgl@sss.pgh.pa.us 13171 : 647244 : initStringInfo(&buf);
6086 peter_e@gmx.net 13172 [ + + ]: 647244 : if (qualifier)
13173 : 229686 : appendStringInfo(&buf, "%s.", quote_identifier(qualifier));
8079 neilc@samurai.com 13174 : 647244 : appendStringInfoString(&buf, quote_identifier(ident));
8725 tgl@sss.pgh.pa.us 13175 : 647244 : return buf.data;
13176 : : }
13177 : :
13178 : : /*
13179 : : * get_relation_name
13180 : : * Get the unqualified name of a relation specified by OID
13181 : : *
13182 : : * This differs from the underlying get_rel_name() function in that it will
13183 : : * throw error instead of silently returning NULL if the OID is bad.
13184 : : */
13185 : : static char *
5612 13186 : 8505 : get_relation_name(Oid relid)
13187 : : {
13188 : 8505 : char *relname = get_rel_name(relid);
13189 : :
13190 [ - + ]: 8505 : if (!relname)
5612 tgl@sss.pgh.pa.us 13191 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
5612 tgl@sss.pgh.pa.us 13192 :CBC 8505 : return relname;
13193 : : }
13194 : :
13195 : : /*
13196 : : * generate_relation_name
13197 : : * Compute the name to display for a relation specified by OID
13198 : : *
13199 : : * The result includes all necessary quoting and schema-prefixing.
13200 : : *
13201 : : * If namespaces isn't NIL, it must be a list of deparse_namespace nodes.
13202 : : * We will forcibly qualify the relation name if it equals any CTE name
13203 : : * visible in the namespace list.
13204 : : */
13205 : : static char *
6369 13206 : 4080 : generate_relation_name(Oid relid, List *namespaces)
13207 : : {
13208 : : HeapTuple tp;
13209 : : Form_pg_class reltup;
13210 : : bool need_qual;
13211 : : ListCell *nslist;
13212 : : char *relname;
13213 : : char *nspname;
13214 : : char *result;
13215 : :
5873 rhaas@postgresql.org 13216 : 4080 : tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
8717 tgl@sss.pgh.pa.us 13217 [ - + ]: 4080 : if (!HeapTupleIsValid(tp))
8267 tgl@sss.pgh.pa.us 13218 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
8717 tgl@sss.pgh.pa.us 13219 :CBC 4080 : reltup = (Form_pg_class) GETSTRUCT(tp);
6369 13220 : 4080 : relname = NameStr(reltup->relname);
13221 : :
13222 : : /* Check for conflicting CTE name */
13223 : 4080 : need_qual = false;
13224 [ + + + + : 6975 : foreach(nslist, namespaces)
+ + ]
13225 : : {
13226 : 2895 : deparse_namespace *dpns = (deparse_namespace *) lfirst(nslist);
13227 : : ListCell *ctlist;
13228 : :
13229 [ + + + + : 2961 : foreach(ctlist, dpns->ctes)
+ + ]
13230 : : {
13231 : 66 : CommonTableExpr *cte = (CommonTableExpr *) lfirst(ctlist);
13232 : :
13233 [ - + ]: 66 : if (strcmp(cte->ctename, relname) == 0)
13234 : : {
6369 tgl@sss.pgh.pa.us 13235 :UBC 0 : need_qual = true;
13236 : 0 : break;
13237 : : }
13238 : : }
6369 tgl@sss.pgh.pa.us 13239 [ - + ]:CBC 2895 : if (need_qual)
6369 tgl@sss.pgh.pa.us 13240 :UBC 0 : break;
13241 : : }
13242 : :
13243 : : /* Otherwise, qualify the name if not visible in search path */
6369 tgl@sss.pgh.pa.us 13244 [ + - ]:CBC 4080 : if (!need_qual)
13245 : 4080 : need_qual = !RelationIsVisible(relid);
13246 : :
13247 [ + + ]: 4080 : if (need_qual)
1692 13248 : 1181 : nspname = get_namespace_name_or_temp(reltup->relnamespace);
13249 : : else
6369 13250 : 2899 : nspname = NULL;
13251 : :
13252 : 4080 : result = quote_qualified_identifier(nspname, relname);
13253 : :
8717 13254 : 4080 : ReleaseSysCache(tp);
13255 : :
13256 : 4080 : return result;
13257 : : }
13258 : :
13259 : : /*
13260 : : * generate_qualified_relation_name
13261 : : * Compute the name to display for a relation specified by OID
13262 : : *
13263 : : * As above, but unconditionally schema-qualify the name.
13264 : : */
13265 : : static char *
3768 13266 : 4126 : generate_qualified_relation_name(Oid relid)
13267 : : {
13268 : : HeapTuple tp;
13269 : : Form_pg_class reltup;
13270 : : char *relname;
13271 : : char *nspname;
13272 : : char *result;
13273 : :
13274 : 4126 : tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
13275 [ - + ]: 4126 : if (!HeapTupleIsValid(tp))
3768 tgl@sss.pgh.pa.us 13276 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
3768 tgl@sss.pgh.pa.us 13277 :CBC 4126 : reltup = (Form_pg_class) GETSTRUCT(tp);
13278 : 4126 : relname = NameStr(reltup->relname);
13279 : :
1692 13280 : 4126 : nspname = get_namespace_name_or_temp(reltup->relnamespace);
3768 13281 [ - + ]: 4126 : if (!nspname)
3768 tgl@sss.pgh.pa.us 13282 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for namespace %u",
13283 : : reltup->relnamespace);
13284 : :
3768 tgl@sss.pgh.pa.us 13285 :CBC 4126 : result = quote_qualified_identifier(nspname, relname);
13286 : :
13287 : 4126 : ReleaseSysCache(tp);
13288 : :
13289 : 4126 : return result;
13290 : : }
13291 : :
13292 : : /*
13293 : : * generate_function_name
13294 : : * Compute the name to display for a function specified by OID,
13295 : : * given that it is being called with the specified actual arg names and
13296 : : * types. (Those matter because of ambiguous-function resolution rules.)
13297 : : *
13298 : : * If we're dealing with a potentially variadic function (in practice, this
13299 : : * means a FuncExpr or Aggref, not some other way of calling a function), then
13300 : : * has_variadic must specify whether variadic arguments have been merged,
13301 : : * and *use_variadic_p will be set to indicate whether to print VARIADIC in
13302 : : * the output. For non-FuncExpr cases, has_variadic should be false and
13303 : : * use_variadic_p can be NULL.
13304 : : *
13305 : : * inGroupBy must be true if we're deparsing a GROUP BY clause.
13306 : : *
13307 : : * The result includes all necessary quoting and schema-prefixing.
13308 : : */
13309 : : static char *
4801 13310 : 7722 : generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
13311 : : bool has_variadic, bool *use_variadic_p,
13312 : : bool inGroupBy)
13313 : : {
13314 : : char *result;
13315 : : HeapTuple proctup;
13316 : : Form_pg_proc procform;
13317 : : char *proname;
13318 : : bool use_variadic;
13319 : : char *nspname;
13320 : : FuncDetailCode p_result;
13321 : : int fgc_flags;
13322 : : Oid p_funcid;
13323 : : Oid p_rettype;
13324 : : bool p_retset;
13325 : : int p_nvargs;
13326 : : Oid p_vatype;
13327 : : Oid *p_true_typeids;
3956 andres@anarazel.de 13328 : 7722 : bool force_qualify = false;
13329 : :
5873 rhaas@postgresql.org 13330 : 7722 : proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
8717 tgl@sss.pgh.pa.us 13331 [ - + ]: 7722 : if (!HeapTupleIsValid(proctup))
8267 tgl@sss.pgh.pa.us 13332 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for function %u", funcid);
8717 tgl@sss.pgh.pa.us 13333 :CBC 7722 : procform = (Form_pg_proc) GETSTRUCT(proctup);
13334 : 7722 : proname = NameStr(procform->proname);
13335 : :
13336 : : /*
13337 : : * Due to parser hacks to avoid needing to reserve CUBE, we need to force
13338 : : * qualification of some function names within GROUP BY.
13339 : : */
563 13340 [ - + ]: 7722 : if (inGroupBy)
13341 : : {
3956 andres@anarazel.de 13342 [ # # # # ]:UBC 0 : if (strcmp(proname, "cube") == 0 || strcmp(proname, "rollup") == 0)
13343 : 0 : force_qualify = true;
13344 : : }
13345 : :
13346 : : /*
13347 : : * Determine whether VARIADIC should be printed. We must do this first
13348 : : * since it affects the lookup rules in func_get_detail().
13349 : : *
13350 : : * We always print VARIADIC if the function has a merged variadic-array
13351 : : * argument. Note that this is always the case for functions taking a
13352 : : * VARIADIC argument type other than VARIADIC ANY. If we omitted VARIADIC
13353 : : * and printed the array elements as separate arguments, the call could
13354 : : * match a newer non-VARIADIC function.
13355 : : */
4801 tgl@sss.pgh.pa.us 13356 [ + + ]:CBC 7722 : if (use_variadic_p)
13357 : : {
13358 : : /* Parser should not have set funcvariadic unless fn is variadic */
4364 13359 [ + + - + ]: 6852 : Assert(!has_variadic || OidIsValid(procform->provariadic));
13360 : 6852 : use_variadic = has_variadic;
4801 13361 : 6852 : *use_variadic_p = use_variadic;
13362 : : }
13363 : : else
13364 : : {
4364 13365 [ - + ]: 870 : Assert(!has_variadic);
4801 13366 : 870 : use_variadic = false;
13367 : : }
13368 : :
13369 : : /*
13370 : : * The idea here is to schema-qualify only if the parser would fail to
13371 : : * resolve the correct function given the unqualified func name with the
13372 : : * specified argtypes and VARIADIC flag. But if we already decided to
13373 : : * force qualification, then we can skip the lookup and pretend we didn't
13374 : : * find it.
13375 : : */
3956 andres@anarazel.de 13376 [ + - ]: 7722 : if (!force_qualify)
13377 : 7722 : p_result = func_get_detail(list_make1(makeString(proname)),
13378 : : NIL, argnames, nargs, argtypes,
1739 tgl@sss.pgh.pa.us 13379 : 7722 : !use_variadic, true, false,
13380 : : &fgc_flags,
13381 : : &p_funcid, &p_rettype,
13382 : : &p_retset, &p_nvargs, &p_vatype,
3956 andres@anarazel.de 13383 : 7722 : &p_true_typeids, NULL);
13384 : : else
13385 : : {
3956 andres@anarazel.de 13386 :UBC 0 : p_result = FUNCDETAIL_NOTFOUND;
13387 : 0 : p_funcid = InvalidOid;
13388 : : }
13389 : :
6286 tgl@sss.pgh.pa.us 13390 [ + + + + ]:CBC 7722 : if ((p_result == FUNCDETAIL_NORMAL ||
13391 [ + + ]: 634 : p_result == FUNCDETAIL_AGGREGATE ||
13392 : 7151 : p_result == FUNCDETAIL_WINDOWFUNC) &&
8290 13393 [ + - ]: 7151 : p_funcid == funcid)
8717 13394 : 7151 : nspname = NULL;
13395 : : else
1692 13396 : 571 : nspname = get_namespace_name_or_temp(procform->pronamespace);
13397 : :
8717 13398 : 7722 : result = quote_qualified_identifier(nspname, proname);
13399 : :
13400 : 7722 : ReleaseSysCache(proctup);
13401 : :
13402 : 7722 : return result;
13403 : : }
13404 : :
13405 : : /*
13406 : : * generate_operator_name
13407 : : * Compute the name to display for an operator specified by OID,
13408 : : * given that it is being called with the specified actual arg types.
13409 : : * (Arg types matter because of ambiguous-operator resolution rules.
13410 : : * Pass InvalidOid for unused arg of a unary operator.)
13411 : : *
13412 : : * The result includes all necessary quoting and schema-prefixing,
13413 : : * plus the OPERATOR() decoration needed to use a qualified operator name
13414 : : * in an expression.
13415 : : */
13416 : : static char *
13417 : 33379 : generate_operator_name(Oid operid, Oid arg1, Oid arg2)
13418 : : {
13419 : : StringInfoData buf;
13420 : : HeapTuple opertup;
13421 : : Form_pg_operator operform;
13422 : : char *oprname;
13423 : : char *nspname;
13424 : : Operator p_result;
13425 : :
13426 : 33379 : initStringInfo(&buf);
13427 : :
5873 rhaas@postgresql.org 13428 : 33379 : opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operid));
8717 tgl@sss.pgh.pa.us 13429 [ - + ]: 33379 : if (!HeapTupleIsValid(opertup))
8267 tgl@sss.pgh.pa.us 13430 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for operator %u", operid);
8717 tgl@sss.pgh.pa.us 13431 :CBC 33379 : operform = (Form_pg_operator) GETSTRUCT(opertup);
13432 : 33379 : oprname = NameStr(operform->oprname);
13433 : :
13434 : : /*
13435 : : * The idea here is to schema-qualify only if the parser would fail to
13436 : : * resolve the correct operator given the unqualified op name with the
13437 : : * specified argtypes.
13438 : : */
13439 [ + + - ]: 33379 : switch (operform->oprkind)
13440 : : {
13441 : 33364 : case 'b':
7306 13442 : 33364 : p_result = oper(NULL, list_make1(makeString(oprname)), arg1, arg2,
13443 : : true, -1);
8717 13444 : 33364 : break;
13445 : 15 : case 'l':
7306 13446 : 15 : p_result = left_oper(NULL, list_make1(makeString(oprname)), arg2,
13447 : : true, -1);
8717 13448 : 15 : break;
8717 tgl@sss.pgh.pa.us 13449 :UBC 0 : default:
8267 13450 [ # # ]: 0 : elog(ERROR, "unrecognized oprkind: %d", operform->oprkind);
13451 : : p_result = NULL; /* keep compiler quiet */
13452 : : break;
13453 : : }
13454 : :
8717 tgl@sss.pgh.pa.us 13455 [ + + + - ]:CBC 33379 : if (p_result != NULL && oprid(p_result) == operid)
13456 : 33374 : nspname = NULL;
13457 : : else
13458 : : {
1692 13459 : 5 : nspname = get_namespace_name_or_temp(operform->oprnamespace);
8717 13460 : 5 : appendStringInfo(&buf, "OPERATOR(%s.", quote_identifier(nspname));
13461 : : }
13462 : :
8079 neilc@samurai.com 13463 : 33379 : appendStringInfoString(&buf, oprname);
13464 : :
8717 tgl@sss.pgh.pa.us 13465 [ + + ]: 33379 : if (nspname)
13466 : 5 : appendStringInfoChar(&buf, ')');
13467 : :
13468 [ + + ]: 33379 : if (p_result != NULL)
13469 : 33374 : ReleaseSysCache(p_result);
13470 : :
13471 : 33379 : ReleaseSysCache(opertup);
13472 : :
13473 : 33379 : return buf.data;
13474 : : }
13475 : :
13476 : : /*
13477 : : * generate_operator_clause --- generate a binary-operator WHERE clause
13478 : : *
13479 : : * This is used for internally-generated-and-executed SQL queries, where
13480 : : * precision is essential and readability is secondary. The basic
13481 : : * requirement is to append "leftop op rightop" to buf, where leftop and
13482 : : * rightop are given as strings and are assumed to yield types leftoptype
13483 : : * and rightoptype; the operator is identified by OID. The complexity
13484 : : * comes from needing to be sure that the parser will select the desired
13485 : : * operator when the query is parsed. We always name the operator using
13486 : : * OPERATOR(schema.op) syntax, so as to avoid search-path uncertainties.
13487 : : * We have to emit casts too, if either input isn't already the input type
13488 : : * of the operator; else we are at the mercy of the parser's heuristics for
13489 : : * ambiguous-operator resolution. The caller must ensure that leftop and
13490 : : * rightop are suitable arguments for a cast operation; it's best to insert
13491 : : * parentheses if they aren't just variables or parameters.
13492 : : */
13493 : : void
2918 13494 : 3346 : generate_operator_clause(StringInfo buf,
13495 : : const char *leftop, Oid leftoptype,
13496 : : Oid opoid,
13497 : : const char *rightop, Oid rightoptype)
13498 : : {
13499 : : HeapTuple opertup;
13500 : : Form_pg_operator operform;
13501 : : char *oprname;
13502 : : char *nspname;
13503 : :
13504 : 3346 : opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(opoid));
13505 [ - + ]: 3346 : if (!HeapTupleIsValid(opertup))
2918 tgl@sss.pgh.pa.us 13506 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for operator %u", opoid);
2918 tgl@sss.pgh.pa.us 13507 :CBC 3346 : operform = (Form_pg_operator) GETSTRUCT(opertup);
13508 [ - + ]: 3346 : Assert(operform->oprkind == 'b');
13509 : 3346 : oprname = NameStr(operform->oprname);
13510 : :
13511 : 3346 : nspname = get_namespace_name(operform->oprnamespace);
13512 : :
13513 : 3346 : appendStringInfoString(buf, leftop);
13514 [ + + ]: 3346 : if (leftoptype != operform->oprleft)
13515 : 599 : add_cast_to(buf, operform->oprleft);
13516 : 3346 : appendStringInfo(buf, " OPERATOR(%s.", quote_identifier(nspname));
13517 : 3346 : appendStringInfoString(buf, oprname);
13518 : 3346 : appendStringInfo(buf, ") %s", rightop);
13519 [ + + ]: 3346 : if (rightoptype != operform->oprright)
13520 : 484 : add_cast_to(buf, operform->oprright);
13521 : :
13522 : 3346 : ReleaseSysCache(opertup);
13523 : 3346 : }
13524 : :
13525 : : /*
13526 : : * Add a cast specification to buf. We spell out the type name the hard way,
13527 : : * intentionally not using format_type_be(). This is to avoid corner cases
13528 : : * for CHARACTER, BIT, and perhaps other types, where specifying the type
13529 : : * using SQL-standard syntax results in undesirable data truncation. By
13530 : : * doing it this way we can be certain that the cast will have default (-1)
13531 : : * target typmod.
13532 : : */
13533 : : static void
13534 : 1083 : add_cast_to(StringInfo buf, Oid typid)
13535 : : {
13536 : : HeapTuple typetup;
13537 : : Form_pg_type typform;
13538 : : char *typname;
13539 : : char *nspname;
13540 : :
13541 : 1083 : typetup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
13542 [ - + ]: 1083 : if (!HeapTupleIsValid(typetup))
2918 tgl@sss.pgh.pa.us 13543 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for type %u", typid);
2918 tgl@sss.pgh.pa.us 13544 :CBC 1083 : typform = (Form_pg_type) GETSTRUCT(typetup);
13545 : :
13546 : 1083 : typname = NameStr(typform->typname);
1692 13547 : 1083 : nspname = get_namespace_name_or_temp(typform->typnamespace);
13548 : :
2918 13549 : 1083 : appendStringInfo(buf, "::%s.%s",
13550 : : quote_identifier(nspname), quote_identifier(typname));
13551 : :
13552 : 1083 : ReleaseSysCache(typetup);
13553 : 1083 : }
13554 : :
13555 : : /*
13556 : : * generate_qualified_type_name
13557 : : * Compute the name to display for a type specified by OID
13558 : : *
13559 : : * This is different from format_type_be() in that we unconditionally
13560 : : * schema-qualify the name. That also means no special syntax for
13561 : : * SQL-standard type names ... although in current usage, this should
13562 : : * only get used for domains, so such cases wouldn't occur anyway.
13563 : : */
13564 : : static char *
3056 13565 : 7 : generate_qualified_type_name(Oid typid)
13566 : : {
13567 : : HeapTuple tp;
13568 : : Form_pg_type typtup;
13569 : : char *typname;
13570 : : char *nspname;
13571 : : char *result;
13572 : :
13573 : 7 : tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
13574 [ - + ]: 7 : if (!HeapTupleIsValid(tp))
3056 tgl@sss.pgh.pa.us 13575 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for type %u", typid);
3056 tgl@sss.pgh.pa.us 13576 :CBC 7 : typtup = (Form_pg_type) GETSTRUCT(tp);
13577 : 7 : typname = NameStr(typtup->typname);
13578 : :
1692 13579 : 7 : nspname = get_namespace_name_or_temp(typtup->typnamespace);
3056 13580 [ - + ]: 7 : if (!nspname)
3056 tgl@sss.pgh.pa.us 13581 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for namespace %u",
13582 : : typtup->typnamespace);
13583 : :
3056 tgl@sss.pgh.pa.us 13584 :CBC 7 : result = quote_qualified_identifier(nspname, typname);
13585 : :
13586 : 7 : ReleaseSysCache(tp);
13587 : :
13588 : 7 : return result;
13589 : : }
13590 : :
13591 : : /*
13592 : : * generate_collation_name
13593 : : * Compute the name to display for a collation specified by OID
13594 : : *
13595 : : * The result includes all necessary quoting and schema-prefixing.
13596 : : */
13597 : : char *
5514 peter_e@gmx.net 13598 : 147 : generate_collation_name(Oid collid)
13599 : : {
13600 : : HeapTuple tp;
13601 : : Form_pg_collation colltup;
13602 : : char *collname;
13603 : : char *nspname;
13604 : : char *result;
13605 : :
13606 : 147 : tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid));
13607 [ - + ]: 147 : if (!HeapTupleIsValid(tp))
5514 peter_e@gmx.net 13608 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for collation %u", collid);
5514 peter_e@gmx.net 13609 :CBC 147 : colltup = (Form_pg_collation) GETSTRUCT(tp);
13610 : 147 : collname = NameStr(colltup->collname);
13611 : :
13612 [ - + ]: 147 : if (!CollationIsVisible(collid))
1692 tgl@sss.pgh.pa.us 13613 :UBC 0 : nspname = get_namespace_name_or_temp(colltup->collnamespace);
13614 : : else
5514 peter_e@gmx.net 13615 :CBC 147 : nspname = NULL;
13616 : :
13617 : 147 : result = quote_qualified_identifier(nspname, collname);
13618 : :
13619 : 147 : ReleaseSysCache(tp);
13620 : :
13621 : 147 : return result;
13622 : : }
13623 : :
13624 : : /*
13625 : : * Given a C string, produce a TEXT datum.
13626 : : *
13627 : : * We assume that the input was palloc'd and may be freed.
13628 : : */
13629 : : static text *
7984 tgl@sss.pgh.pa.us 13630 : 22834 : string_to_text(char *str)
13631 : : {
13632 : : text *result;
13633 : :
6564 13634 : 22834 : result = cstring_to_text(str);
7984 13635 : 22834 : pfree(str);
13636 : 22834 : return result;
13637 : : }
13638 : :
13639 : : /*
13640 : : * Generate a C string representing a relation options from text[] datum.
13641 : : */
13642 : : static void
2176 akorotkov@postgresql 13643 : 122 : get_reloptions(StringInfo buf, Datum reloptions)
13644 : : {
13645 : : Datum *options;
13646 : : int noptions;
13647 : : int i;
13648 : :
1353 peter@eisentraut.org 13649 : 122 : deconstruct_array_builtin(DatumGetArrayTypeP(reloptions), TEXTOID,
13650 : : &options, NULL, &noptions);
13651 : :
2176 akorotkov@postgresql 13652 [ + + ]: 254 : for (i = 0; i < noptions; i++)
13653 : : {
13654 : 132 : char *option = TextDatumGetCString(options[i]);
13655 : : char *name;
13656 : : char *separator;
13657 : : char *value;
13658 : :
13659 : : /*
13660 : : * Each array element should have the form name=value. If the "=" is
13661 : : * missing for some reason, treat it like an empty value.
13662 : : */
13663 : 132 : name = option;
13664 : 132 : separator = strchr(option, '=');
13665 [ + - ]: 132 : if (separator)
13666 : : {
13667 : 132 : *separator = '\0';
13668 : 132 : value = separator + 1;
13669 : : }
13670 : : else
2176 akorotkov@postgresql 13671 :UBC 0 : value = "";
13672 : :
2176 akorotkov@postgresql 13673 [ + + ]:CBC 132 : if (i > 0)
13674 : 10 : appendStringInfoString(buf, ", ");
13675 : 132 : appendStringInfo(buf, "%s=", quote_identifier(name));
13676 : :
13677 : : /*
13678 : : * In general we need to quote the value; but to avoid unnecessary
13679 : : * clutter, do not quote if it is an identifier that would not need
13680 : : * quoting. (We could also allow numbers, but that is a bit trickier
13681 : : * than it looks --- for example, are leading zeroes significant? We
13682 : : * don't want to assume very much here about what custom reloptions
13683 : : * might mean.)
13684 : : */
13685 [ + + ]: 132 : if (quote_identifier(value) == value)
13686 : 4 : appendStringInfoString(buf, value);
13687 : : else
13688 : 128 : simple_quote_literal(buf, value);
13689 : :
13690 : 132 : pfree(option);
13691 : : }
13692 : 122 : }
13693 : :
13694 : : /*
13695 : : * Generate a C string representing a relation's reloptions, or NULL if none.
13696 : : */
13697 : : static char *
7196 bruce@momjian.us 13698 : 3907 : flatten_reloptions(Oid relid)
13699 : : {
13700 : 3907 : char *result = NULL;
13701 : : HeapTuple tuple;
13702 : : Datum reloptions;
13703 : : bool isnull;
13704 : :
5873 rhaas@postgresql.org 13705 : 3907 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
7195 tgl@sss.pgh.pa.us 13706 [ - + ]: 3907 : if (!HeapTupleIsValid(tuple))
7195 tgl@sss.pgh.pa.us 13707 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
13708 : :
7195 tgl@sss.pgh.pa.us 13709 :CBC 3907 : reloptions = SysCacheGetAttr(RELOID, tuple,
13710 : : Anum_pg_class_reloptions, &isnull);
13711 [ + + ]: 3907 : if (!isnull)
13712 : : {
13713 : : StringInfoData buf;
13714 : :
3726 13715 : 105 : initStringInfo(&buf);
2176 akorotkov@postgresql 13716 : 105 : get_reloptions(&buf, reloptions);
13717 : :
3726 tgl@sss.pgh.pa.us 13718 : 105 : result = buf.data;
13719 : : }
13720 : :
7195 13721 : 3907 : ReleaseSysCache(tuple);
13722 : :
7196 bruce@momjian.us 13723 : 3907 : return result;
13724 : : }
13725 : :
13726 : : /*
13727 : : * get_range_partbound_string
13728 : : * A C string representation of one range partition bound
13729 : : */
13730 : : char *
3139 rhaas@postgresql.org 13731 : 2726 : get_range_partbound_string(List *bound_datums)
13732 : : {
13733 : : deparse_context context;
13734 : : StringInfoData buf;
13735 : : ListCell *cell;
13736 : : char *sep;
13737 : :
129 drowley@postgresql.o 13738 :GNC 2726 : initStringInfo(&buf);
3139 rhaas@postgresql.org 13739 :CBC 2726 : memset(&context, 0, sizeof(deparse_context));
129 drowley@postgresql.o 13740 :GNC 2726 : context.buf = &buf;
13741 : :
13742 : 2726 : appendStringInfoChar(&buf, '(');
3139 rhaas@postgresql.org 13743 :CBC 2726 : sep = "";
13744 [ + - + + : 5848 : foreach(cell, bound_datums)
+ + ]
13745 : : {
13746 : : PartitionRangeDatum *datum =
1031 tgl@sss.pgh.pa.us 13747 : 3122 : lfirst_node(PartitionRangeDatum, cell);
13748 : :
129 drowley@postgresql.o 13749 :GNC 3122 : appendStringInfoString(&buf, sep);
3139 rhaas@postgresql.org 13750 [ + + ]:CBC 3122 : if (datum->kind == PARTITION_RANGE_DATUM_MINVALUE)
129 drowley@postgresql.o 13751 :GNC 111 : appendStringInfoString(&buf, "MINVALUE");
3139 rhaas@postgresql.org 13752 [ + + ]:CBC 3011 : else if (datum->kind == PARTITION_RANGE_DATUM_MAXVALUE)
129 drowley@postgresql.o 13753 :GNC 60 : appendStringInfoString(&buf, "MAXVALUE");
13754 : : else
13755 : : {
3139 rhaas@postgresql.org 13756 :CBC 2951 : Const *val = castNode(Const, datum->value);
13757 : :
13758 : 2951 : get_const_expr(val, &context, -1);
13759 : : }
13760 : 3122 : sep = ", ";
13761 : : }
129 drowley@postgresql.o 13762 :GNC 2726 : appendStringInfoChar(&buf, ')');
13763 : :
13764 : 2726 : return buf.data;
13765 : : }
|