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-2024, 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_node.h"
54 : : #include "parser/parse_oper.h"
55 : : #include "parser/parse_relation.h"
56 : : #include "parser/parser.h"
57 : : #include "parser/parsetree.h"
58 : : #include "rewrite/rewriteHandler.h"
59 : : #include "rewrite/rewriteManip.h"
60 : : #include "rewrite/rewriteSupport.h"
61 : : #include "utils/array.h"
62 : : #include "utils/builtins.h"
63 : : #include "utils/fmgroids.h"
64 : : #include "utils/guc.h"
65 : : #include "utils/hsearch.h"
66 : : #include "utils/lsyscache.h"
67 : : #include "utils/partcache.h"
68 : : #include "utils/rel.h"
69 : : #include "utils/ruleutils.h"
70 : : #include "utils/snapmgr.h"
71 : : #include "utils/syscache.h"
72 : : #include "utils/typcache.h"
73 : : #include "utils/varlena.h"
74 : : #include "utils/xml.h"
75 : :
76 : : /* ----------
77 : : * Pretty formatting constants
78 : : * ----------
79 : : */
80 : :
81 : : /* Indent counts */
82 : : #define PRETTYINDENT_STD 8
83 : : #define PRETTYINDENT_JOIN 4
84 : : #define PRETTYINDENT_VAR 4
85 : :
86 : : #define PRETTYINDENT_LIMIT 40 /* wrap limit */
87 : :
88 : : /* Pretty flags */
89 : : #define PRETTYFLAG_PAREN 0x0001
90 : : #define PRETTYFLAG_INDENT 0x0002
91 : : #define PRETTYFLAG_SCHEMA 0x0004
92 : :
93 : : /* Standard conversion of a "bool pretty" option to detailed flags */
94 : : #define GET_PRETTY_FLAGS(pretty) \
95 : : ((pretty) ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT | PRETTYFLAG_SCHEMA) \
96 : : : PRETTYFLAG_INDENT)
97 : :
98 : : /* Default line length for pretty-print wrapping: 0 means wrap always */
99 : : #define WRAP_COLUMN_DEFAULT 0
100 : :
101 : : /* macros to test if pretty action needed */
102 : : #define PRETTY_PAREN(context) ((context)->prettyFlags & PRETTYFLAG_PAREN)
103 : : #define PRETTY_INDENT(context) ((context)->prettyFlags & PRETTYFLAG_INDENT)
104 : : #define PRETTY_SCHEMA(context) ((context)->prettyFlags & PRETTYFLAG_SCHEMA)
105 : :
106 : :
107 : : /* ----------
108 : : * Local data types
109 : : * ----------
110 : : */
111 : :
112 : : /* Context info needed for invoking a recursive querytree display routine */
113 : : typedef struct
114 : : {
115 : : StringInfo buf; /* output buffer to append to */
116 : : List *namespaces; /* List of deparse_namespace nodes */
117 : : List *windowClause; /* Current query level's WINDOW clause */
118 : : List *windowTList; /* targetlist for resolving 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 : : ParseExprKind special_exprkind; /* set only for exprkinds needing special
124 : : * handling */
125 : : Bitmapset *appendparents; /* if not null, map child Vars of these relids
126 : : * back to the parent rel */
127 : : } deparse_context;
128 : :
129 : : /*
130 : : * Each level of query context around a subtree needs a level of Var namespace.
131 : : * A Var having varlevelsup=N refers to the N'th item (counting from 0) in
132 : : * the current context's namespaces list.
133 : : *
134 : : * rtable is the list of actual RTEs from the Query or PlannedStmt.
135 : : * rtable_names holds the alias name to be used for each RTE (either a C
136 : : * string, or NULL for nameless RTEs such as unnamed joins).
137 : : * rtable_columns holds the column alias names to be used for each RTE.
138 : : *
139 : : * subplans is a list of Plan trees for SubPlans and CTEs (it's only used
140 : : * in the PlannedStmt case).
141 : : * ctes is a list of CommonTableExpr nodes (only used in the Query case).
142 : : * appendrels, if not null (it's only used in the PlannedStmt case), is an
143 : : * array of AppendRelInfo nodes, indexed by child relid. We use that to map
144 : : * child-table Vars to their inheritance parents.
145 : : *
146 : : * In some cases we need to make names of merged JOIN USING columns unique
147 : : * across the whole query, not only per-RTE. If so, unique_using is true
148 : : * and using_names is a list of C strings representing names already assigned
149 : : * to USING columns.
150 : : *
151 : : * When deparsing plan trees, there is always just a single item in the
152 : : * deparse_namespace list (since a plan tree never contains Vars with
153 : : * varlevelsup > 0). We store the Plan node that is the immediate
154 : : * parent of the expression to be deparsed, as well as a list of that
155 : : * Plan's ancestors. In addition, we store its outer and inner subplan nodes,
156 : : * as well as their targetlists, and the index tlist if the current plan node
157 : : * might contain INDEX_VAR Vars. (These fields could be derived on-the-fly
158 : : * from the current Plan node, but it seems notationally clearer to set them
159 : : * up as separate fields.)
160 : : */
161 : : typedef struct
162 : : {
163 : : List *rtable; /* List of RangeTblEntry nodes */
164 : : List *rtable_names; /* Parallel list of names for RTEs */
165 : : List *rtable_columns; /* Parallel list of deparse_columns structs */
166 : : List *subplans; /* List of Plan trees for SubPlans */
167 : : List *ctes; /* List of CommonTableExpr nodes */
168 : : AppendRelInfo **appendrels; /* Array of AppendRelInfo nodes, or NULL */
169 : : /* Workspace for column alias assignment: */
170 : : bool unique_using; /* Are we making USING names globally unique */
171 : : List *using_names; /* List of assigned names for USING columns */
172 : : /* Remaining fields are used only when deparsing a Plan tree: */
173 : : Plan *plan; /* immediate parent of current expression */
174 : : List *ancestors; /* ancestors of plan */
175 : : Plan *outer_plan; /* outer subnode, or NULL if none */
176 : : Plan *inner_plan; /* inner subnode, or NULL if none */
177 : : List *outer_tlist; /* referent for OUTER_VAR Vars */
178 : : List *inner_tlist; /* referent for INNER_VAR Vars */
179 : : List *index_tlist; /* referent for INDEX_VAR Vars */
180 : : /* Special namespace representing a function signature: */
181 : : char *funcname;
182 : : int numargs;
183 : : char **argnames;
184 : : } deparse_namespace;
185 : :
186 : : /*
187 : : * Per-relation data about column alias names.
188 : : *
189 : : * Selecting aliases is unreasonably complicated because of the need to dump
190 : : * rules/views whose underlying tables may have had columns added, deleted, or
191 : : * renamed since the query was parsed. We must nonetheless print the rule/view
192 : : * in a form that can be reloaded and will produce the same results as before.
193 : : *
194 : : * For each RTE used in the query, we must assign column aliases that are
195 : : * unique within that RTE. SQL does not require this of the original query,
196 : : * but due to factors such as *-expansion we need to be able to uniquely
197 : : * reference every column in a decompiled query. As long as we qualify all
198 : : * column references, per-RTE uniqueness is sufficient for that.
199 : : *
200 : : * However, we can't ensure per-column name uniqueness for unnamed join RTEs,
201 : : * since they just inherit column names from their input RTEs, and we can't
202 : : * rename the columns at the join level. Most of the time this isn't an issue
203 : : * because we don't need to reference the join's output columns as such; we
204 : : * can reference the input columns instead. That approach can fail for merged
205 : : * JOIN USING columns, however, so when we have one of those in an unnamed
206 : : * join, we have to make that column's alias globally unique across the whole
207 : : * query to ensure it can be referenced unambiguously.
208 : : *
209 : : * Another problem is that a JOIN USING clause requires the columns to be
210 : : * merged to have the same aliases in both input RTEs, and that no other
211 : : * columns in those RTEs or their children conflict with the USING names.
212 : : * To handle that, we do USING-column alias assignment in a recursive
213 : : * traversal of the query's jointree. When descending through a JOIN with
214 : : * USING, we preassign the USING column names to the child columns, overriding
215 : : * other rules for column alias assignment. We also mark each RTE with a list
216 : : * of all USING column names selected for joins containing that RTE, so that
217 : : * when we assign other columns' aliases later, we can avoid conflicts.
218 : : *
219 : : * Another problem is that if a JOIN's input tables have had columns added or
220 : : * deleted since the query was parsed, we must generate a column alias list
221 : : * for the join that matches the current set of input columns --- otherwise, a
222 : : * change in the number of columns in the left input would throw off matching
223 : : * of aliases to columns of the right input. Thus, positions in the printable
224 : : * column alias list are not necessarily one-for-one with varattnos of the
225 : : * JOIN, so we need a separate new_colnames[] array for printing purposes.
226 : : */
227 : : typedef struct
228 : : {
229 : : /*
230 : : * colnames is an array containing column aliases to use for columns that
231 : : * existed when the query was parsed. Dropped columns have NULL entries.
232 : : * This array can be directly indexed by varattno to get a Var's name.
233 : : *
234 : : * Non-NULL entries are guaranteed unique within the RTE, *except* when
235 : : * this is for an unnamed JOIN RTE. In that case we merely copy up names
236 : : * from the two input RTEs.
237 : : *
238 : : * During the recursive descent in set_using_names(), forcible assignment
239 : : * of a child RTE's column name is represented by pre-setting that element
240 : : * of the child's colnames array. So at that stage, NULL entries in this
241 : : * array just mean that no name has been preassigned, not necessarily that
242 : : * the column is dropped.
243 : : */
244 : : int num_cols; /* length of colnames[] array */
245 : : char **colnames; /* array of C strings and NULLs */
246 : :
247 : : /*
248 : : * new_colnames is an array containing column aliases to use for columns
249 : : * that would exist if the query was re-parsed against the current
250 : : * definitions of its base tables. This is what to print as the column
251 : : * alias list for the RTE. This array does not include dropped columns,
252 : : * but it will include columns added since original parsing. Indexes in
253 : : * it therefore have little to do with current varattno values. As above,
254 : : * entries are unique unless this is for an unnamed JOIN RTE. (In such an
255 : : * RTE, we never actually print this array, but we must compute it anyway
256 : : * for possible use in computing column names of upper joins.) The
257 : : * parallel array is_new_col marks which of these columns are new since
258 : : * original parsing. Entries with is_new_col false must match the
259 : : * non-NULL colnames entries one-for-one.
260 : : */
261 : : int num_new_cols; /* length of new_colnames[] array */
262 : : char **new_colnames; /* array of C strings */
263 : : bool *is_new_col; /* array of bool flags */
264 : :
265 : : /* This flag tells whether we should actually print a column alias list */
266 : : bool printaliases;
267 : :
268 : : /* This list has all names used as USING names in joins above this RTE */
269 : : List *parentUsing; /* names assigned to parent merged columns */
270 : :
271 : : /*
272 : : * If this struct is for a JOIN RTE, we fill these fields during the
273 : : * set_using_names() pass to describe its relationship to its child RTEs.
274 : : *
275 : : * leftattnos and rightattnos are arrays with one entry per existing
276 : : * output column of the join (hence, indexable by join varattno). For a
277 : : * simple reference to a column of the left child, leftattnos[i] is the
278 : : * child RTE's attno and rightattnos[i] is zero; and conversely for a
279 : : * column of the right child. But for merged columns produced by JOIN
280 : : * USING/NATURAL JOIN, both leftattnos[i] and rightattnos[i] are nonzero.
281 : : * Note that a simple reference might be to a child RTE column that's been
282 : : * dropped; but that's OK since the column could not be used in the query.
283 : : *
284 : : * If it's a JOIN USING, usingNames holds the alias names selected for the
285 : : * merged columns (these might be different from the original USING list,
286 : : * if we had to modify names to achieve uniqueness).
287 : : */
288 : : int leftrti; /* rangetable index of left child */
289 : : int rightrti; /* rangetable index of right child */
290 : : int *leftattnos; /* left-child varattnos of join cols, or 0 */
291 : : int *rightattnos; /* right-child varattnos of join cols, or 0 */
292 : : List *usingNames; /* names assigned to merged columns */
293 : : } deparse_columns;
294 : :
295 : : /* This macro is analogous to rt_fetch(), but for deparse_columns structs */
296 : : #define deparse_columns_fetch(rangetable_index, dpns) \
297 : : ((deparse_columns *) list_nth((dpns)->rtable_columns, (rangetable_index)-1))
298 : :
299 : : /*
300 : : * Entry in set_rtable_names' hash table
301 : : */
302 : : typedef struct
303 : : {
304 : : char name[NAMEDATALEN]; /* Hash key --- must be first */
305 : : int counter; /* Largest addition used so far for name */
306 : : } NameHashEntry;
307 : :
308 : : /* Callback signature for resolve_special_varno() */
309 : : typedef void (*rsv_callback) (Node *node, deparse_context *context,
310 : : void *callback_arg);
311 : :
312 : :
313 : : /* ----------
314 : : * Global data
315 : : * ----------
316 : : */
317 : : static SPIPlanPtr plan_getrulebyoid = NULL;
318 : : static const char *const query_getrulebyoid = "SELECT * FROM pg_catalog.pg_rewrite WHERE oid = $1";
319 : : static SPIPlanPtr plan_getviewrule = NULL;
320 : : static const char *const query_getviewrule = "SELECT * FROM pg_catalog.pg_rewrite WHERE ev_class = $1 AND rulename = $2";
321 : :
322 : : /* GUC parameters */
323 : : bool quote_all_identifiers = false;
324 : :
325 : :
326 : : /* ----------
327 : : * Local functions
328 : : *
329 : : * Most of these functions used to use fixed-size buffers to build their
330 : : * results. Now, they take an (already initialized) StringInfo object
331 : : * as a parameter, and append their text output to its contents.
332 : : * ----------
333 : : */
334 : : static char *deparse_expression_pretty(Node *expr, List *dpcontext,
335 : : bool forceprefix, bool showimplicit,
336 : : int prettyFlags, int startIndent);
337 : : static char *pg_get_viewdef_worker(Oid viewoid,
338 : : int prettyFlags, int wrapColumn);
339 : : static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
340 : : static int decompile_column_index_array(Datum column_index_array, Oid relId,
341 : : bool withPeriod, StringInfo buf);
342 : : static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
343 : : static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
344 : : const Oid *excludeOps,
345 : : bool attrsOnly, bool keysOnly,
346 : : bool showTblSpc, bool inherits,
347 : : int prettyFlags, bool missing_ok);
348 : : static char *pg_get_statisticsobj_worker(Oid statextid, bool columns_only,
349 : : bool missing_ok);
350 : : static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags,
351 : : bool attrsOnly, bool missing_ok);
352 : : static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
353 : : int prettyFlags, bool missing_ok);
354 : : static text *pg_get_expr_worker(text *expr, Oid relid, int prettyFlags);
355 : : static int print_function_arguments(StringInfo buf, HeapTuple proctup,
356 : : bool print_table_args, bool print_defaults);
357 : : static void print_function_rettype(StringInfo buf, HeapTuple proctup);
358 : : static void print_function_trftypes(StringInfo buf, HeapTuple proctup);
359 : : static void print_function_sqlbody(StringInfo buf, HeapTuple proctup);
360 : : static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
361 : : Bitmapset *rels_used);
362 : : static void set_deparse_for_query(deparse_namespace *dpns, Query *query,
363 : : List *parent_namespaces);
364 : : static void set_simple_column_names(deparse_namespace *dpns);
365 : : static bool has_dangerous_join_using(deparse_namespace *dpns, Node *jtnode);
366 : : static void set_using_names(deparse_namespace *dpns, Node *jtnode,
367 : : List *parentUsing);
368 : : static void set_relation_column_names(deparse_namespace *dpns,
369 : : RangeTblEntry *rte,
370 : : deparse_columns *colinfo);
371 : : static void set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
372 : : deparse_columns *colinfo);
373 : : static bool colname_is_unique(const char *colname, deparse_namespace *dpns,
374 : : deparse_columns *colinfo);
375 : : static char *make_colname_unique(char *colname, deparse_namespace *dpns,
376 : : deparse_columns *colinfo);
377 : : static void expand_colnames_array_to(deparse_columns *colinfo, int n);
378 : : static void identify_join_columns(JoinExpr *j, RangeTblEntry *jrte,
379 : : deparse_columns *colinfo);
380 : : static char *get_rtable_name(int rtindex, deparse_context *context);
381 : : static void set_deparse_plan(deparse_namespace *dpns, Plan *plan);
382 : : static Plan *find_recursive_union(deparse_namespace *dpns,
383 : : WorkTableScan *wtscan);
384 : : static void push_child_plan(deparse_namespace *dpns, Plan *plan,
385 : : deparse_namespace *save_dpns);
386 : : static void pop_child_plan(deparse_namespace *dpns,
387 : : deparse_namespace *save_dpns);
388 : : static void push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell,
389 : : deparse_namespace *save_dpns);
390 : : static void pop_ancestor_plan(deparse_namespace *dpns,
391 : : deparse_namespace *save_dpns);
392 : : static void make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
393 : : int prettyFlags);
394 : : static void make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
395 : : int prettyFlags, int wrapColumn);
396 : : static void get_query_def(Query *query, StringInfo buf, List *parentnamespace,
397 : : TupleDesc resultDesc, bool colNamesVisible,
398 : : int prettyFlags, int wrapColumn, int startIndent);
399 : : static void get_values_def(List *values_lists, deparse_context *context);
400 : : static void get_with_clause(Query *query, deparse_context *context);
401 : : static void get_select_query_def(Query *query, deparse_context *context,
402 : : TupleDesc resultDesc, bool colNamesVisible);
403 : : static void get_insert_query_def(Query *query, deparse_context *context,
404 : : bool colNamesVisible);
405 : : static void get_update_query_def(Query *query, deparse_context *context,
406 : : bool colNamesVisible);
407 : : static void get_update_query_targetlist_def(Query *query, List *targetList,
408 : : deparse_context *context,
409 : : RangeTblEntry *rte);
410 : : static void get_delete_query_def(Query *query, deparse_context *context,
411 : : bool colNamesVisible);
412 : : static void get_merge_query_def(Query *query, deparse_context *context,
413 : : bool colNamesVisible);
414 : : static void get_utility_query_def(Query *query, deparse_context *context);
415 : : static void get_basic_select_query(Query *query, deparse_context *context,
416 : : TupleDesc resultDesc, bool colNamesVisible);
417 : : static void get_target_list(List *targetList, deparse_context *context,
418 : : TupleDesc resultDesc, bool colNamesVisible);
419 : : static void get_setop_query(Node *setOp, Query *query,
420 : : deparse_context *context,
421 : : TupleDesc resultDesc, bool colNamesVisible);
422 : : static Node *get_rule_sortgroupclause(Index ref, List *tlist,
423 : : bool force_colno,
424 : : deparse_context *context);
425 : : static void get_rule_groupingset(GroupingSet *gset, List *targetlist,
426 : : bool omit_parens, deparse_context *context);
427 : : static void get_rule_orderby(List *orderList, List *targetList,
428 : : bool force_colno, deparse_context *context);
429 : : static void get_rule_windowclause(Query *query, deparse_context *context);
430 : : static void get_rule_windowspec(WindowClause *wc, List *targetList,
431 : : deparse_context *context);
432 : : static char *get_variable(Var *var, int levelsup, bool istoplevel,
433 : : deparse_context *context);
434 : : static void get_special_variable(Node *node, deparse_context *context,
435 : : void *callback_arg);
436 : : static void resolve_special_varno(Node *node, deparse_context *context,
437 : : rsv_callback callback, void *callback_arg);
438 : : static Node *find_param_referent(Param *param, deparse_context *context,
439 : : deparse_namespace **dpns_p, ListCell **ancestor_cell_p);
440 : : static SubPlan *find_param_generator(Param *param, deparse_context *context,
441 : : int *column_p);
442 : : static SubPlan *find_param_generator_initplan(Param *param, Plan *plan,
443 : : int *column_p);
444 : : static void get_parameter(Param *param, deparse_context *context);
445 : : static const char *get_simple_binary_op_name(OpExpr *expr);
446 : : static bool isSimpleNode(Node *node, Node *parentNode, int prettyFlags);
447 : : static void appendContextKeyword(deparse_context *context, const char *str,
448 : : int indentBefore, int indentAfter, int indentPlus);
449 : : static void removeStringInfoSpaces(StringInfo str);
450 : : static void get_rule_expr(Node *node, deparse_context *context,
451 : : bool showimplicit);
452 : : static void get_rule_expr_toplevel(Node *node, deparse_context *context,
453 : : bool showimplicit);
454 : : static void get_rule_list_toplevel(List *lst, deparse_context *context,
455 : : bool showimplicit);
456 : : static void get_rule_expr_funccall(Node *node, deparse_context *context,
457 : : bool showimplicit);
458 : : static bool looks_like_function(Node *node);
459 : : static void get_oper_expr(OpExpr *expr, deparse_context *context);
460 : : static void get_func_expr(FuncExpr *expr, deparse_context *context,
461 : : bool showimplicit);
462 : : static void get_agg_expr(Aggref *aggref, deparse_context *context,
463 : : Aggref *original_aggref);
464 : : static void get_agg_expr_helper(Aggref *aggref, deparse_context *context,
465 : : Aggref *original_aggref, const char *funcname,
466 : : const char *options, bool is_json_objectagg);
467 : : static void get_agg_combine_expr(Node *node, deparse_context *context,
468 : : void *callback_arg);
469 : : static void get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context);
470 : : static void get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context,
471 : : const char *funcname, const char *options,
472 : : bool is_json_objectagg);
473 : : static bool get_func_sql_syntax(FuncExpr *expr, deparse_context *context);
474 : : static void get_coercion_expr(Node *arg, deparse_context *context,
475 : : Oid resulttype, int32 resulttypmod,
476 : : Node *parentNode);
477 : : static void get_const_expr(Const *constval, deparse_context *context,
478 : : int showtype);
479 : : static void get_const_collation(Const *constval, deparse_context *context);
480 : : static void get_json_format(JsonFormat *format, StringInfo buf);
481 : : static void get_json_returning(JsonReturning *returning, StringInfo buf,
482 : : bool json_format_by_default);
483 : : static void get_json_constructor(JsonConstructorExpr *ctor,
484 : : deparse_context *context, bool showimplicit);
485 : : static void get_json_constructor_options(JsonConstructorExpr *ctor,
486 : : StringInfo buf);
487 : : static void get_json_agg_constructor(JsonConstructorExpr *ctor,
488 : : deparse_context *context,
489 : : const char *funcname,
490 : : bool is_json_objectagg);
491 : : static void simple_quote_literal(StringInfo buf, const char *val);
492 : : static void get_sublink_expr(SubLink *sublink, deparse_context *context);
493 : : static void get_tablefunc(TableFunc *tf, deparse_context *context,
494 : : bool showimplicit);
495 : : static void get_from_clause(Query *query, const char *prefix,
496 : : deparse_context *context);
497 : : static void get_from_clause_item(Node *jtnode, Query *query,
498 : : deparse_context *context);
499 : : static void get_rte_alias(RangeTblEntry *rte, int varno, bool use_as,
500 : : deparse_context *context);
501 : : static void get_column_alias_list(deparse_columns *colinfo,
502 : : deparse_context *context);
503 : : static void get_from_clause_coldeflist(RangeTblFunction *rtfunc,
504 : : deparse_columns *colinfo,
505 : : deparse_context *context);
506 : : static void get_tablesample_def(TableSampleClause *tablesample,
507 : : deparse_context *context);
508 : : static void get_opclass_name(Oid opclass, Oid actual_datatype,
509 : : StringInfo buf);
510 : : static Node *processIndirection(Node *node, deparse_context *context);
511 : : static void printSubscripts(SubscriptingRef *sbsref, deparse_context *context);
512 : : static char *get_relation_name(Oid relid);
513 : : static char *generate_relation_name(Oid relid, List *namespaces);
514 : : static char *generate_qualified_relation_name(Oid relid);
515 : : static char *generate_function_name(Oid funcid, int nargs,
516 : : List *argnames, Oid *argtypes,
517 : : bool has_variadic, bool *use_variadic_p,
518 : : ParseExprKind special_exprkind);
519 : : static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
520 : : static void add_cast_to(StringInfo buf, Oid typid);
521 : : static char *generate_qualified_type_name(Oid typid);
522 : : static text *string_to_text(char *str);
523 : : static char *flatten_reloptions(Oid relid);
524 : : static void get_reloptions(StringInfo buf, Datum reloptions);
525 : : static void get_json_path_spec(Node *path_spec, deparse_context *context,
526 : : bool showimplicit);
527 : : static void get_json_table_columns(TableFunc *tf, JsonTablePathScan *scan,
528 : : deparse_context *context,
529 : : bool showimplicit);
530 : : static void get_json_table_nested_columns(TableFunc *tf, JsonTablePlan *plan,
531 : : deparse_context *context,
532 : : bool showimplicit,
533 : : bool needcomma);
534 : :
535 : : #define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
536 : :
537 : :
538 : : /* ----------
539 : : * pg_get_ruledef - Do it all and return a text
540 : : * that could be used as a statement
541 : : * to recreate the rule
542 : : * ----------
543 : : */
544 : : Datum
8683 tgl@sss.pgh.pa.us 545 :CBC 221 : pg_get_ruledef(PG_FUNCTION_ARGS)
546 : : {
8032 547 : 221 : Oid ruleoid = PG_GETARG_OID(0);
548 : : int prettyFlags;
549 : : char *res;
550 : :
4088 551 : 221 : prettyFlags = PRETTYFLAG_INDENT;
552 : :
2819 rhaas@postgresql.org 553 : 221 : res = pg_get_ruledef_worker(ruleoid, prettyFlags);
554 : :
555 [ + + ]: 221 : if (res == NULL)
556 : 3 : PG_RETURN_NULL();
557 : :
558 : 218 : PG_RETURN_TEXT_P(string_to_text(res));
559 : : }
560 : :
561 : :
562 : : Datum
7564 tgl@sss.pgh.pa.us 563 : 57 : pg_get_ruledef_ext(PG_FUNCTION_ARGS)
564 : : {
565 : 57 : Oid ruleoid = PG_GETARG_OID(0);
566 : 57 : bool pretty = PG_GETARG_BOOL(1);
567 : : int prettyFlags;
568 : : char *res;
569 : :
748 570 [ + - ]: 57 : prettyFlags = GET_PRETTY_FLAGS(pretty);
571 : :
2819 rhaas@postgresql.org 572 : 57 : res = pg_get_ruledef_worker(ruleoid, prettyFlags);
573 : :
574 [ - + ]: 57 : if (res == NULL)
2819 rhaas@postgresql.org 575 :UBC 0 : PG_RETURN_NULL();
576 : :
2819 rhaas@postgresql.org 577 :CBC 57 : PG_RETURN_TEXT_P(string_to_text(res));
578 : : }
579 : :
580 : :
581 : : static char *
7564 tgl@sss.pgh.pa.us 582 : 278 : pg_get_ruledef_worker(Oid ruleoid, int prettyFlags)
583 : : {
584 : : Datum args[1];
585 : : char nulls[1];
586 : : int spirc;
587 : : HeapTuple ruletup;
588 : : TupleDesc rulettc;
589 : : StringInfoData buf;
590 : :
591 : : /*
592 : : * Do this first so that string is alloc'd in outer context not SPI's.
593 : : */
7284 594 : 278 : initStringInfo(&buf);
595 : :
596 : : /*
597 : : * Connect to SPI manager
598 : : */
9357 bruce@momjian.us 599 [ - + ]: 278 : if (SPI_connect() != SPI_OK_CONNECT)
7567 tgl@sss.pgh.pa.us 600 [ # # ]:UBC 0 : elog(ERROR, "SPI_connect failed");
601 : :
602 : : /*
603 : : * On the first call prepare the plan to lookup pg_rewrite. We read
604 : : * pg_rewrite over the SPI manager instead of using the syscache to be
605 : : * checked for read access on pg_rewrite.
606 : : */
8032 tgl@sss.pgh.pa.us 607 [ + + ]:CBC 278 : if (plan_getrulebyoid == NULL)
608 : : {
609 : : Oid argtypes[1];
610 : : SPIPlanPtr plan;
611 : :
612 : 18 : argtypes[0] = OIDOID;
613 : 18 : plan = SPI_prepare(query_getrulebyoid, 1, argtypes);
9357 bruce@momjian.us 614 [ - + ]: 18 : if (plan == NULL)
7567 tgl@sss.pgh.pa.us 615 [ # # ]:UBC 0 : elog(ERROR, "SPI_prepare failed for \"%s\"", query_getrulebyoid);
4594 tgl@sss.pgh.pa.us 616 :CBC 18 : SPI_keepplan(plan);
617 : 18 : plan_getrulebyoid = plan;
618 : : }
619 : :
620 : : /*
621 : : * Get the pg_rewrite tuple for this rule
622 : : */
8032 623 : 278 : args[0] = ObjectIdGetDatum(ruleoid);
624 : 278 : nulls[0] = ' ';
3795 peter_e@gmx.net 625 : 278 : spirc = SPI_execute_plan(plan_getrulebyoid, args, nulls, true, 0);
9357 bruce@momjian.us 626 [ - + ]: 278 : if (spirc != SPI_OK_SELECT)
7567 tgl@sss.pgh.pa.us 627 [ # # ]:UBC 0 : elog(ERROR, "failed to get pg_rewrite tuple for rule %u", ruleoid);
9357 bruce@momjian.us 628 [ + + ]:CBC 278 : if (SPI_processed != 1)
629 : : {
630 : : /*
631 : : * There is no tuple data available here, just keep the output buffer
632 : : * empty.
633 : : */
634 : : }
635 : : else
636 : : {
637 : : /*
638 : : * Get the rule's definition and put it into executor's memory
639 : : */
7284 tgl@sss.pgh.pa.us 640 : 275 : ruletup = SPI_tuptable->vals[0];
641 : 275 : rulettc = SPI_tuptable->tupdesc;
642 : 275 : make_ruledef(&buf, ruletup, rulettc, prettyFlags);
643 : : }
644 : :
645 : : /*
646 : : * Disconnect from SPI manager
647 : : */
9357 bruce@momjian.us 648 [ - + ]: 278 : if (SPI_finish() != SPI_OK_FINISH)
7567 tgl@sss.pgh.pa.us 649 [ # # ]:UBC 0 : elog(ERROR, "SPI_finish failed");
650 : :
2819 rhaas@postgresql.org 651 [ + + ]:CBC 278 : if (buf.len == 0)
652 : 3 : return NULL;
653 : :
7284 tgl@sss.pgh.pa.us 654 : 275 : return buf.data;
655 : : }
656 : :
657 : :
658 : : /* ----------
659 : : * pg_get_viewdef - Mainly the same thing, but we
660 : : * only return the SELECT part of a view
661 : : * ----------
662 : : */
663 : : Datum
8683 664 : 1058 : pg_get_viewdef(PG_FUNCTION_ARGS)
665 : : {
666 : : /* By OID */
8032 667 : 1058 : Oid viewoid = PG_GETARG_OID(0);
668 : : int prettyFlags;
669 : : char *res;
670 : :
4088 671 : 1058 : prettyFlags = PRETTYFLAG_INDENT;
672 : :
2819 rhaas@postgresql.org 673 : 1058 : res = pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT);
674 : :
675 [ + + ]: 1058 : if (res == NULL)
676 : 3 : PG_RETURN_NULL();
677 : :
678 : 1055 : PG_RETURN_TEXT_P(string_to_text(res));
679 : : }
680 : :
681 : :
682 : : Datum
7564 tgl@sss.pgh.pa.us 683 : 254 : pg_get_viewdef_ext(PG_FUNCTION_ARGS)
684 : : {
685 : : /* By OID */
686 : 254 : Oid viewoid = PG_GETARG_OID(0);
687 : 254 : bool pretty = PG_GETARG_BOOL(1);
688 : : int prettyFlags;
689 : : char *res;
690 : :
748 691 [ + - ]: 254 : prettyFlags = GET_PRETTY_FLAGS(pretty);
692 : :
2819 rhaas@postgresql.org 693 : 254 : res = pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT);
694 : :
695 [ - + ]: 254 : if (res == NULL)
2819 rhaas@postgresql.org 696 :UBC 0 : PG_RETURN_NULL();
697 : :
2819 rhaas@postgresql.org 698 :CBC 254 : PG_RETURN_TEXT_P(string_to_text(res));
699 : : }
700 : :
701 : : Datum
4438 andrew@dunslane.net 702 : 3 : pg_get_viewdef_wrap(PG_FUNCTION_ARGS)
703 : : {
704 : : /* By OID */
705 : 3 : Oid viewoid = PG_GETARG_OID(0);
4326 bruce@momjian.us 706 : 3 : int wrap = PG_GETARG_INT32(1);
707 : : int prettyFlags;
708 : : char *res;
709 : :
710 : : /* calling this implies we want pretty printing */
748 tgl@sss.pgh.pa.us 711 : 3 : prettyFlags = GET_PRETTY_FLAGS(true);
712 : :
2819 rhaas@postgresql.org 713 : 3 : res = pg_get_viewdef_worker(viewoid, prettyFlags, wrap);
714 : :
715 [ - + ]: 3 : if (res == NULL)
2819 rhaas@postgresql.org 716 :UBC 0 : PG_RETURN_NULL();
717 : :
2819 rhaas@postgresql.org 718 :CBC 3 : PG_RETURN_TEXT_P(string_to_text(res));
719 : : }
720 : :
721 : : Datum
8032 tgl@sss.pgh.pa.us 722 : 36 : pg_get_viewdef_name(PG_FUNCTION_ARGS)
723 : : {
724 : : /* By qualified name */
2590 noah@leadboat.com 725 : 36 : text *viewname = PG_GETARG_TEXT_PP(0);
726 : : int prettyFlags;
727 : : RangeVar *viewrel;
728 : : Oid viewoid;
729 : : char *res;
730 : :
4088 tgl@sss.pgh.pa.us 731 : 36 : prettyFlags = PRETTYFLAG_INDENT;
732 : :
733 : : /* Look up view name. Can't lock it - we might not have privileges. */
6897 neilc@samurai.com 734 : 36 : viewrel = makeRangeVarFromNameList(textToQualifiedNameList(viewname));
4519 rhaas@postgresql.org 735 : 36 : viewoid = RangeVarGetRelid(viewrel, NoLock, false);
736 : :
2819 737 : 36 : res = pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT);
738 : :
739 [ - + ]: 36 : if (res == NULL)
2819 rhaas@postgresql.org 740 :UBC 0 : PG_RETURN_NULL();
741 : :
2819 rhaas@postgresql.org 742 :CBC 36 : PG_RETURN_TEXT_P(string_to_text(res));
743 : : }
744 : :
745 : :
746 : : Datum
7564 tgl@sss.pgh.pa.us 747 : 201 : pg_get_viewdef_name_ext(PG_FUNCTION_ARGS)
748 : : {
749 : : /* By qualified name */
2590 noah@leadboat.com 750 : 201 : text *viewname = PG_GETARG_TEXT_PP(0);
7564 tgl@sss.pgh.pa.us 751 : 201 : bool pretty = PG_GETARG_BOOL(1);
752 : : int prettyFlags;
753 : : RangeVar *viewrel;
754 : : Oid viewoid;
755 : : char *res;
756 : :
748 757 [ + - ]: 201 : prettyFlags = GET_PRETTY_FLAGS(pretty);
758 : :
759 : : /* Look up view name. Can't lock it - we might not have privileges. */
6897 neilc@samurai.com 760 : 201 : viewrel = makeRangeVarFromNameList(textToQualifiedNameList(viewname));
4519 rhaas@postgresql.org 761 : 201 : viewoid = RangeVarGetRelid(viewrel, NoLock, false);
762 : :
2807 tgl@sss.pgh.pa.us 763 : 201 : res = pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT);
764 : :
765 [ - + ]: 201 : if (res == NULL)
2807 tgl@sss.pgh.pa.us 766 :UBC 0 : PG_RETURN_NULL();
767 : :
2807 tgl@sss.pgh.pa.us 768 :CBC 201 : PG_RETURN_TEXT_P(string_to_text(res));
769 : : }
770 : :
771 : : /*
772 : : * Common code for by-OID and by-name variants of pg_get_viewdef
773 : : */
774 : : static char *
4129 775 : 1552 : pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn)
776 : : {
777 : : Datum args[2];
778 : : char nulls[2];
779 : : int spirc;
780 : : HeapTuple ruletup;
781 : : TupleDesc rulettc;
782 : : StringInfoData buf;
783 : :
784 : : /*
785 : : * Do this first so that string is alloc'd in outer context not SPI's.
786 : : */
7284 787 : 1552 : initStringInfo(&buf);
788 : :
789 : : /*
790 : : * Connect to SPI manager
791 : : */
9357 bruce@momjian.us 792 [ - + ]: 1552 : if (SPI_connect() != SPI_OK_CONNECT)
7567 tgl@sss.pgh.pa.us 793 [ # # ]:UBC 0 : elog(ERROR, "SPI_connect failed");
794 : :
795 : : /*
796 : : * On the first call prepare the plan to lookup pg_rewrite. We read
797 : : * pg_rewrite over the SPI manager instead of using the syscache to be
798 : : * checked for read access on pg_rewrite.
799 : : */
8032 tgl@sss.pgh.pa.us 800 [ + + ]:CBC 1552 : if (plan_getviewrule == NULL)
801 : : {
802 : : Oid argtypes[2];
803 : : SPIPlanPtr plan;
804 : :
805 : 118 : argtypes[0] = OIDOID;
806 : 118 : argtypes[1] = NAMEOID;
807 : 118 : plan = SPI_prepare(query_getviewrule, 2, argtypes);
9357 bruce@momjian.us 808 [ - + ]: 118 : if (plan == NULL)
7567 tgl@sss.pgh.pa.us 809 [ # # ]:UBC 0 : elog(ERROR, "SPI_prepare failed for \"%s\"", query_getviewrule);
4594 tgl@sss.pgh.pa.us 810 :CBC 118 : SPI_keepplan(plan);
811 : 118 : plan_getviewrule = plan;
812 : : }
813 : :
814 : : /*
815 : : * Get the pg_rewrite tuple for the view's SELECT rule
816 : : */
8032 817 : 1552 : args[0] = ObjectIdGetDatum(viewoid);
3795 peter_e@gmx.net 818 : 1552 : args[1] = DirectFunctionCall1(namein, CStringGetDatum(ViewSelectRuleName));
9357 bruce@momjian.us 819 : 1552 : nulls[0] = ' ';
8032 tgl@sss.pgh.pa.us 820 : 1552 : nulls[1] = ' ';
3795 peter_e@gmx.net 821 : 1552 : spirc = SPI_execute_plan(plan_getviewrule, args, nulls, true, 0);
9357 bruce@momjian.us 822 [ - + ]: 1552 : if (spirc != SPI_OK_SELECT)
8017 tgl@sss.pgh.pa.us 823 [ # # ]:UBC 0 : elog(ERROR, "failed to get pg_rewrite tuple for view %u", viewoid);
9357 bruce@momjian.us 824 [ + + ]:CBC 1552 : if (SPI_processed != 1)
825 : : {
826 : : /*
827 : : * There is no tuple data available here, just keep the output buffer
828 : : * empty.
829 : : */
830 : : }
831 : : else
832 : : {
833 : : /*
834 : : * Get the rule's definition and put it into executor's memory
835 : : */
836 : 1549 : ruletup = SPI_tuptable->vals[0];
837 : 1549 : rulettc = SPI_tuptable->tupdesc;
4129 tgl@sss.pgh.pa.us 838 : 1549 : make_viewdef(&buf, ruletup, rulettc, prettyFlags, wrapColumn);
839 : : }
840 : :
841 : : /*
842 : : * Disconnect from SPI manager
843 : : */
9357 bruce@momjian.us 844 [ - + ]: 1552 : if (SPI_finish() != SPI_OK_FINISH)
7567 tgl@sss.pgh.pa.us 845 [ # # ]:UBC 0 : elog(ERROR, "SPI_finish failed");
846 : :
2819 rhaas@postgresql.org 847 [ + + ]:CBC 1552 : if (buf.len == 0)
848 : 3 : return NULL;
849 : :
7284 tgl@sss.pgh.pa.us 850 : 1549 : return buf.data;
851 : : }
852 : :
853 : : /* ----------
854 : : * pg_get_triggerdef - Get the definition of a trigger
855 : : * ----------
856 : : */
857 : : Datum
7696 bruce@momjian.us 858 : 102 : pg_get_triggerdef(PG_FUNCTION_ARGS)
859 : : {
860 : 102 : Oid trigid = PG_GETARG_OID(0);
861 : : char *res;
862 : :
2819 rhaas@postgresql.org 863 : 102 : res = pg_get_triggerdef_worker(trigid, false);
864 : :
865 [ + + ]: 102 : if (res == NULL)
866 : 3 : PG_RETURN_NULL();
867 : :
868 : 99 : PG_RETURN_TEXT_P(string_to_text(res));
869 : : }
870 : :
871 : : Datum
5301 peter_e@gmx.net 872 : 580 : pg_get_triggerdef_ext(PG_FUNCTION_ARGS)
873 : : {
874 : 580 : Oid trigid = PG_GETARG_OID(0);
875 : 580 : bool pretty = PG_GETARG_BOOL(1);
876 : : char *res;
877 : :
2819 rhaas@postgresql.org 878 : 580 : res = pg_get_triggerdef_worker(trigid, pretty);
879 : :
880 [ - + ]: 580 : if (res == NULL)
2819 rhaas@postgresql.org 881 :UBC 0 : PG_RETURN_NULL();
882 : :
2819 rhaas@postgresql.org 883 :CBC 580 : PG_RETURN_TEXT_P(string_to_text(res));
884 : : }
885 : :
886 : : static char *
5301 peter_e@gmx.net 887 : 682 : pg_get_triggerdef_worker(Oid trigid, bool pretty)
888 : : {
889 : : HeapTuple ht_trig;
890 : : Form_pg_trigger trigrec;
891 : : StringInfoData buf;
892 : : Relation tgrel;
893 : : ScanKeyData skey[1];
894 : : SysScanDesc tgscan;
7635 tgl@sss.pgh.pa.us 895 : 682 : int findx = 0;
896 : : char *tgname;
897 : : char *tgoldtable;
898 : : char *tgnewtable;
899 : : Datum value;
900 : : bool isnull;
901 : :
902 : : /*
903 : : * Fetch the pg_trigger tuple by the Oid of the trigger
904 : : */
1910 andres@anarazel.de 905 : 682 : tgrel = table_open(TriggerRelationId, AccessShareLock);
906 : :
7459 tgl@sss.pgh.pa.us 907 : 682 : ScanKeyInit(&skey[0],
908 : : Anum_pg_trigger_oid,
909 : : BTEqualStrategyNumber, F_OIDEQ,
910 : : ObjectIdGetDatum(trigid));
911 : :
6940 912 : 682 : tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true,
913 : : NULL, 1, skey);
914 : :
7635 915 : 682 : ht_trig = systable_getnext(tgscan);
916 : :
917 [ + + ]: 682 : if (!HeapTupleIsValid(ht_trig))
918 : : {
2819 rhaas@postgresql.org 919 : 3 : systable_endscan(tgscan);
1910 andres@anarazel.de 920 : 3 : table_close(tgrel, AccessShareLock);
2819 rhaas@postgresql.org 921 : 3 : return NULL;
922 : : }
923 : :
7696 bruce@momjian.us 924 : 679 : trigrec = (Form_pg_trigger) GETSTRUCT(ht_trig);
925 : :
926 : : /*
927 : : * Start the trigger definition. Note that the trigger's name should never
928 : : * be schema-qualified, but the trigger rel's name may be.
929 : : */
930 : 679 : initStringInfo(&buf);
931 : :
932 : 679 : tgname = NameStr(trigrec->tgname);
5197 itagaki.takahiro@gma 933 :UBC 0 : appendStringInfo(&buf, "CREATE %sTRIGGER %s ",
5201 tgl@sss.pgh.pa.us 934 [ - + ]:CBC 679 : OidIsValid(trigrec->tgconstraint) ? "CONSTRAINT " : "",
935 : : quote_identifier(tgname));
936 : :
7696 bruce@momjian.us 937 [ + + ]: 679 : if (TRIGGER_FOR_BEFORE(trigrec->tgtype))
3818 rhaas@postgresql.org 938 : 263 : appendStringInfoString(&buf, "BEFORE");
4935 tgl@sss.pgh.pa.us 939 [ + + ]: 416 : else if (TRIGGER_FOR_AFTER(trigrec->tgtype))
3818 rhaas@postgresql.org 940 : 404 : appendStringInfoString(&buf, "AFTER");
4935 tgl@sss.pgh.pa.us 941 [ + - ]: 12 : else if (TRIGGER_FOR_INSTEAD(trigrec->tgtype))
3818 rhaas@postgresql.org 942 : 12 : appendStringInfoString(&buf, "INSTEAD OF");
943 : : else
4935 tgl@sss.pgh.pa.us 944 [ # # ]:UBC 0 : elog(ERROR, "unexpected tgtype value: %d", trigrec->tgtype);
945 : :
7696 bruce@momjian.us 946 [ + + ]:CBC 679 : if (TRIGGER_FOR_INSERT(trigrec->tgtype))
947 : : {
3818 rhaas@postgresql.org 948 : 447 : appendStringInfoString(&buf, " INSERT");
7696 bruce@momjian.us 949 : 447 : findx++;
950 : : }
951 [ + + ]: 679 : if (TRIGGER_FOR_DELETE(trigrec->tgtype))
952 : : {
953 [ + + ]: 113 : if (findx > 0)
3818 rhaas@postgresql.org 954 : 45 : appendStringInfoString(&buf, " OR DELETE");
955 : : else
956 : 68 : appendStringInfoString(&buf, " DELETE");
7696 bruce@momjian.us 957 : 113 : findx++;
958 : : }
959 [ + + ]: 679 : if (TRIGGER_FOR_UPDATE(trigrec->tgtype))
960 : : {
961 [ + + ]: 324 : if (findx > 0)
3818 rhaas@postgresql.org 962 : 160 : appendStringInfoString(&buf, " OR UPDATE");
963 : : else
964 : 164 : appendStringInfoString(&buf, " UPDATE");
5259 tgl@sss.pgh.pa.us 965 : 324 : findx++;
966 : : /* tgattr is first var-width field, so OK to access directly */
5296 967 [ + + ]: 324 : if (trigrec->tgattr.dim1 > 0)
968 : : {
969 : : int i;
970 : :
971 : 38 : appendStringInfoString(&buf, " OF ");
972 [ + + ]: 84 : for (i = 0; i < trigrec->tgattr.dim1; i++)
973 : : {
974 : : char *attname;
975 : :
976 [ + + ]: 46 : if (i > 0)
977 : 8 : appendStringInfoString(&buf, ", ");
2253 alvherre@alvh.no-ip. 978 : 46 : attname = get_attname(trigrec->tgrelid,
979 : 46 : trigrec->tgattr.values[i], false);
5296 tgl@sss.pgh.pa.us 980 : 46 : appendStringInfoString(&buf, quote_identifier(attname));
981 : : }
982 : : }
983 : : }
5861 984 [ - + ]: 679 : if (TRIGGER_FOR_TRUNCATE(trigrec->tgtype))
985 : : {
5861 tgl@sss.pgh.pa.us 986 [ # # ]:UBC 0 : if (findx > 0)
3818 rhaas@postgresql.org 987 : 0 : appendStringInfoString(&buf, " OR TRUNCATE");
988 : : else
989 : 0 : appendStringInfoString(&buf, " TRUNCATE");
5259 tgl@sss.pgh.pa.us 990 : 0 : findx++;
991 : : }
992 : :
993 : : /*
994 : : * In non-pretty mode, always schema-qualify the target table name for
995 : : * safety. In pretty mode, schema-qualify only if not visible.
996 : : */
5197 itagaki.takahiro@gma 997 [ + + ]:CBC 1358 : appendStringInfo(&buf, " ON %s ",
998 : : pretty ?
2239 tgl@sss.pgh.pa.us 999 : 69 : generate_relation_name(trigrec->tgrelid, NIL) :
1000 : 610 : generate_qualified_relation_name(trigrec->tgrelid));
1001 : :
5201 1002 [ - + ]: 679 : if (OidIsValid(trigrec->tgconstraint))
1003 : : {
5259 tgl@sss.pgh.pa.us 1004 [ # # ]:UBC 0 : if (OidIsValid(trigrec->tgconstrrelid))
5197 itagaki.takahiro@gma 1005 : 0 : appendStringInfo(&buf, "FROM %s ",
1006 : : generate_relation_name(trigrec->tgconstrrelid, NIL));
7696 bruce@momjian.us 1007 [ # # ]: 0 : if (!trigrec->tgdeferrable)
3818 rhaas@postgresql.org 1008 : 0 : appendStringInfoString(&buf, "NOT ");
1009 : 0 : appendStringInfoString(&buf, "DEFERRABLE INITIALLY ");
7696 bruce@momjian.us 1010 [ # # ]: 0 : if (trigrec->tginitdeferred)
3818 rhaas@postgresql.org 1011 : 0 : appendStringInfoString(&buf, "DEFERRED ");
1012 : : else
1013 : 0 : appendStringInfoString(&buf, "IMMEDIATE ");
1014 : : }
1015 : :
2718 kgrittn@postgresql.o 1016 :CBC 679 : value = fastgetattr(ht_trig, Anum_pg_trigger_tgoldtable,
1017 : : tgrel->rd_att, &isnull);
1018 [ + + ]: 679 : if (!isnull)
2004 tgl@sss.pgh.pa.us 1019 : 49 : tgoldtable = NameStr(*DatumGetName(value));
1020 : : else
2718 kgrittn@postgresql.o 1021 : 630 : tgoldtable = NULL;
1022 : 679 : value = fastgetattr(ht_trig, Anum_pg_trigger_tgnewtable,
1023 : : tgrel->rd_att, &isnull);
1024 [ + + ]: 679 : if (!isnull)
2004 tgl@sss.pgh.pa.us 1025 : 54 : tgnewtable = NameStr(*DatumGetName(value));
1026 : : else
2718 kgrittn@postgresql.o 1027 : 625 : tgnewtable = NULL;
1028 [ + + + + ]: 679 : if (tgoldtable != NULL || tgnewtable != NULL)
1029 : : {
1030 : 76 : appendStringInfoString(&buf, "REFERENCING ");
1031 [ + + ]: 76 : if (tgoldtable != NULL)
2004 tgl@sss.pgh.pa.us 1032 : 49 : appendStringInfo(&buf, "OLD TABLE AS %s ",
1033 : : quote_identifier(tgoldtable));
2718 kgrittn@postgresql.o 1034 [ + + ]: 76 : if (tgnewtable != NULL)
2004 tgl@sss.pgh.pa.us 1035 : 54 : appendStringInfo(&buf, "NEW TABLE AS %s ",
1036 : : quote_identifier(tgnewtable));
1037 : : }
1038 : :
7696 bruce@momjian.us 1039 [ + + ]: 679 : if (TRIGGER_FOR_ROW(trigrec->tgtype))
3818 rhaas@postgresql.org 1040 : 520 : appendStringInfoString(&buf, "FOR EACH ROW ");
1041 : : else
1042 : 159 : appendStringInfoString(&buf, "FOR EACH STATEMENT ");
1043 : :
1044 : : /* If the trigger has a WHEN qualification, add that */
5259 tgl@sss.pgh.pa.us 1045 : 679 : value = fastgetattr(ht_trig, Anum_pg_trigger_tgqual,
1046 : : tgrel->rd_att, &isnull);
1047 [ + + ]: 679 : if (!isnull)
1048 : : {
1049 : : Node *qual;
1050 : : char relkind;
1051 : : deparse_context context;
1052 : : deparse_namespace dpns;
1053 : : RangeTblEntry *oldrte;
1054 : : RangeTblEntry *newrte;
1055 : :
1056 : 72 : appendStringInfoString(&buf, "WHEN (");
1057 : :
1058 : 72 : qual = stringToNode(TextDatumGetCString(value));
1059 : :
4800 1060 : 72 : relkind = get_rel_relkind(trigrec->tgrelid);
1061 : :
1062 : : /* Build minimal OLD and NEW RTEs for the rel */
5259 1063 : 72 : oldrte = makeNode(RangeTblEntry);
1064 : 72 : oldrte->rtekind = RTE_RELATION;
1065 : 72 : oldrte->relid = trigrec->tgrelid;
4800 1066 : 72 : oldrte->relkind = relkind;
2023 1067 : 72 : oldrte->rellockmode = AccessShareLock;
4223 1068 : 72 : oldrte->alias = makeAlias("old", NIL);
1069 : 72 : oldrte->eref = oldrte->alias;
4268 1070 : 72 : oldrte->lateral = false;
5259 1071 : 72 : oldrte->inh = false;
1072 : 72 : oldrte->inFromCl = true;
1073 : :
1074 : 72 : newrte = makeNode(RangeTblEntry);
1075 : 72 : newrte->rtekind = RTE_RELATION;
1076 : 72 : newrte->relid = trigrec->tgrelid;
4800 1077 : 72 : newrte->relkind = relkind;
2023 1078 : 72 : newrte->rellockmode = AccessShareLock;
4223 1079 : 72 : newrte->alias = makeAlias("new", NIL);
1080 : 72 : newrte->eref = newrte->alias;
4268 1081 : 72 : newrte->lateral = false;
5259 1082 : 72 : newrte->inh = false;
1083 : 72 : newrte->inFromCl = true;
1084 : :
1085 : : /* Build two-element rtable */
5024 1086 : 72 : memset(&dpns, 0, sizeof(dpns));
5259 1087 : 72 : dpns.rtable = list_make2(oldrte, newrte);
1586 1088 : 72 : dpns.subplans = NIL;
5259 1089 : 72 : dpns.ctes = NIL;
1586 1090 : 72 : dpns.appendrels = NULL;
4223 1091 : 72 : set_rtable_names(&dpns, NIL, NULL);
4122 1092 : 72 : set_simple_column_names(&dpns);
1093 : :
1094 : : /* Set up context with one-deep namespace stack */
5259 1095 : 72 : context.buf = &buf;
1096 : 72 : context.namespaces = list_make1(&dpns);
1097 : 72 : context.windowClause = NIL;
1098 : 72 : context.windowTList = NIL;
1099 : 72 : context.varprefix = true;
748 1100 [ + + ]: 72 : context.prettyFlags = GET_PRETTY_FLAGS(pretty);
4129 1101 : 72 : context.wrapColumn = WRAP_COLUMN_DEFAULT;
5259 1102 : 72 : context.indentLevel = PRETTYINDENT_STD;
3256 andres@anarazel.de 1103 : 72 : context.special_exprkind = EXPR_KIND_NONE;
1586 tgl@sss.pgh.pa.us 1104 : 72 : context.appendparents = NULL;
1105 : :
5259 1106 : 72 : get_rule_expr(qual, &context, false);
1107 : :
3818 rhaas@postgresql.org 1108 : 72 : appendStringInfoString(&buf, ") ");
1109 : : }
1110 : :
1893 peter@eisentraut.org 1111 : 679 : appendStringInfo(&buf, "EXECUTE FUNCTION %s(",
1112 : : generate_function_name(trigrec->tgfoid, 0,
1113 : : NIL, NULL,
1114 : : false, NULL, EXPR_KIND_NONE));
1115 : :
7635 tgl@sss.pgh.pa.us 1116 [ + + ]: 679 : if (trigrec->tgnargs > 0)
1117 : : {
1118 : : char *p;
1119 : : int i;
1120 : :
5259 1121 : 225 : value = fastgetattr(ht_trig, Anum_pg_trigger_tgargs,
1122 : : tgrel->rd_att, &isnull);
7635 1123 [ - + ]: 225 : if (isnull)
7635 tgl@sss.pgh.pa.us 1124 [ # # ]:UBC 0 : elog(ERROR, "tgargs is null for trigger %u", trigid);
2590 noah@leadboat.com 1125 [ + - ]:CBC 225 : p = (char *) VARDATA_ANY(DatumGetByteaPP(value));
7635 tgl@sss.pgh.pa.us 1126 [ + + ]: 598 : for (i = 0; i < trigrec->tgnargs; i++)
1127 : : {
1128 [ + + ]: 373 : if (i > 0)
3818 rhaas@postgresql.org 1129 : 148 : appendStringInfoString(&buf, ", ");
5699 tgl@sss.pgh.pa.us 1130 : 373 : simple_quote_literal(&buf, p);
1131 : : /* advance p to next string embedded in tgargs */
1132 [ + + ]: 3378 : while (*p)
1133 : 3005 : p++;
6531 1134 : 373 : p++;
1135 : : }
1136 : : }
1137 : :
1138 : : /* We deliberately do not put semi-colon at end */
3818 rhaas@postgresql.org 1139 : 679 : appendStringInfoChar(&buf, ')');
1140 : :
1141 : : /* Clean up */
7635 tgl@sss.pgh.pa.us 1142 : 679 : systable_endscan(tgscan);
1143 : :
1910 andres@anarazel.de 1144 : 679 : table_close(tgrel, AccessShareLock);
1145 : :
5301 peter_e@gmx.net 1146 : 679 : return buf.data;
1147 : : }
1148 : :
1149 : : /* ----------
1150 : : * pg_get_indexdef - Get the definition of an index
1151 : : *
1152 : : * In the extended version, there is a colno argument as well as pretty bool.
1153 : : * if colno == 0, we want a complete index definition.
1154 : : * if colno > 0, we only want the Nth index key's variable or expression.
1155 : : *
1156 : : * Note that the SQL-function versions of this omit any info about the
1157 : : * index tablespace; this is intentional because pg_dump wants it that way.
1158 : : * However pg_get_indexdef_string() includes the index tablespace.
1159 : : * ----------
1160 : : */
1161 : : Datum
8710 tgl@sss.pgh.pa.us 1162 : 2468 : pg_get_indexdef(PG_FUNCTION_ARGS)
1163 : : {
1164 : 2468 : Oid indexrelid = PG_GETARG_OID(0);
1165 : : int prettyFlags;
1166 : : char *res;
1167 : :
4088 1168 : 2468 : prettyFlags = PRETTYFLAG_INDENT;
1169 : :
2096 1170 : 2468 : res = pg_get_indexdef_worker(indexrelid, 0, NULL,
1171 : : false, false,
1172 : : false, false,
1173 : : prettyFlags, true);
1174 : :
2819 rhaas@postgresql.org 1175 [ + + ]: 2468 : if (res == NULL)
1176 : 3 : PG_RETURN_NULL();
1177 : :
1178 : 2465 : PG_RETURN_TEXT_P(string_to_text(res));
1179 : : }
1180 : :
1181 : : Datum
7564 tgl@sss.pgh.pa.us 1182 : 928 : pg_get_indexdef_ext(PG_FUNCTION_ARGS)
1183 : : {
1184 : 928 : Oid indexrelid = PG_GETARG_OID(0);
7559 bruce@momjian.us 1185 : 928 : int32 colno = PG_GETARG_INT32(1);
7564 tgl@sss.pgh.pa.us 1186 : 928 : bool pretty = PG_GETARG_BOOL(2);
1187 : : int prettyFlags;
1188 : : char *res;
1189 : :
748 1190 [ + - ]: 928 : prettyFlags = GET_PRETTY_FLAGS(pretty);
1191 : :
2096 1192 : 928 : res = pg_get_indexdef_worker(indexrelid, colno, NULL,
1193 : : colno != 0, false,
1194 : : false, false,
1195 : : prettyFlags, true);
1196 : :
2819 rhaas@postgresql.org 1197 [ - + ]: 928 : if (res == NULL)
2819 rhaas@postgresql.org 1198 :UBC 0 : PG_RETURN_NULL();
1199 : :
2819 rhaas@postgresql.org 1200 :CBC 928 : PG_RETURN_TEXT_P(string_to_text(res));
1201 : : }
1202 : :
1203 : : /*
1204 : : * Internal version for use by ALTER TABLE.
1205 : : * Includes a tablespace clause in the result.
1206 : : * Returns a palloc'd C string; no pretty-printing.
1207 : : */
1208 : : char *
7284 tgl@sss.pgh.pa.us 1209 : 106 : pg_get_indexdef_string(Oid indexrelid)
1210 : : {
2096 1211 : 106 : return pg_get_indexdef_worker(indexrelid, 0, NULL,
1212 : : false, false,
1213 : : true, true,
1214 : : 0, false);
1215 : : }
1216 : :
1217 : : /* Internal version that just reports the key-column definitions */
1218 : : char *
5370 1219 : 382 : pg_get_indexdef_columns(Oid indexrelid, bool pretty)
1220 : : {
1221 : : int prettyFlags;
1222 : :
748 1223 [ + - ]: 382 : prettyFlags = GET_PRETTY_FLAGS(pretty);
1224 : :
2096 1225 : 382 : return pg_get_indexdef_worker(indexrelid, 0, NULL,
1226 : : true, true,
1227 : : false, false,
1228 : : prettyFlags, false);
1229 : : }
1230 : :
1231 : : /* Internal version, extensible with flags to control its behavior */
1232 : : char *
331 michael@paquier.xyz 1233 : 4 : pg_get_indexdef_columns_extended(Oid indexrelid, bits16 flags)
1234 : : {
1235 : 4 : bool pretty = ((flags & RULE_INDEXDEF_PRETTY) != 0);
1236 : 4 : bool keys_only = ((flags & RULE_INDEXDEF_KEYS_ONLY) != 0);
1237 : : int prettyFlags;
1238 : :
1239 [ + - ]: 4 : prettyFlags = GET_PRETTY_FLAGS(pretty);
1240 : :
1241 : 4 : return pg_get_indexdef_worker(indexrelid, 0, NULL,
1242 : : true, keys_only,
1243 : : false, false,
1244 : : prettyFlags, false);
1245 : : }
1246 : :
1247 : : /*
1248 : : * Internal workhorse to decompile an index definition.
1249 : : *
1250 : : * This is now used for exclusion constraints as well: if excludeOps is not
1251 : : * NULL then it points to an array of exclusion operator OIDs.
1252 : : */
1253 : : static char *
5370 tgl@sss.pgh.pa.us 1254 : 3940 : pg_get_indexdef_worker(Oid indexrelid, int colno,
1255 : : const Oid *excludeOps,
1256 : : bool attrsOnly, bool keysOnly,
1257 : : bool showTblSpc, bool inherits,
1258 : : int prettyFlags, bool missing_ok)
1259 : : {
1260 : : /* might want a separate isConstraint parameter later */
5242 1261 : 3940 : bool isConstraint = (excludeOps != NULL);
1262 : : HeapTuple ht_idx;
1263 : : HeapTuple ht_idxrel;
1264 : : HeapTuple ht_am;
1265 : : Form_pg_index idxrec;
1266 : : Form_pg_class idxrelrec;
1267 : : Form_pg_am amrec;
1268 : : IndexAmRoutine *amroutine;
1269 : : List *indexprs;
1270 : : ListCell *indexpr_item;
1271 : : List *context;
1272 : : Oid indrelid;
1273 : : int keyno;
1274 : : Datum indcollDatum;
1275 : : Datum indclassDatum;
1276 : : Datum indoptionDatum;
1277 : : oidvector *indcollation;
1278 : : oidvector *indclass;
1279 : : int2vector *indoption;
1280 : : StringInfoData buf;
1281 : : char *str;
1282 : : char *sep;
1283 : :
1284 : : /*
1285 : : * Fetch the pg_index tuple by the Oid of the index
1286 : : */
5173 rhaas@postgresql.org 1287 : 3940 : ht_idx = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexrelid));
9326 bruce@momjian.us 1288 [ + + ]: 3940 : if (!HeapTupleIsValid(ht_idx))
1289 : : {
2819 rhaas@postgresql.org 1290 [ + - ]: 3 : if (missing_ok)
1291 : 3 : return NULL;
7567 tgl@sss.pgh.pa.us 1292 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for index %u", indexrelid);
1293 : : }
9091 bruce@momjian.us 1294 :CBC 3937 : idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
1295 : :
8017 tgl@sss.pgh.pa.us 1296 : 3937 : indrelid = idxrec->indrelid;
1297 [ - + ]: 3937 : Assert(indexrelid == idxrec->indexrelid);
1298 : :
1299 : : /* Must get indcollation, indclass, and indoption the hard way */
386 dgustafsson@postgres 1300 : 3937 : indcollDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx,
1301 : : Anum_pg_index_indcollation);
4814 peter_e@gmx.net 1302 : 3937 : indcollation = (oidvector *) DatumGetPointer(indcollDatum);
1303 : :
386 dgustafsson@postgres 1304 : 3937 : indclassDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx,
1305 : : Anum_pg_index_indclass);
6956 tgl@sss.pgh.pa.us 1306 : 3937 : indclass = (oidvector *) DatumGetPointer(indclassDatum);
1307 : :
386 dgustafsson@postgres 1308 : 3937 : indoptionDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx,
1309 : : Anum_pg_index_indoption);
6305 tgl@sss.pgh.pa.us 1310 : 3937 : indoption = (int2vector *) DatumGetPointer(indoptionDatum);
1311 : :
1312 : : /*
1313 : : * Fetch the pg_class tuple of the index relation
1314 : : */
5173 rhaas@postgresql.org 1315 : 3937 : ht_idxrel = SearchSysCache1(RELOID, ObjectIdGetDatum(indexrelid));
9326 bruce@momjian.us 1316 [ - + ]: 3937 : if (!HeapTupleIsValid(ht_idxrel))
7567 tgl@sss.pgh.pa.us 1317 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", indexrelid);
9091 bruce@momjian.us 1318 :CBC 3937 : idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
1319 : :
1320 : : /*
1321 : : * Fetch the pg_am tuple of the index' access method
1322 : : */
5173 rhaas@postgresql.org 1323 : 3937 : ht_am = SearchSysCache1(AMOID, ObjectIdGetDatum(idxrelrec->relam));
8090 tgl@sss.pgh.pa.us 1324 [ - + ]: 3937 : if (!HeapTupleIsValid(ht_am))
7567 tgl@sss.pgh.pa.us 1325 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for access method %u",
1326 : : idxrelrec->relam);
8090 tgl@sss.pgh.pa.us 1327 :CBC 3937 : amrec = (Form_pg_am) GETSTRUCT(ht_am);
1328 : :
1329 : : /* Fetch the index AM's API struct */
3010 1330 : 3937 : amroutine = GetIndexAmRoutine(amrec->amhandler);
1331 : :
1332 : : /*
1333 : : * Get the index expressions, if any. (NOTE: we do not use the relcache
1334 : : * versions of the expressions and predicate, because we want to display
1335 : : * non-const-folded expressions.)
1336 : : */
2209 andrew@dunslane.net 1337 [ + + ]: 3937 : if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs, NULL))
1338 : : {
1339 : : Datum exprsDatum;
1340 : : char *exprsString;
1341 : :
386 dgustafsson@postgres 1342 : 281 : exprsDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx,
1343 : : Anum_pg_index_indexprs);
5864 tgl@sss.pgh.pa.us 1344 : 281 : exprsString = TextDatumGetCString(exprsDatum);
7627 1345 : 281 : indexprs = (List *) stringToNode(exprsString);
1346 : 281 : pfree(exprsString);
1347 : : }
1348 : : else
1349 : 3656 : indexprs = NIL;
1350 : :
7263 neilc@samurai.com 1351 : 3937 : indexpr_item = list_head(indexprs);
1352 : :
4912 tgl@sss.pgh.pa.us 1353 : 3937 : context = deparse_context_for(get_relation_name(indrelid), indrelid);
1354 : :
1355 : : /*
1356 : : * Start the index definition. Note that the index's name should never be
1357 : : * schema-qualified, but the indexed rel's name may be.
1358 : : */
8961 1359 : 3937 : initStringInfo(&buf);
1360 : :
5370 1361 [ + + ]: 3937 : if (!attrsOnly)
1362 : : {
5242 1363 [ + + ]: 3326 : if (!isConstraint)
2277 alvherre@alvh.no-ip. 1364 : 6548 : appendStringInfo(&buf, "CREATE %sINDEX %s ON %s%s USING %s (",
5242 tgl@sss.pgh.pa.us 1365 [ + + ]: 3274 : idxrec->indisunique ? "UNIQUE " : "",
1366 : 3274 : quote_identifier(NameStr(idxrelrec->relname)),
2277 alvherre@alvh.no-ip. 1367 [ + + ]: 3274 : idxrelrec->relkind == RELKIND_PARTITIONED_INDEX
1368 [ + + ]: 318 : && !inherits ? "ONLY " : "",
2239 tgl@sss.pgh.pa.us 1369 [ + + ]: 3274 : (prettyFlags & PRETTYFLAG_SCHEMA) ?
1370 : 703 : generate_relation_name(indrelid, NIL) :
1371 : 2571 : generate_qualified_relation_name(indrelid),
5242 1372 : 3274 : quote_identifier(NameStr(amrec->amname)));
1373 : : else /* currently, must be EXCLUDE constraint */
1374 : 52 : appendStringInfo(&buf, "EXCLUDE USING %s (",
1375 : 52 : quote_identifier(NameStr(amrec->amname)));
1376 : : }
1377 : :
1378 : : /*
1379 : : * Report the indexed attributes
1380 : : */
9326 bruce@momjian.us 1381 : 3937 : sep = "";
7627 tgl@sss.pgh.pa.us 1382 [ + + ]: 9896 : for (keyno = 0; keyno < idxrec->indnatts; keyno++)
1383 : : {
6956 1384 : 6008 : AttrNumber attnum = idxrec->indkey.values[keyno];
1385 : : Oid keycoltype;
1386 : : Oid keycolcollation;
1387 : :
1388 : : /*
1389 : : * Ignore non-key attributes if told to.
1390 : : */
2096 1391 [ + + + + ]: 6008 : if (keysOnly && keyno >= idxrec->indnkeyatts)
2199 teodor@sigaev.ru 1392 : 49 : break;
1393 : :
1394 : : /* Otherwise, print INCLUDE to divide key and non-key attrs. */
2096 tgl@sss.pgh.pa.us 1395 [ + + + + ]: 5959 : if (!colno && keyno == idxrec->indnkeyatts)
1396 : : {
2199 teodor@sigaev.ru 1397 : 125 : appendStringInfoString(&buf, ") INCLUDE (");
1398 : 125 : sep = "";
1399 : : }
1400 : :
7564 tgl@sss.pgh.pa.us 1401 [ + + ]: 5959 : if (!colno)
6924 neilc@samurai.com 1402 : 5644 : appendStringInfoString(&buf, sep);
9326 bruce@momjian.us 1403 : 5959 : sep = ", ";
1404 : :
7627 tgl@sss.pgh.pa.us 1405 [ + + ]: 5959 : if (attnum != 0)
1406 : : {
1407 : : /* Simple index column */
1408 : : char *attname;
1409 : : int32 keycoltypmod;
1410 : :
2253 alvherre@alvh.no-ip. 1411 : 5599 : attname = get_attname(indrelid, attnum, false);
7559 bruce@momjian.us 1412 [ + + + + ]: 5599 : if (!colno || colno == keyno + 1)
7379 neilc@samurai.com 1413 : 5515 : appendStringInfoString(&buf, quote_identifier(attname));
4768 tgl@sss.pgh.pa.us 1414 : 5599 : get_atttypetypmodcoll(indrelid, attnum,
1415 : : &keycoltype, &keycoltypmod,
1416 : : &keycolcollation);
1417 : : }
1418 : : else
1419 : : {
1420 : : /* expressional index */
1421 : : Node *indexkey;
1422 : :
7263 neilc@samurai.com 1423 [ - + ]: 360 : if (indexpr_item == NULL)
7627 tgl@sss.pgh.pa.us 1424 [ # # ]:UBC 0 : elog(ERROR, "too few entries in indexprs list");
7263 neilc@samurai.com 1425 :CBC 360 : indexkey = (Node *) lfirst(indexpr_item);
1735 tgl@sss.pgh.pa.us 1426 : 360 : indexpr_item = lnext(indexprs, indexpr_item);
1427 : : /* Deparse */
7564 1428 : 360 : str = deparse_expression_pretty(indexkey, context, false, false,
1429 : : prettyFlags, 0);
7559 bruce@momjian.us 1430 [ + + + + ]: 360 : if (!colno || colno == keyno + 1)
1431 : : {
1432 : : /* Need parens if it's not a bare function call */
2467 tgl@sss.pgh.pa.us 1433 [ + + ]: 354 : if (looks_like_function(indexkey))
7379 neilc@samurai.com 1434 : 26 : appendStringInfoString(&buf, str);
1435 : : else
7564 tgl@sss.pgh.pa.us 1436 : 328 : appendStringInfo(&buf, "(%s)", str);
1437 : : }
7627 1438 : 360 : keycoltype = exprType(indexkey);
4770 1439 : 360 : keycolcollation = exprCollation(indexkey);
1440 : : }
1441 : :
1442 : : /* Print additional decoration for (selected) key columns */
2096 1443 [ + + + + : 5959 : if (!attrsOnly && keyno < idxrec->indnkeyatts &&
- + ]
2096 tgl@sss.pgh.pa.us 1444 [ # # ]:UBC 0 : (!colno || colno == keyno + 1))
1445 : : {
1834 tgl@sss.pgh.pa.us 1446 :CBC 4929 : int16 opt = indoption->values[keyno];
1447 : 4929 : Oid indcoll = indcollation->values[keyno];
1476 akorotkov@postgresql 1448 : 4929 : Datum attoptions = get_attoptions(indexrelid, keyno + 1);
1449 : 4929 : bool has_options = attoptions != (Datum) 0;
1450 : :
1451 : : /* Add collation, if not default for column */
4770 tgl@sss.pgh.pa.us 1452 [ + + + + ]: 4929 : if (OidIsValid(indcoll) && indcoll != keycolcollation)
1453 : 45 : appendStringInfo(&buf, " COLLATE %s",
1454 : : generate_collation_name((indcoll)));
1455 : :
1456 : : /* Add the operator class name, if not default */
1476 akorotkov@postgresql 1457 [ + + ]: 4929 : get_opclass_name(indclass->values[keyno],
1458 : : has_options ? InvalidOid : keycoltype, &buf);
1459 : :
1460 [ + + ]: 4929 : if (has_options)
1461 : : {
1462 : 15 : appendStringInfoString(&buf, " (");
1463 : 15 : get_reloptions(&buf, attoptions);
1464 : 15 : appendStringInfoChar(&buf, ')');
1465 : : }
1466 : :
1467 : : /* Add options if relevant */
3010 tgl@sss.pgh.pa.us 1468 [ + + ]: 4929 : if (amroutine->amcanorder)
1469 : : {
1470 : : /* if it supports sort ordering, report DESC and NULLS opts */
5960 1471 [ - + ]: 3899 : if (opt & INDOPTION_DESC)
1472 : : {
3818 rhaas@postgresql.org 1473 :UBC 0 : appendStringInfoString(&buf, " DESC");
1474 : : /* NULLS FIRST is the default in this case */
5960 tgl@sss.pgh.pa.us 1475 [ # # ]: 0 : if (!(opt & INDOPTION_NULLS_FIRST))
3818 rhaas@postgresql.org 1476 : 0 : appendStringInfoString(&buf, " NULLS LAST");
1477 : : }
1478 : : else
1479 : : {
5960 tgl@sss.pgh.pa.us 1480 [ - + ]:CBC 3899 : if (opt & INDOPTION_NULLS_FIRST)
3818 rhaas@postgresql.org 1481 :UBC 0 : appendStringInfoString(&buf, " NULLS FIRST");
1482 : : }
1483 : : }
1484 : :
1485 : : /* Add the exclusion operator if relevant */
5242 tgl@sss.pgh.pa.us 1486 [ + + ]:CBC 4929 : if (excludeOps != NULL)
1487 : 62 : appendStringInfo(&buf, " WITH %s",
1488 : 62 : generate_operator_name(excludeOps[keyno],
1489 : : keycoltype,
1490 : : keycoltype));
1491 : : }
1492 : : }
1493 : :
5370 1494 [ + + ]: 3937 : if (!attrsOnly)
1495 : : {
7559 bruce@momjian.us 1496 : 3326 : appendStringInfoChar(&buf, ')');
1497 : :
801 peter@eisentraut.org 1498 [ + + ]: 3326 : if (idxrec->indnullsnotdistinct)
586 drowley@postgresql.o 1499 : 6 : appendStringInfoString(&buf, " NULLS NOT DISTINCT");
1500 : :
1501 : : /*
1502 : : * If it has options, append "WITH (options)"
1503 : : */
6495 tgl@sss.pgh.pa.us 1504 : 3326 : str = flatten_reloptions(indexrelid);
1505 [ + + ]: 3326 : if (str)
1506 : : {
1507 : 105 : appendStringInfo(&buf, " WITH (%s)", str);
1508 : 105 : pfree(str);
1509 : : }
1510 : :
1511 : : /*
1512 : : * Print tablespace, but only if requested
1513 : : */
6028 1514 [ + + ]: 3326 : if (showTblSpc)
1515 : : {
1516 : : Oid tblspc;
1517 : :
1518 : 106 : tblspc = get_rel_tablespace(indexrelid);
1816 alvherre@alvh.no-ip. 1519 [ + + ]: 106 : if (OidIsValid(tblspc))
1520 : : {
1521 [ - + ]: 27 : if (isConstraint)
1816 alvherre@alvh.no-ip. 1522 :UBC 0 : appendStringInfoString(&buf, " USING INDEX");
1816 alvherre@alvh.no-ip. 1523 :CBC 27 : appendStringInfo(&buf, " TABLESPACE %s",
1524 : 27 : quote_identifier(get_tablespace_name(tblspc)));
1525 : : }
1526 : : }
1527 : :
1528 : : /*
1529 : : * If it's a partial index, decompile and append the predicate
1530 : : */
2209 andrew@dunslane.net 1531 [ + + ]: 3326 : if (!heap_attisnull(ht_idx, Anum_pg_index_indpred, NULL))
1532 : : {
1533 : : Node *node;
1534 : : Datum predDatum;
1535 : : char *predString;
1536 : :
1537 : : /* Convert text string to node tree */
386 dgustafsson@postgres 1538 : 157 : predDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx,
1539 : : Anum_pg_index_indpred);
5864 tgl@sss.pgh.pa.us 1540 : 157 : predString = TextDatumGetCString(predDatum);
7564 1541 : 157 : node = (Node *) stringToNode(predString);
1542 : 157 : pfree(predString);
1543 : :
1544 : : /* Deparse */
1545 : 157 : str = deparse_expression_pretty(node, context, false, false,
1546 : : prettyFlags, 0);
5242 1547 [ + + ]: 157 : if (isConstraint)
1548 : 21 : appendStringInfo(&buf, " WHERE (%s)", str);
1549 : : else
1550 : 136 : appendStringInfo(&buf, " WHERE %s", str);
1551 : : }
1552 : : }
1553 : :
1554 : : /* Clean up */
8550 1555 : 3937 : ReleaseSysCache(ht_idx);
1556 : 3937 : ReleaseSysCache(ht_idxrel);
8090 1557 : 3937 : ReleaseSysCache(ht_am);
1558 : :
7284 1559 : 3937 : return buf.data;
1560 : : }
1561 : :
1562 : : /* ----------
1563 : : * pg_get_querydef
1564 : : *
1565 : : * Public entry point to deparse one query parsetree.
1566 : : * The pretty flags are determined by GET_PRETTY_FLAGS(pretty).
1567 : : *
1568 : : * The result is a palloc'd C string.
1569 : : * ----------
1570 : : */
1571 : : char *
748 tgl@sss.pgh.pa.us 1572 :UBC 0 : pg_get_querydef(Query *query, bool pretty)
1573 : : {
1574 : : StringInfoData buf;
1575 : : int prettyFlags;
1576 : :
1577 [ # # ]: 0 : prettyFlags = GET_PRETTY_FLAGS(pretty);
1578 : :
1579 : 0 : initStringInfo(&buf);
1580 : :
694 1581 : 0 : get_query_def(query, &buf, NIL, NULL, true,
1582 : : prettyFlags, WRAP_COLUMN_DEFAULT, 0);
1583 : :
748 1584 : 0 : return buf.data;
1585 : : }
1586 : :
1587 : : /*
1588 : : * pg_get_statisticsobjdef
1589 : : * Get the definition of an extended statistics object
1590 : : */
1591 : : Datum
2527 tgl@sss.pgh.pa.us 1592 :CBC 133 : pg_get_statisticsobjdef(PG_FUNCTION_ARGS)
1593 : : {
2578 alvherre@alvh.no-ip. 1594 : 133 : Oid statextid = PG_GETARG_OID(0);
1595 : : char *res;
1596 : :
1115 tomas.vondra@postgre 1597 : 133 : res = pg_get_statisticsobj_worker(statextid, false, true);
1598 : :
1599 [ + + ]: 133 : if (res == NULL)
1600 : 3 : PG_RETURN_NULL();
1601 : :
1602 : 130 : PG_RETURN_TEXT_P(string_to_text(res));
1603 : : }
1604 : :
1605 : : /*
1606 : : * Internal version for use by ALTER TABLE.
1607 : : * Includes a tablespace clause in the result.
1608 : : * Returns a palloc'd C string; no pretty-printing.
1609 : : */
1610 : : char *
1611 : 7 : pg_get_statisticsobjdef_string(Oid statextid)
1612 : : {
1613 : 7 : return pg_get_statisticsobj_worker(statextid, false, false);
1614 : : }
1615 : :
1616 : : /*
1617 : : * pg_get_statisticsobjdef_columns
1618 : : * Get columns and expressions for an extended statistics object
1619 : : */
1620 : : Datum
1621 : 198 : pg_get_statisticsobjdef_columns(PG_FUNCTION_ARGS)
1622 : : {
1623 : 198 : Oid statextid = PG_GETARG_OID(0);
1624 : : char *res;
1625 : :
1626 : 198 : res = pg_get_statisticsobj_worker(statextid, true, true);
1627 : :
2578 alvherre@alvh.no-ip. 1628 [ - + ]: 198 : if (res == NULL)
2578 alvherre@alvh.no-ip. 1629 :UBC 0 : PG_RETURN_NULL();
1630 : :
2578 alvherre@alvh.no-ip. 1631 :CBC 198 : PG_RETURN_TEXT_P(string_to_text(res));
1632 : : }
1633 : :
1634 : : /*
1635 : : * Internal workhorse to decompile an extended statistics object.
1636 : : */
1637 : : static char *
1115 tomas.vondra@postgre 1638 : 338 : pg_get_statisticsobj_worker(Oid statextid, bool columns_only, bool missing_ok)
1639 : : {
1640 : : Form_pg_statistic_ext statextrec;
1641 : : HeapTuple statexttup;
1642 : : StringInfoData buf;
1643 : : int colno;
1644 : : char *nsp;
1645 : : ArrayType *arr;
1646 : : char *enabled;
1647 : : Datum datum;
1648 : : bool ndistinct_enabled;
1649 : : bool dependencies_enabled;
1650 : : bool mcv_enabled;
1651 : : int i;
1652 : : List *context;
1653 : : ListCell *lc;
1654 : 338 : List *exprs = NIL;
1655 : : bool has_exprs;
1656 : : int ncolumns;
1657 : :
2578 alvherre@alvh.no-ip. 1658 : 338 : statexttup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statextid));
1659 : :
1660 [ + + ]: 338 : if (!HeapTupleIsValid(statexttup))
1661 : : {
1662 [ + - ]: 3 : if (missing_ok)
1663 : 3 : return NULL;
2527 tgl@sss.pgh.pa.us 1664 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for statistics object %u", statextid);
1665 : : }
1666 : :
1667 : : /* has the statistics expressions? */
1115 tomas.vondra@postgre 1668 :CBC 335 : has_exprs = !heap_attisnull(statexttup, Anum_pg_statistic_ext_stxexprs, NULL);
1669 : :
1670 : 335 : statextrec = (Form_pg_statistic_ext) GETSTRUCT(statexttup);
1671 : :
1672 : : /*
1673 : : * Get the statistics expressions, if any. (NOTE: we do not use the
1674 : : * relcache versions of the expressions, because we want to display
1675 : : * non-const-folded expressions.)
1676 : : */
1677 [ + + ]: 335 : if (has_exprs)
1678 : : {
1679 : : Datum exprsDatum;
1680 : : char *exprsString;
1681 : :
386 dgustafsson@postgres 1682 : 71 : exprsDatum = SysCacheGetAttrNotNull(STATEXTOID, statexttup,
1683 : : Anum_pg_statistic_ext_stxexprs);
1115 tomas.vondra@postgre 1684 : 71 : exprsString = TextDatumGetCString(exprsDatum);
1685 : 71 : exprs = (List *) stringToNode(exprsString);
1686 : 71 : pfree(exprsString);
1687 : : }
1688 : : else
1689 : 264 : exprs = NIL;
1690 : :
1691 : : /* count the number of columns (attributes and expressions) */
1692 : 335 : ncolumns = statextrec->stxkeys.dim1 + list_length(exprs);
1693 : :
1694 : 335 : initStringInfo(&buf);
1695 : :
1696 [ + + ]: 335 : if (!columns_only)
1697 : : {
992 tgl@sss.pgh.pa.us 1698 : 137 : nsp = get_namespace_name_or_temp(statextrec->stxnamespace);
1115 tomas.vondra@postgre 1699 : 137 : appendStringInfo(&buf, "CREATE STATISTICS %s",
1700 : : quote_qualified_identifier(nsp,
1701 : 137 : NameStr(statextrec->stxname)));
1702 : :
1703 : : /*
1704 : : * Decode the stxkind column so that we know which stats types to
1705 : : * print.
1706 : : */
386 dgustafsson@postgres 1707 : 137 : datum = SysCacheGetAttrNotNull(STATEXTOID, statexttup,
1708 : : Anum_pg_statistic_ext_stxkind);
1115 tomas.vondra@postgre 1709 : 137 : arr = DatumGetArrayTypeP(datum);
1710 [ + - ]: 137 : if (ARR_NDIM(arr) != 1 ||
1711 [ + - ]: 137 : ARR_HASNULL(arr) ||
1712 [ - + ]: 137 : ARR_ELEMTYPE(arr) != CHAROID)
1115 tomas.vondra@postgre 1713 [ # # ]:UBC 0 : elog(ERROR, "stxkind is not a 1-D char array");
1115 tomas.vondra@postgre 1714 [ - + ]:CBC 137 : enabled = (char *) ARR_DATA_PTR(arr);
1715 : :
1716 : 137 : ndistinct_enabled = false;
1717 : 137 : dependencies_enabled = false;
1718 : 137 : mcv_enabled = false;
1719 : :
1720 [ + + ]: 355 : for (i = 0; i < ARR_DIMS(arr)[0]; i++)
1721 : : {
1722 [ + + ]: 218 : if (enabled[i] == STATS_EXT_NDISTINCT)
1723 : 74 : ndistinct_enabled = true;
1724 [ + + ]: 144 : else if (enabled[i] == STATS_EXT_DEPENDENCIES)
1725 : 47 : dependencies_enabled = true;
1726 [ + + ]: 97 : else if (enabled[i] == STATS_EXT_MCV)
1727 : 56 : mcv_enabled = true;
1728 : :
1729 : : /* ignore STATS_EXT_EXPRESSIONS (it's built automatically) */
1730 : : }
1731 : :
1732 : : /*
1733 : : * If any option is disabled, then we'll need to append the types
1734 : : * clause to show which options are enabled. We omit the types clause
1735 : : * on purpose when all options are enabled, so a pg_dump/pg_restore
1736 : : * will create all statistics types on a newer postgres version, if
1737 : : * the statistics had all options enabled on the original version.
1738 : : *
1739 : : * But if the statistics is defined on just a single column, it has to
1740 : : * be an expression statistics. In that case we don't need to specify
1741 : : * kinds.
1742 : : */
1743 [ + + + + : 137 : if ((!ndistinct_enabled || !dependencies_enabled || !mcv_enabled) &&
- + + + ]
1744 : : (ncolumns > 1))
1745 : : {
1746 : 63 : bool gotone = false;
1747 : :
1748 : 63 : appendStringInfoString(&buf, " (");
1749 : :
1750 [ + + ]: 63 : if (ndistinct_enabled)
1751 : : {
1752 : 36 : appendStringInfoString(&buf, "ndistinct");
1753 : 36 : gotone = true;
1754 : : }
1755 : :
1756 [ + + ]: 63 : if (dependencies_enabled)
1757 : : {
1758 [ - + ]: 9 : appendStringInfo(&buf, "%sdependencies", gotone ? ", " : "");
1759 : 9 : gotone = true;
1760 : : }
1761 : :
1762 [ + + ]: 63 : if (mcv_enabled)
1763 [ - + ]: 18 : appendStringInfo(&buf, "%smcv", gotone ? ", " : "");
1764 : :
1765 : 63 : appendStringInfoChar(&buf, ')');
1766 : : }
1767 : :
1768 : 137 : appendStringInfoString(&buf, " ON ");
1769 : : }
1770 : :
1771 : : /* decode simple column references */
2554 alvherre@alvh.no-ip. 1772 [ + + ]: 953 : for (colno = 0; colno < statextrec->stxkeys.dim1; colno++)
1773 : : {
1774 : 618 : AttrNumber attnum = statextrec->stxkeys.values[colno];
1775 : : char *attname;
1776 : :
2578 1777 [ + + ]: 618 : if (colno > 0)
1778 : 348 : appendStringInfoString(&buf, ", ");
1779 : :
2253 1780 : 618 : attname = get_attname(statextrec->stxrelid, attnum, false);
1781 : :
2578 1782 : 618 : appendStringInfoString(&buf, quote_identifier(attname));
1783 : : }
1784 : :
1115 tomas.vondra@postgre 1785 : 335 : context = deparse_context_for(get_relation_name(statextrec->stxrelid),
1786 : : statextrec->stxrelid);
1787 : :
1788 [ + + + + : 449 : foreach(lc, exprs)
+ + ]
1789 : : {
1790 : 114 : Node *expr = (Node *) lfirst(lc);
1791 : : char *str;
956 1792 : 114 : int prettyFlags = PRETTYFLAG_PAREN;
1793 : :
1115 1794 : 114 : str = deparse_expression_pretty(expr, context, false, false,
1795 : : prettyFlags, 0);
1796 : :
1797 [ + + ]: 114 : if (colno > 0)
1798 : 49 : appendStringInfoString(&buf, ", ");
1799 : :
1800 : : /* Need parens if it's not a bare function call */
1801 [ + + ]: 114 : if (looks_like_function(expr))
1802 : 17 : appendStringInfoString(&buf, str);
1803 : : else
1804 : 97 : appendStringInfo(&buf, "(%s)", str);
1805 : :
1806 : 114 : colno++;
1807 : : }
1808 : :
1809 [ + + ]: 335 : if (!columns_only)
1810 : 137 : appendStringInfo(&buf, " FROM %s",
1811 : : generate_relation_name(statextrec->stxrelid, NIL));
1812 : :
2578 alvherre@alvh.no-ip. 1813 : 335 : ReleaseSysCache(statexttup);
1814 : :
1815 : 335 : return buf.data;
1816 : : }
1817 : :
1818 : : /*
1819 : : * Generate text array of expressions for statistics object.
1820 : : */
1821 : : Datum
1115 tomas.vondra@postgre 1822 :UBC 0 : pg_get_statisticsobjdef_expressions(PG_FUNCTION_ARGS)
1823 : : {
1824 : 0 : Oid statextid = PG_GETARG_OID(0);
1825 : : Form_pg_statistic_ext statextrec;
1826 : : HeapTuple statexttup;
1827 : : Datum datum;
1828 : : List *context;
1829 : : ListCell *lc;
1830 : 0 : List *exprs = NIL;
1831 : : bool has_exprs;
1832 : : char *tmp;
1833 : 0 : ArrayBuildState *astate = NULL;
1834 : :
1835 : 0 : statexttup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statextid));
1836 : :
1837 [ # # ]: 0 : if (!HeapTupleIsValid(statexttup))
1073 1838 : 0 : PG_RETURN_NULL();
1839 : :
1840 : : /* Does the stats object have expressions? */
1115 1841 : 0 : has_exprs = !heap_attisnull(statexttup, Anum_pg_statistic_ext_stxexprs, NULL);
1842 : :
1843 : : /* no expressions? we're done */
1844 [ # # ]: 0 : if (!has_exprs)
1845 : : {
1846 : 0 : ReleaseSysCache(statexttup);
1847 : 0 : PG_RETURN_NULL();
1848 : : }
1849 : :
1850 : 0 : statextrec = (Form_pg_statistic_ext) GETSTRUCT(statexttup);
1851 : :
1852 : : /*
1853 : : * Get the statistics expressions, and deparse them into text values.
1854 : : */
386 dgustafsson@postgres 1855 : 0 : datum = SysCacheGetAttrNotNull(STATEXTOID, statexttup,
1856 : : Anum_pg_statistic_ext_stxexprs);
1115 tomas.vondra@postgre 1857 : 0 : tmp = TextDatumGetCString(datum);
1858 : 0 : exprs = (List *) stringToNode(tmp);
1859 : 0 : pfree(tmp);
1860 : :
1861 : 0 : context = deparse_context_for(get_relation_name(statextrec->stxrelid),
1862 : : statextrec->stxrelid);
1863 : :
1864 [ # # # # : 0 : foreach(lc, exprs)
# # ]
1865 : : {
1866 : 0 : Node *expr = (Node *) lfirst(lc);
1867 : : char *str;
1868 : 0 : int prettyFlags = PRETTYFLAG_INDENT;
1869 : :
1870 : 0 : str = deparse_expression_pretty(expr, context, false, false,
1871 : : prettyFlags, 0);
1872 : :
1873 : 0 : astate = accumArrayResult(astate,
1874 : 0 : PointerGetDatum(cstring_to_text(str)),
1875 : : false,
1876 : : TEXTOID,
1877 : : CurrentMemoryContext);
1878 : : }
1879 : :
1880 : 0 : ReleaseSysCache(statexttup);
1881 : :
1882 : 0 : PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext));
1883 : : }
1884 : :
1885 : : /*
1886 : : * pg_get_partkeydef
1887 : : *
1888 : : * Returns the partition key specification, ie, the following:
1889 : : *
1890 : : * { RANGE | LIST | HASH } (column opt_collation opt_opclass [, ...])
1891 : : */
1892 : : Datum
2685 rhaas@postgresql.org 1893 :CBC 641 : pg_get_partkeydef(PG_FUNCTION_ARGS)
1894 : : {
1895 : 641 : Oid relid = PG_GETARG_OID(0);
1896 : : char *res;
1897 : :
2545 sfrost@snowman.net 1898 : 641 : res = pg_get_partkeydef_worker(relid, PRETTYFLAG_INDENT, false, true);
1899 : :
1900 [ + + ]: 641 : if (res == NULL)
1901 : 3 : PG_RETURN_NULL();
1902 : :
1903 : 638 : PG_RETURN_TEXT_P(string_to_text(res));
1904 : : }
1905 : :
1906 : : /* Internal version that just reports the column definitions */
1907 : : char *
2599 rhaas@postgresql.org 1908 : 71 : pg_get_partkeydef_columns(Oid relid, bool pretty)
1909 : : {
1910 : : int prettyFlags;
1911 : :
748 tgl@sss.pgh.pa.us 1912 [ + - ]: 71 : prettyFlags = GET_PRETTY_FLAGS(pretty);
1913 : :
2545 sfrost@snowman.net 1914 : 71 : return pg_get_partkeydef_worker(relid, prettyFlags, true, false);
1915 : : }
1916 : :
1917 : : /*
1918 : : * Internal workhorse to decompile a partition key definition.
1919 : : */
1920 : : static char *
2599 rhaas@postgresql.org 1921 : 712 : pg_get_partkeydef_worker(Oid relid, int prettyFlags,
1922 : : bool attrsOnly, bool missing_ok)
1923 : : {
1924 : : Form_pg_partitioned_table form;
1925 : : HeapTuple tuple;
1926 : : oidvector *partclass;
1927 : : oidvector *partcollation;
1928 : : List *partexprs;
1929 : : ListCell *partexpr_item;
1930 : : List *context;
1931 : : Datum datum;
1932 : : StringInfoData buf;
1933 : : int keyno;
1934 : : char *str;
1935 : : char *sep;
1936 : :
2685 1937 : 712 : tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
1938 [ + + ]: 712 : if (!HeapTupleIsValid(tuple))
1939 : : {
2545 sfrost@snowman.net 1940 [ + - ]: 3 : if (missing_ok)
1941 : 3 : return NULL;
2685 rhaas@postgresql.org 1942 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for partition key of %u", relid);
1943 : : }
1944 : :
2685 rhaas@postgresql.org 1945 :CBC 709 : form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
1946 : :
1947 [ - + ]: 709 : Assert(form->partrelid == relid);
1948 : :
1949 : : /* Must get partclass and partcollation the hard way */
386 dgustafsson@postgres 1950 : 709 : datum = SysCacheGetAttrNotNull(PARTRELID, tuple,
1951 : : Anum_pg_partitioned_table_partclass);
2685 rhaas@postgresql.org 1952 : 709 : partclass = (oidvector *) DatumGetPointer(datum);
1953 : :
386 dgustafsson@postgres 1954 : 709 : datum = SysCacheGetAttrNotNull(PARTRELID, tuple,
1955 : : Anum_pg_partitioned_table_partcollation);
2685 rhaas@postgresql.org 1956 : 709 : partcollation = (oidvector *) DatumGetPointer(datum);
1957 : :
1958 : :
1959 : : /*
1960 : : * Get the expressions, if any. (NOTE: we do not use the relcache
1961 : : * versions of the expressions, because we want to display
1962 : : * non-const-folded expressions.)
1963 : : */
2209 andrew@dunslane.net 1964 [ + + ]: 709 : if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs, NULL))
1965 : : {
1966 : : Datum exprsDatum;
1967 : : char *exprsString;
1968 : :
386 dgustafsson@postgres 1969 : 73 : exprsDatum = SysCacheGetAttrNotNull(PARTRELID, tuple,
1970 : : Anum_pg_partitioned_table_partexprs);
2685 rhaas@postgresql.org 1971 : 73 : exprsString = TextDatumGetCString(exprsDatum);
1972 : 73 : partexprs = (List *) stringToNode(exprsString);
1973 : :
1974 [ - + ]: 73 : if (!IsA(partexprs, List))
2685 rhaas@postgresql.org 1975 [ # # ]:UBC 0 : elog(ERROR, "unexpected node type found in partexprs: %d",
1976 : : (int) nodeTag(partexprs));
1977 : :
2685 rhaas@postgresql.org 1978 :CBC 73 : pfree(exprsString);
1979 : : }
1980 : : else
1981 : 636 : partexprs = NIL;
1982 : :
1983 : 709 : partexpr_item = list_head(partexprs);
1984 : 709 : context = deparse_context_for(get_relation_name(relid), relid);
1985 : :
1986 : 709 : initStringInfo(&buf);
1987 : :
1988 [ + + + - ]: 709 : switch (form->partstrat)
1989 : : {
2348 1990 : 27 : case PARTITION_STRATEGY_HASH:
1991 [ + - ]: 27 : if (!attrsOnly)
1746 drowley@postgresql.o 1992 : 27 : appendStringInfoString(&buf, "HASH");
2348 rhaas@postgresql.org 1993 : 27 : break;
2685 1994 : 256 : case PARTITION_STRATEGY_LIST:
2599 1995 [ + + ]: 256 : if (!attrsOnly)
2434 peter_e@gmx.net 1996 : 236 : appendStringInfoString(&buf, "LIST");
2685 rhaas@postgresql.org 1997 : 256 : break;
1998 : 426 : case PARTITION_STRATEGY_RANGE:
2599 1999 [ + + ]: 426 : if (!attrsOnly)
2434 peter_e@gmx.net 2000 : 375 : appendStringInfoString(&buf, "RANGE");
2685 rhaas@postgresql.org 2001 : 426 : break;
2685 rhaas@postgresql.org 2002 :UBC 0 : default:
2003 [ # # ]: 0 : elog(ERROR, "unexpected partition strategy: %d",
2004 : : (int) form->partstrat);
2005 : : }
2006 : :
2599 rhaas@postgresql.org 2007 [ + + ]:CBC 709 : if (!attrsOnly)
2434 peter_e@gmx.net 2008 : 638 : appendStringInfoString(&buf, " (");
2685 rhaas@postgresql.org 2009 : 709 : sep = "";
2010 [ + + ]: 1494 : for (keyno = 0; keyno < form->partnatts; keyno++)
2011 : : {
2012 : 785 : AttrNumber attnum = form->partattrs.values[keyno];
2013 : : Oid keycoltype;
2014 : : Oid keycolcollation;
2015 : : Oid partcoll;
2016 : :
2017 : 785 : appendStringInfoString(&buf, sep);
2018 : 785 : sep = ", ";
2019 [ + + ]: 785 : if (attnum != 0)
2020 : : {
2021 : : /* Simple attribute reference */
2022 : : char *attname;
2023 : : int32 keycoltypmod;
2024 : :
2253 alvherre@alvh.no-ip. 2025 : 706 : attname = get_attname(relid, attnum, false);
2685 rhaas@postgresql.org 2026 : 706 : appendStringInfoString(&buf, quote_identifier(attname));
2027 : 706 : get_atttypetypmodcoll(relid, attnum,
2028 : : &keycoltype, &keycoltypmod,
2029 : : &keycolcollation);
2030 : : }
2031 : : else
2032 : : {
2033 : : /* Expression */
2034 : : Node *partkey;
2035 : :
2036 [ - + ]: 79 : if (partexpr_item == NULL)
2685 rhaas@postgresql.org 2037 [ # # ]:UBC 0 : elog(ERROR, "too few entries in partexprs list");
2685 rhaas@postgresql.org 2038 :CBC 79 : partkey = (Node *) lfirst(partexpr_item);
1735 tgl@sss.pgh.pa.us 2039 : 79 : partexpr_item = lnext(partexprs, partexpr_item);
2040 : :
2041 : : /* Deparse */
2685 rhaas@postgresql.org 2042 : 79 : str = deparse_expression_pretty(partkey, context, false, false,
2043 : : prettyFlags, 0);
2044 : : /* Need parens if it's not a bare function call */
2467 tgl@sss.pgh.pa.us 2045 [ + + ]: 79 : if (looks_like_function(partkey))
2046 : 28 : appendStringInfoString(&buf, str);
2047 : : else
2048 : 51 : appendStringInfo(&buf, "(%s)", str);
2049 : :
2685 rhaas@postgresql.org 2050 : 79 : keycoltype = exprType(partkey);
2051 : 79 : keycolcollation = exprCollation(partkey);
2052 : : }
2053 : :
2054 : : /* Add collation, if not default for column */
2055 : 785 : partcoll = partcollation->values[keyno];
2599 2056 [ + + + + : 785 : if (!attrsOnly && OidIsValid(partcoll) && partcoll != keycolcollation)
+ + ]
2685 2057 : 3 : appendStringInfo(&buf, " COLLATE %s",
2058 : : generate_collation_name((partcoll)));
2059 : :
2060 : : /* Add the operator class name, if not default */
2599 2061 [ + + ]: 785 : if (!attrsOnly)
2062 : 687 : get_opclass_name(partclass->values[keyno], keycoltype, &buf);
2063 : : }
2064 : :
2065 [ + + ]: 709 : if (!attrsOnly)
2066 : 638 : appendStringInfoChar(&buf, ')');
2067 : :
2068 : : /* Clean up */
2685 2069 : 709 : ReleaseSysCache(tuple);
2070 : :
2071 : 709 : return buf.data;
2072 : : }
2073 : :
2074 : : /*
2075 : : * pg_get_partition_constraintdef
2076 : : *
2077 : : * Returns partition constraint expression as a string for the input relation
2078 : : */
2079 : : Datum
2528 2080 : 78 : pg_get_partition_constraintdef(PG_FUNCTION_ARGS)
2081 : : {
2524 bruce@momjian.us 2082 : 78 : Oid relationId = PG_GETARG_OID(0);
2083 : : Expr *constr_expr;
2084 : : int prettyFlags;
2085 : : List *context;
2086 : : char *consrc;
2087 : :
2528 rhaas@postgresql.org 2088 : 78 : constr_expr = get_partition_qual_relid(relationId);
2089 : :
2090 : : /* Quick exit if no partition constraint */
2091 [ + + ]: 78 : if (constr_expr == NULL)
2092 : 9 : PG_RETURN_NULL();
2093 : :
2094 : : /*
2095 : : * Deparse and return the constraint expression.
2096 : : */
2097 : 69 : prettyFlags = PRETTYFLAG_INDENT;
2098 : 69 : context = deparse_context_for(get_relation_name(relationId), relationId);
2099 : 69 : consrc = deparse_expression_pretty((Node *) constr_expr, context, false,
2100 : : false, prettyFlags, 0);
2101 : :
2102 : 69 : PG_RETURN_TEXT_P(string_to_text(consrc));
2103 : : }
2104 : :
2105 : : /*
2106 : : * pg_get_partconstrdef_string
2107 : : *
2108 : : * Returns the partition constraint as a C-string for the input relation, with
2109 : : * the given alias. No pretty-printing.
2110 : : */
2111 : : char *
1838 alvherre@alvh.no-ip. 2112 : 43 : pg_get_partconstrdef_string(Oid partitionId, char *aliasname)
2113 : : {
2114 : : Expr *constr_expr;
2115 : : List *context;
2116 : :
2117 : 43 : constr_expr = get_partition_qual_relid(partitionId);
2118 : 43 : context = deparse_context_for(aliasname, partitionId);
2119 : :
2120 : 43 : return deparse_expression((Node *) constr_expr, context, true, false);
2121 : : }
2122 : :
2123 : : /*
2124 : : * pg_get_constraintdef
2125 : : *
2126 : : * Returns the definition for the constraint, ie, everything that needs to
2127 : : * appear after "ALTER TABLE ... ADD CONSTRAINT <constraintname>".
2128 : : */
2129 : : Datum
7912 tgl@sss.pgh.pa.us 2130 : 916 : pg_get_constraintdef(PG_FUNCTION_ARGS)
2131 : : {
7893 bruce@momjian.us 2132 : 916 : Oid constraintId = PG_GETARG_OID(0);
2133 : : int prettyFlags;
2134 : : char *res;
2135 : :
4088 tgl@sss.pgh.pa.us 2136 : 916 : prettyFlags = PRETTYFLAG_INDENT;
2137 : :
2819 rhaas@postgresql.org 2138 : 916 : res = pg_get_constraintdef_worker(constraintId, false, prettyFlags, true);
2139 : :
2140 [ + + ]: 916 : if (res == NULL)
2141 : 3 : PG_RETURN_NULL();
2142 : :
2143 : 913 : PG_RETURN_TEXT_P(string_to_text(res));
2144 : : }
2145 : :
2146 : : Datum
7564 tgl@sss.pgh.pa.us 2147 : 1944 : pg_get_constraintdef_ext(PG_FUNCTION_ARGS)
2148 : : {
2149 : 1944 : Oid constraintId = PG_GETARG_OID(0);
2150 : 1944 : bool pretty = PG_GETARG_BOOL(1);
2151 : : int prettyFlags;
2152 : : char *res;
2153 : :
748 2154 [ + + ]: 1944 : prettyFlags = GET_PRETTY_FLAGS(pretty);
2155 : :
2819 rhaas@postgresql.org 2156 : 1944 : res = pg_get_constraintdef_worker(constraintId, false, prettyFlags, true);
2157 : :
2158 [ - + ]: 1944 : if (res == NULL)
2819 rhaas@postgresql.org 2159 :UBC 0 : PG_RETURN_NULL();
2160 : :
2819 rhaas@postgresql.org 2161 :CBC 1944 : PG_RETURN_TEXT_P(string_to_text(res));
2162 : : }
2163 : :
2164 : : /*
2165 : : * Internal version that returns a full ALTER TABLE ... ADD CONSTRAINT command
2166 : : */
2167 : : char *
3068 tgl@sss.pgh.pa.us 2168 : 223 : pg_get_constraintdef_command(Oid constraintId)
2169 : : {
2819 rhaas@postgresql.org 2170 : 223 : return pg_get_constraintdef_worker(constraintId, true, 0, false);
2171 : : }
2172 : :
2173 : : /*
2174 : : * As of 9.4, we now use an MVCC snapshot for this.
2175 : : */
2176 : : static char *
7284 tgl@sss.pgh.pa.us 2177 : 3083 : pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
2178 : : int prettyFlags, bool missing_ok)
2179 : : {
2180 : : HeapTuple tup;
2181 : : Form_pg_constraint conForm;
2182 : : StringInfoData buf;
2183 : : SysScanDesc scandesc;
2184 : : ScanKeyData scankey[1];
3631 bruce@momjian.us 2185 : 3083 : Snapshot snapshot = RegisterSnapshot(GetTransactionSnapshot());
1910 andres@anarazel.de 2186 : 3083 : Relation relation = table_open(ConstraintRelationId, AccessShareLock);
2187 : :
3661 simon@2ndQuadrant.co 2188 : 3083 : ScanKeyInit(&scankey[0],
2189 : : Anum_pg_constraint_oid,
2190 : : BTEqualStrategyNumber, F_OIDEQ,
2191 : : ObjectIdGetDatum(constraintId));
2192 : :
2193 : 3083 : scandesc = systable_beginscan(relation,
2194 : : ConstraintOidIndexId,
2195 : : true,
2196 : : snapshot,
2197 : : 1,
2198 : : scankey);
2199 : :
2200 : : /*
2201 : : * We later use the tuple with SysCacheGetAttr() as if we had obtained it
2202 : : * via SearchSysCache, which works fine.
2203 : : */
2204 : 3083 : tup = systable_getnext(scandesc);
2205 : :
2206 : 3083 : UnregisterSnapshot(snapshot);
2207 : :
2819 rhaas@postgresql.org 2208 [ + + ]: 3083 : if (!HeapTupleIsValid(tup))
2209 : : {
2210 [ + - ]: 3 : if (missing_ok)
2211 : : {
2212 : 3 : systable_endscan(scandesc);
1910 andres@anarazel.de 2213 : 3 : table_close(relation, AccessShareLock);
2819 rhaas@postgresql.org 2214 : 3 : return NULL;
2215 : : }
2506 tgl@sss.pgh.pa.us 2216 [ # # ]:UBC 0 : elog(ERROR, "could not find tuple for constraint %u", constraintId);
2217 : : }
2218 : :
7912 tgl@sss.pgh.pa.us 2219 :CBC 3080 : conForm = (Form_pg_constraint) GETSTRUCT(tup);
2220 : :
2221 : 3080 : initStringInfo(&buf);
2222 : :
3068 2223 [ + + ]: 3080 : if (fullCommand)
2224 : : {
2356 2225 [ + + ]: 223 : if (OidIsValid(conForm->conrelid))
2226 : : {
2227 : : /*
2228 : : * Currently, callers want ALTER TABLE (without ONLY) for CHECK
2229 : : * constraints, and other types of constraints don't inherit
2230 : : * anyway so it doesn't matter whether we say ONLY or not. Someday
2231 : : * we might need to let callers specify whether to put ONLY in the
2232 : : * command.
2233 : : */
2234 : 216 : appendStringInfo(&buf, "ALTER TABLE %s ADD CONSTRAINT %s ",
2235 : : generate_qualified_relation_name(conForm->conrelid),
2236 : 216 : quote_identifier(NameStr(conForm->conname)));
2237 : : }
2238 : : else
2239 : : {
2240 : : /* Must be a domain constraint */
2241 [ - + ]: 7 : Assert(OidIsValid(conForm->contypid));
2242 : 7 : appendStringInfo(&buf, "ALTER DOMAIN %s ADD CONSTRAINT %s ",
2243 : : generate_qualified_type_name(conForm->contypid),
2244 : 7 : quote_identifier(NameStr(conForm->conname)));
2245 : : }
2246 : : }
2247 : :
7912 2248 [ + + + + : 3080 : switch (conForm->contype)
- + - ]
2249 : : {
2250 : 372 : case CONSTRAINT_FOREIGN:
2251 : : {
2252 : : Datum val;
2253 : : bool isnull;
2254 : : const char *string;
2255 : :
2256 : : /* Start off the constraint definition */
3818 rhaas@postgresql.org 2257 : 372 : appendStringInfoString(&buf, "FOREIGN KEY (");
2258 : :
2259 : : /* Fetch and build referencing-column list */
386 dgustafsson@postgres 2260 : 372 : val = SysCacheGetAttrNotNull(CONSTROID, tup,
2261 : : Anum_pg_constraint_conkey);
2262 : :
2263 : : /* If it is a temporal foreign key then it uses PERIOD. */
21 peter@eisentraut.org 2264 :GNC 372 : decompile_column_index_array(val, conForm->conrelid, conForm->conperiod, &buf);
2265 : :
2266 : : /* add foreign relation name */
7893 bruce@momjian.us 2267 :CBC 372 : appendStringInfo(&buf, ") REFERENCES %s(",
2268 : : generate_relation_name(conForm->confrelid,
2269 : : NIL));
2270 : :
2271 : : /* Fetch and build referenced-column list */
386 dgustafsson@postgres 2272 : 372 : val = SysCacheGetAttrNotNull(CONSTROID, tup,
2273 : : Anum_pg_constraint_confkey);
2274 : :
21 peter@eisentraut.org 2275 :GNC 372 : decompile_column_index_array(val, conForm->confrelid, conForm->conperiod, &buf);
2276 : :
3818 rhaas@postgresql.org 2277 :CBC 372 : appendStringInfoChar(&buf, ')');
2278 : :
2279 : : /* Add match type */
7893 bruce@momjian.us 2280 [ + - + - ]: 372 : switch (conForm->confmatchtype)
2281 : : {
2282 : 17 : case FKCONSTR_MATCH_FULL:
2283 : 17 : string = " MATCH FULL";
2284 : 17 : break;
7893 bruce@momjian.us 2285 :UBC 0 : case FKCONSTR_MATCH_PARTIAL:
2286 : 0 : string = " MATCH PARTIAL";
2287 : 0 : break;
4319 tgl@sss.pgh.pa.us 2288 :CBC 355 : case FKCONSTR_MATCH_SIMPLE:
7893 bruce@momjian.us 2289 : 355 : string = "";
2290 : 355 : break;
7893 bruce@momjian.us 2291 :UBC 0 : default:
7567 tgl@sss.pgh.pa.us 2292 [ # # ]: 0 : elog(ERROR, "unrecognized confmatchtype: %d",
2293 : : conForm->confmatchtype);
2294 : : string = ""; /* keep compiler quiet */
2295 : : break;
2296 : : }
7379 neilc@samurai.com 2297 :CBC 372 : appendStringInfoString(&buf, string);
2298 : :
2299 : : /* Add ON UPDATE and ON DELETE clauses, if needed */
7893 bruce@momjian.us 2300 [ + - + + : 372 : switch (conForm->confupdtype)
- - ]
2301 : : {
2302 : 310 : case FKCONSTR_ACTION_NOACTION:
7559 2303 : 310 : string = NULL; /* suppress default */
7893 2304 : 310 : break;
7893 bruce@momjian.us 2305 :UBC 0 : case FKCONSTR_ACTION_RESTRICT:
2306 : 0 : string = "RESTRICT";
2307 : 0 : break;
7893 bruce@momjian.us 2308 :CBC 48 : case FKCONSTR_ACTION_CASCADE:
2309 : 48 : string = "CASCADE";
2310 : 48 : break;
2311 : 14 : case FKCONSTR_ACTION_SETNULL:
2312 : 14 : string = "SET NULL";
2313 : 14 : break;
7893 bruce@momjian.us 2314 :UBC 0 : case FKCONSTR_ACTION_SETDEFAULT:
2315 : 0 : string = "SET DEFAULT";
2316 : 0 : break;
2317 : 0 : default:
7567 tgl@sss.pgh.pa.us 2318 [ # # ]: 0 : elog(ERROR, "unrecognized confupdtype: %d",
2319 : : conForm->confupdtype);
2320 : : string = NULL; /* keep compiler quiet */
2321 : : break;
2322 : : }
7741 tgl@sss.pgh.pa.us 2323 [ + + ]:CBC 372 : if (string)
bruce@momjian.us 2324 : 62 : appendStringInfo(&buf, " ON UPDATE %s", string);
2325 : :
7893 2326 [ + + + + : 372 : switch (conForm->confdeltype)
+ - ]
2327 : : {
2328 : 302 : case FKCONSTR_ACTION_NOACTION:
7559 2329 : 302 : string = NULL; /* suppress default */
7893 2330 : 302 : break;
7893 bruce@momjian.us 2331 :GBC 10 : case FKCONSTR_ACTION_RESTRICT:
2332 : 10 : string = "RESTRICT";
2333 : 10 : break;
7893 bruce@momjian.us 2334 :CBC 48 : case FKCONSTR_ACTION_CASCADE:
2335 : 48 : string = "CASCADE";
2336 : 48 : break;
2337 : 9 : case FKCONSTR_ACTION_SETNULL:
2338 : 9 : string = "SET NULL";
2339 : 9 : break;
2340 : 3 : case FKCONSTR_ACTION_SETDEFAULT:
2341 : 3 : string = "SET DEFAULT";
2342 : 3 : break;
7893 bruce@momjian.us 2343 :UBC 0 : default:
7567 tgl@sss.pgh.pa.us 2344 [ # # ]: 0 : elog(ERROR, "unrecognized confdeltype: %d",
2345 : : conForm->confdeltype);
2346 : : string = NULL; /* keep compiler quiet */
2347 : : break;
2348 : : }
7741 tgl@sss.pgh.pa.us 2349 [ + + ]:CBC 372 : if (string)
bruce@momjian.us 2350 : 70 : appendStringInfo(&buf, " ON DELETE %s", string);
2351 : :
2352 : : /*
2353 : : * Add columns specified to SET NULL or SET DEFAULT if
2354 : : * provided.
2355 : : */
858 peter@eisentraut.org 2356 : 372 : val = SysCacheGetAttr(CONSTROID, tup,
2357 : : Anum_pg_constraint_confdelsetcols, &isnull);
2358 [ + + ]: 372 : if (!isnull)
2359 : : {
586 drowley@postgresql.o 2360 : 6 : appendStringInfoString(&buf, " (");
21 peter@eisentraut.org 2361 :GNC 6 : decompile_column_index_array(val, conForm->conrelid, false, &buf);
586 drowley@postgresql.o 2362 :CBC 6 : appendStringInfoChar(&buf, ')');
2363 : : }
2364 : :
7893 bruce@momjian.us 2365 : 372 : break;
2366 : : }
7731 2367 : 1610 : case CONSTRAINT_PRIMARY:
2368 : : case CONSTRAINT_UNIQUE:
2369 : : {
2370 : : Datum val;
2371 : : Oid indexId;
2372 : : int keyatts;
2373 : : HeapTuple indtup;
2374 : :
2375 : : /* Start off the constraint definition */
2376 [ + + ]: 1610 : if (conForm->contype == CONSTRAINT_PRIMARY)
801 peter@eisentraut.org 2377 : 1349 : appendStringInfoString(&buf, "PRIMARY KEY ");
2378 : : else
2379 : 261 : appendStringInfoString(&buf, "UNIQUE ");
2380 : :
2381 : 1610 : indexId = conForm->conindid;
2382 : :
2383 : 1610 : indtup = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexId));
2384 [ - + ]: 1610 : if (!HeapTupleIsValid(indtup))
801 peter@eisentraut.org 2385 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for index %u", indexId);
801 peter@eisentraut.org 2386 [ + + ]:CBC 1610 : if (conForm->contype == CONSTRAINT_UNIQUE &&
2387 [ - + ]: 261 : ((Form_pg_index) GETSTRUCT(indtup))->indnullsnotdistinct)
801 peter@eisentraut.org 2388 :UBC 0 : appendStringInfoString(&buf, "NULLS NOT DISTINCT ");
2389 : :
586 drowley@postgresql.o 2390 :CBC 1610 : appendStringInfoChar(&buf, '(');
2391 : :
2392 : : /* Fetch and build target column list */
386 dgustafsson@postgres 2393 : 1610 : val = SysCacheGetAttrNotNull(CONSTROID, tup,
2394 : : Anum_pg_constraint_conkey);
2395 : :
21 peter@eisentraut.org 2396 :GNC 1610 : keyatts = decompile_column_index_array(val, conForm->conrelid, false, &buf);
40 2397 [ + + ]: 1610 : if (conForm->conperiod)
81 2398 : 183 : appendStringInfoString(&buf, " WITHOUT OVERLAPS");
2399 : :
3818 rhaas@postgresql.org 2400 :CBC 1610 : appendStringInfoChar(&buf, ')');
2401 : :
2402 : : /* Build including column list (from pg_index.indkeys) */
386 dgustafsson@postgres 2403 : 1610 : val = SysCacheGetAttrNotNull(INDEXRELID, indtup,
2404 : : Anum_pg_index_indnatts);
2050 alvherre@alvh.no-ip. 2405 [ + + ]: 1610 : if (DatumGetInt32(val) > keyatts)
2406 : : {
2407 : : Datum cols;
2408 : : Datum *keys;
2409 : : int nKeys;
2410 : : int j;
2411 : :
2199 teodor@sigaev.ru 2412 : 41 : appendStringInfoString(&buf, " INCLUDE (");
2413 : :
386 dgustafsson@postgres 2414 : 41 : cols = SysCacheGetAttrNotNull(INDEXRELID, indtup,
2415 : : Anum_pg_index_indkey);
2416 : :
653 peter@eisentraut.org 2417 : 41 : deconstruct_array_builtin(DatumGetArrayTypeP(cols), INT2OID,
2418 : : &keys, NULL, &nKeys);
2419 : :
2050 alvherre@alvh.no-ip. 2420 [ + + ]: 123 : for (j = keyatts; j < nKeys; j++)
2421 : : {
2422 : : char *colName;
2423 : :
2424 : 82 : colName = get_attname(conForm->conrelid,
2425 : 82 : DatumGetInt16(keys[j]), false);
2426 [ + + ]: 82 : if (j > keyatts)
2427 : 41 : appendStringInfoString(&buf, ", ");
2428 : 82 : appendStringInfoString(&buf, quote_identifier(colName));
2429 : : }
2430 : :
2199 teodor@sigaev.ru 2431 : 41 : appendStringInfoChar(&buf, ')');
2432 : : }
2050 alvherre@alvh.no-ip. 2433 : 1610 : ReleaseSysCache(indtup);
2434 : :
2435 : : /* XXX why do we only print these bits if fullCommand? */
6028 tgl@sss.pgh.pa.us 2436 [ + + + - ]: 1610 : if (fullCommand && OidIsValid(indexId))
2437 : : {
2438 : 99 : char *options = flatten_reloptions(indexId);
2439 : : Oid tblspc;
2440 : :
6496 bruce@momjian.us 2441 [ - + ]: 99 : if (options)
2442 : : {
6496 bruce@momjian.us 2443 :UBC 0 : appendStringInfo(&buf, " WITH (%s)", options);
2444 : 0 : pfree(options);
2445 : : }
2446 : :
2447 : : /*
2448 : : * Print the tablespace, unless it's the database default.
2449 : : * This is to help ALTER TABLE usage of this facility,
2450 : : * which needs this behavior to recreate exact catalog
2451 : : * state.
2452 : : */
6028 tgl@sss.pgh.pa.us 2453 :CBC 99 : tblspc = get_rel_tablespace(indexId);
2454 [ + + ]: 99 : if (OidIsValid(tblspc))
2455 : 12 : appendStringInfo(&buf, " USING INDEX TABLESPACE %s",
2489 2456 : 12 : quote_identifier(get_tablespace_name(tblspc)));
2457 : : }
2458 : :
7731 bruce@momjian.us 2459 : 1610 : break;
2460 : : }
2461 : 1019 : case CONSTRAINT_CHECK:
2462 : : {
2463 : : Datum val;
2464 : : char *conbin;
2465 : : char *consrc;
2466 : : Node *expr;
2467 : : List *context;
2468 : :
2469 : : /* Fetch constraint expression in parsetree form */
386 dgustafsson@postgres 2470 : 1019 : val = SysCacheGetAttrNotNull(CONSTROID, tup,
2471 : : Anum_pg_constraint_conbin);
2472 : :
5864 tgl@sss.pgh.pa.us 2473 : 1019 : conbin = TextDatumGetCString(val);
7599 bruce@momjian.us 2474 : 1019 : expr = stringToNode(conbin);
2475 : :
2476 : : /* Set up deparsing context for Var nodes in constraint */
2477 [ + + ]: 1019 : if (conForm->conrelid != InvalidOid)
2478 : : {
2479 : : /* relation constraint */
4912 tgl@sss.pgh.pa.us 2480 : 923 : context = deparse_context_for(get_relation_name(conForm->conrelid),
2481 : : conForm->conrelid);
2482 : : }
2483 : : else
2484 : : {
2485 : : /* domain constraint --- can't have Vars */
7498 2486 : 96 : context = NIL;
2487 : : }
2488 : :
7564 2489 : 1019 : consrc = deparse_expression_pretty(expr, context, false, false,
2490 : : prettyFlags, 0);
2491 : :
2492 : : /*
2493 : : * Now emit the constraint definition, adding NO INHERIT if
2494 : : * necessary.
2495 : : *
2496 : : * There are cases where the constraint expression will be
2497 : : * fully parenthesized and we don't need the outer parens ...
2498 : : * but there are other cases where we do need 'em. Be
2499 : : * conservative for now.
2500 : : *
2501 : : * Note that simply checking for leading '(' and trailing ')'
2502 : : * would NOT be good enough, consider "(x > 0) AND (y > 0)".
2503 : : */
4282 alvherre@alvh.no-ip. 2504 : 1019 : appendStringInfo(&buf, "CHECK (%s)%s",
2505 : : consrc,
2506 [ + + ]: 1019 : conForm->connoinherit ? " NO INHERIT" : "");
7731 bruce@momjian.us 2507 : 1019 : break;
2508 : : }
233 alvherre@alvh.no-ip. 2509 :GNC 27 : case CONSTRAINT_NOTNULL:
2510 : : {
25 peter@eisentraut.org 2511 [ + - ]: 27 : if (conForm->conrelid)
2512 : : {
2513 : : AttrNumber attnum;
2514 : :
2515 : 27 : attnum = extractNotNullColumn(tup);
2516 : :
2517 : 27 : appendStringInfo(&buf, "NOT NULL %s",
2518 : 27 : quote_identifier(get_attname(conForm->conrelid,
2519 : : attnum, false)));
2520 [ - + ]: 27 : if (((Form_pg_constraint) GETSTRUCT(tup))->connoinherit)
25 peter@eisentraut.org 2521 :UNC 0 : appendStringInfoString(&buf, " NO INHERIT");
2522 : : }
2523 [ # # ]: 0 : else if (conForm->contypid)
2524 : : {
2525 : : /* conkey is null for domain not-null constraints */
2526 : 0 : appendStringInfoString(&buf, "NOT NULL VALUE");
2527 : : }
233 alvherre@alvh.no-ip. 2528 :GNC 27 : break;
2529 : : }
2530 : :
5201 tgl@sss.pgh.pa.us 2531 :UBC 0 : case CONSTRAINT_TRIGGER:
2532 : :
2533 : : /*
2534 : : * There isn't an ALTER TABLE syntax for creating a user-defined
2535 : : * constraint trigger, but it seems better to print something than
2536 : : * throw an error; if we throw error then this function couldn't
2537 : : * safely be applied to all rows of pg_constraint.
2538 : : */
3818 rhaas@postgresql.org 2539 : 0 : appendStringInfoString(&buf, "TRIGGER");
5201 tgl@sss.pgh.pa.us 2540 : 0 : break;
5242 tgl@sss.pgh.pa.us 2541 :CBC 52 : case CONSTRAINT_EXCLUSION:
2542 : : {
5161 bruce@momjian.us 2543 : 52 : Oid indexOid = conForm->conindid;
2544 : : Datum val;
2545 : : Datum *elems;
2546 : : int nElems;
2547 : : int i;
2548 : : Oid *operators;
2549 : :
2550 : : /* Extract operator OIDs from the pg_constraint tuple */
386 dgustafsson@postgres 2551 : 52 : val = SysCacheGetAttrNotNull(CONSTROID, tup,
2552 : : Anum_pg_constraint_conexclop);
2553 : :
653 peter@eisentraut.org 2554 : 52 : deconstruct_array_builtin(DatumGetArrayTypeP(val), OIDOID,
2555 : : &elems, NULL, &nElems);
2556 : :
5242 tgl@sss.pgh.pa.us 2557 : 52 : operators = (Oid *) palloc(nElems * sizeof(Oid));
2558 [ + + ]: 114 : for (i = 0; i < nElems; i++)
2559 : 62 : operators[i] = DatumGetObjectId(elems[i]);
2560 : :
2561 : : /* pg_get_indexdef_worker does the rest */
2562 : : /* suppress tablespace because pg_dump wants it that way */
2563 : 52 : appendStringInfoString(&buf,
2564 : 52 : pg_get_indexdef_worker(indexOid,
2565 : : 0,
2566 : : operators,
2567 : : false,
2568 : : false,
2569 : : false,
2570 : : false,
2571 : : prettyFlags,
2572 : : false));
2573 : 52 : break;
2574 : : }
7912 tgl@sss.pgh.pa.us 2575 :UBC 0 : default:
7517 peter_e@gmx.net 2576 [ # # ]: 0 : elog(ERROR, "invalid constraint type \"%c\"", conForm->contype);
2577 : : break;
2578 : : }
2579 : :
5373 tgl@sss.pgh.pa.us 2580 [ + + ]:CBC 3080 : if (conForm->condeferrable)
3818 rhaas@postgresql.org 2581 : 63 : appendStringInfoString(&buf, " DEFERRABLE");
5373 tgl@sss.pgh.pa.us 2582 [ + + ]: 3080 : if (conForm->condeferred)
3818 rhaas@postgresql.org 2583 :GBC 24 : appendStringInfoString(&buf, " INITIALLY DEFERRED");
4700 alvherre@alvh.no-ip. 2584 [ + + ]:CBC 3080 : if (!conForm->convalidated)
2585 : 47 : appendStringInfoString(&buf, " NOT VALID");
2586 : :
2587 : : /* Cleanup */
3661 simon@2ndQuadrant.co 2588 : 3080 : systable_endscan(scandesc);
1910 andres@anarazel.de 2589 : 3080 : table_close(relation, AccessShareLock);
2590 : :
7284 tgl@sss.pgh.pa.us 2591 : 3080 : return buf.data;
2592 : : }
2593 : :
2594 : :
2595 : : /*
2596 : : * Convert an int16[] Datum into a comma-separated list of column names
2597 : : * for the indicated relation; append the list to buf. Returns the number
2598 : : * of keys.
2599 : : */
2600 : : static int
7912 2601 : 2360 : decompile_column_index_array(Datum column_index_array, Oid relId,
2602 : : bool withPeriod, StringInfo buf)
2603 : : {
2604 : : Datum *keys;
2605 : : int nKeys;
2606 : : int j;
2607 : :
2608 : : /* Extract data from array of int16 */
653 peter@eisentraut.org 2609 : 2360 : deconstruct_array_builtin(DatumGetArrayTypeP(column_index_array), INT2OID,
2610 : : &keys, NULL, &nKeys);
2611 : :
7912 tgl@sss.pgh.pa.us 2612 [ + + ]: 5689 : for (j = 0; j < nKeys; j++)
2613 : : {
2614 : : char *colName;
2615 : :
2253 alvherre@alvh.no-ip. 2616 : 3329 : colName = get_attname(relId, DatumGetInt16(keys[j]), false);
2617 : :
7912 tgl@sss.pgh.pa.us 2618 [ + + ]: 3329 : if (j == 0)
7379 neilc@samurai.com 2619 : 2360 : appendStringInfoString(buf, quote_identifier(colName));
2620 : : else
21 peter@eisentraut.org 2621 [ + + ]:GNC 1093 : appendStringInfo(buf, ", %s%s",
2622 [ + + ]: 124 : (withPeriod && j == nKeys - 1) ? "PERIOD " : "",
2623 : : quote_identifier(colName));
2624 : : }
2625 : :
2050 alvherre@alvh.no-ip. 2626 :CBC 2360 : return nKeys;
2627 : : }
2628 : :
2629 : :
2630 : : /* ----------
2631 : : * pg_get_expr - Decompile an expression tree
2632 : : *
2633 : : * Input: an expression tree in nodeToString form, and a relation OID
2634 : : *
2635 : : * Output: reverse-listed expression
2636 : : *
2637 : : * Currently, the expression can only refer to a single relation, namely
2638 : : * the one specified by the second parameter. This is sufficient for
2639 : : * partial indexes, column default expressions, etc. We also support
2640 : : * Var-free expressions, for which the OID can be InvalidOid.
2641 : : *
2642 : : * If the OID is nonzero but not actually valid, don't throw an error,
2643 : : * just return NULL. This is a bit questionable, but it's what we've
2644 : : * done historically, and it can help avoid unwanted failures when
2645 : : * examining catalog entries for just-deleted relations.
2646 : : *
2647 : : * We expect this function to work, or throw a reasonably clean error,
2648 : : * for any node tree that can appear in a catalog pg_node_tree column.
2649 : : * Query trees, such as those appearing in pg_rewrite.ev_action, are
2650 : : * not supported. Nor are expressions in more than one relation, which
2651 : : * can appear in places like pg_rewrite.ev_qual.
2652 : : * ----------
2653 : : */
2654 : : Datum
8308 tgl@sss.pgh.pa.us 2655 : 3801 : pg_get_expr(PG_FUNCTION_ARGS)
2656 : : {
2590 noah@leadboat.com 2657 : 3801 : text *expr = PG_GETARG_TEXT_PP(0);
7559 bruce@momjian.us 2658 : 3801 : Oid relid = PG_GETARG_OID(1);
2659 : : text *result;
2660 : : int prettyFlags;
2661 : :
4088 tgl@sss.pgh.pa.us 2662 : 3801 : prettyFlags = PRETTYFLAG_INDENT;
2663 : :
65 2664 : 3801 : result = pg_get_expr_worker(expr, relid, prettyFlags);
2665 [ + - ]: 3801 : if (result)
2666 : 3801 : PG_RETURN_TEXT_P(result);
2667 : : else
65 tgl@sss.pgh.pa.us 2668 :UBC 0 : PG_RETURN_NULL();
2669 : : }
2670 : :
2671 : : Datum
7564 tgl@sss.pgh.pa.us 2672 :CBC 239 : pg_get_expr_ext(PG_FUNCTION_ARGS)
2673 : : {
2590 noah@leadboat.com 2674 : 239 : text *expr = PG_GETARG_TEXT_PP(0);
7559 bruce@momjian.us 2675 : 239 : Oid relid = PG_GETARG_OID(1);
7564 tgl@sss.pgh.pa.us 2676 : 239 : bool pretty = PG_GETARG_BOOL(2);
2677 : : text *result;
2678 : : int prettyFlags;
2679 : :
748 2680 [ + - ]: 239 : prettyFlags = GET_PRETTY_FLAGS(pretty);
2681 : :
65 2682 : 239 : result = pg_get_expr_worker(expr, relid, prettyFlags);
2683 [ + - ]: 239 : if (result)
2684 : 239 : PG_RETURN_TEXT_P(result);
2685 : : else
65 tgl@sss.pgh.pa.us 2686 :UBC 0 : PG_RETURN_NULL();
2687 : : }
2688 : :
2689 : : static text *
65 tgl@sss.pgh.pa.us 2690 :CBC 4040 : pg_get_expr_worker(text *expr, Oid relid, int prettyFlags)
2691 : : {
2692 : : Node *node;
2693 : : Node *tst;
2694 : : Relids relids;
2695 : : List *context;
2696 : : char *exprstr;
2697 : 4040 : Relation rel = NULL;
2698 : : char *str;
2699 : :
2700 : : /* Convert input pg_node_tree (really TEXT) object to C string */
5864 2701 : 4040 : exprstr = text_to_cstring(expr);
2702 : :
2703 : : /* Convert expression to node tree */
8308 2704 : 4040 : node = (Node *) stringToNode(exprstr);
2705 : :
5437 2706 : 4040 : pfree(exprstr);
2707 : :
2708 : : /*
2709 : : * Throw error if the input is a querytree rather than an expression tree.
2710 : : * While we could support queries here, there seems no very good reason
2711 : : * to. In most such catalog columns, we'll see a List of Query nodes, or
2712 : : * even nested Lists, so drill down to a non-List node before checking.
2713 : : */
826 2714 : 4040 : tst = node;
2715 [ + - - + ]: 4040 : while (tst && IsA(tst, List))
826 tgl@sss.pgh.pa.us 2716 :UBC 0 : tst = linitial((List *) tst);
826 tgl@sss.pgh.pa.us 2717 [ + - - + ]:CBC 4040 : if (tst && IsA(tst, Query))
826 tgl@sss.pgh.pa.us 2718 [ # # ]:UBC 0 : ereport(ERROR,
2719 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2720 : : errmsg("input is a query, not an expression")));
2721 : :
2722 : : /*
2723 : : * Throw error if the expression contains Vars we won't be able to
2724 : : * deparse.
2725 : : */
826 tgl@sss.pgh.pa.us 2726 :CBC 4040 : relids = pull_varnos(NULL, node);
2727 [ + + ]: 4040 : if (OidIsValid(relid))
2728 : : {
2729 [ - + ]: 4013 : if (!bms_is_subset(relids, bms_make_singleton(1)))
826 tgl@sss.pgh.pa.us 2730 [ # # ]:UBC 0 : ereport(ERROR,
2731 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2732 : : errmsg("expression contains variables of more than one relation")));
2733 : : }
2734 : : else
2735 : : {
826 tgl@sss.pgh.pa.us 2736 [ - + ]:GBC 27 : if (!bms_is_empty(relids))
826 tgl@sss.pgh.pa.us 2737 [ # # ]:UBC 0 : ereport(ERROR,
2738 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2739 : : errmsg("expression contains variables")));
2740 : : }
2741 : :
2742 : : /*
2743 : : * Prepare deparse context if needed. If we are deparsing with a relid,
2744 : : * we need to transiently open and lock the rel, to make sure it won't go
2745 : : * away underneath us. (set_relation_column_names would lock it anyway,
2746 : : * so this isn't really introducing any new behavior.)
2747 : : */
5437 tgl@sss.pgh.pa.us 2748 [ + + ]:CBC 4040 : if (OidIsValid(relid))
2749 : : {
65 2750 : 4013 : rel = try_relation_open(relid, AccessShareLock);
2751 [ - + ]: 4013 : if (rel == NULL)
65 tgl@sss.pgh.pa.us 2752 :UBC 0 : return NULL;
65 tgl@sss.pgh.pa.us 2753 :CBC 4013 : context = deparse_context_for(RelationGetRelationName(rel), relid);
2754 : : }
2755 : : else
5437 tgl@sss.pgh.pa.us 2756 :GBC 27 : context = NIL;
2757 : :
2758 : : /* Deparse */
7564 tgl@sss.pgh.pa.us 2759 :CBC 4040 : str = deparse_expression_pretty(node, context, false, false,
2760 : : prettyFlags, 0);
2761 : :
65 2762 [ + + ]: 4040 : if (rel != NULL)
2763 : 4013 : relation_close(rel, AccessShareLock);
2764 : :
5437 2765 : 4040 : return string_to_text(str);
2766 : : }
2767 : :
2768 : :
2769 : : /* ----------
2770 : : * pg_get_userbyid - Get a user name by roleid and
2771 : : * fallback to 'unknown (OID=n)'
2772 : : * ----------
2773 : : */
2774 : : Datum
8706 2775 : 794 : pg_get_userbyid(PG_FUNCTION_ARGS)
2776 : : {
6865 2777 : 794 : Oid roleid = PG_GETARG_OID(0);
2778 : : Name result;
2779 : : HeapTuple roletup;
2780 : : Form_pg_authid role_rec;
2781 : :
2782 : : /*
2783 : : * Allocate space for the result
2784 : : */
8706 2785 : 794 : result = (Name) palloc(NAMEDATALEN);
8925 bruce@momjian.us 2786 : 794 : memset(NameStr(*result), 0, NAMEDATALEN);
2787 : :
2788 : : /*
2789 : : * Get the pg_authid entry and print the result
2790 : : */
5173 rhaas@postgresql.org 2791 : 794 : roletup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
6865 tgl@sss.pgh.pa.us 2792 [ + - ]: 794 : if (HeapTupleIsValid(roletup))
2793 : : {
2794 : 794 : role_rec = (Form_pg_authid) GETSTRUCT(roletup);
1343 peter@eisentraut.org 2795 : 794 : *result = role_rec->rolname;
6865 tgl@sss.pgh.pa.us 2796 : 794 : ReleaseSysCache(roletup);
2797 : : }
2798 : : else
6865 tgl@sss.pgh.pa.us 2799 :UBC 0 : sprintf(NameStr(*result), "unknown (OID=%u)", roleid);
2800 : :
8706 tgl@sss.pgh.pa.us 2801 :CBC 794 : PG_RETURN_NAME(result);
2802 : : }
2803 : :
2804 : :
2805 : : /*
2806 : : * pg_get_serial_sequence
2807 : : * Get the name of the sequence used by an identity or serial column,
2808 : : * formatted suitably for passing to setval, nextval or currval.
2809 : : * First parameter is not treated as double-quoted, second parameter
2810 : : * is --- see documentation for reason.
2811 : : */
2812 : : Datum
7233 2813 : 6 : pg_get_serial_sequence(PG_FUNCTION_ARGS)
2814 : : {
2590 noah@leadboat.com 2815 : 6 : text *tablename = PG_GETARG_TEXT_PP(0);
5864 tgl@sss.pgh.pa.us 2816 : 6 : text *columnname = PG_GETARG_TEXT_PP(1);
2817 : : RangeVar *tablerv;
2818 : : Oid tableOid;
2819 : : char *column;
2820 : : AttrNumber attnum;
7168 bruce@momjian.us 2821 : 6 : Oid sequenceId = InvalidOid;
2822 : : Relation depRel;
2823 : : ScanKeyData key[3];
2824 : : SysScanDesc scan;
2825 : : HeapTuple tup;
2826 : :
2827 : : /* Look up table name. Can't lock it - we might not have privileges. */
6897 neilc@samurai.com 2828 : 6 : tablerv = makeRangeVarFromNameList(textToQualifiedNameList(tablename));
4519 rhaas@postgresql.org 2829 : 6 : tableOid = RangeVarGetRelid(tablerv, NoLock, false);
2830 : :
2831 : : /* Get the number of the column */
5864 tgl@sss.pgh.pa.us 2832 : 6 : column = text_to_cstring(columnname);
2833 : :
7233 2834 : 6 : attnum = get_attnum(tableOid, column);
2835 [ - + ]: 6 : if (attnum == InvalidAttrNumber)
7233 tgl@sss.pgh.pa.us 2836 [ # # ]:UBC 0 : ereport(ERROR,
2837 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
2838 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
2839 : : column, tablerv->relname)));
2840 : :
2841 : : /* Search the dependency table for the dependent sequence */
1910 andres@anarazel.de 2842 :CBC 6 : depRel = table_open(DependRelationId, AccessShareLock);
2843 : :
7233 tgl@sss.pgh.pa.us 2844 : 6 : ScanKeyInit(&key[0],
2845 : : Anum_pg_depend_refclassid,
2846 : : BTEqualStrategyNumber, F_OIDEQ,
2847 : : ObjectIdGetDatum(RelationRelationId));
2848 : 6 : ScanKeyInit(&key[1],
2849 : : Anum_pg_depend_refobjid,
2850 : : BTEqualStrategyNumber, F_OIDEQ,
2851 : : ObjectIdGetDatum(tableOid));
2852 : 6 : ScanKeyInit(&key[2],
2853 : : Anum_pg_depend_refobjsubid,
2854 : : BTEqualStrategyNumber, F_INT4EQ,
2855 : : Int32GetDatum(attnum));
2856 : :
6940 2857 : 6 : scan = systable_beginscan(depRel, DependReferenceIndexId, true,
2858 : : NULL, 3, key);
2859 : :
7233 2860 [ + - ]: 15 : while (HeapTupleIsValid(tup = systable_getnext(scan)))
2861 : : {
7168 bruce@momjian.us 2862 : 15 : Form_pg_depend deprec = (Form_pg_depend) GETSTRUCT(tup);
2863 : :
2864 : : /*
2865 : : * Look for an auto dependency (serial column) or internal dependency
2866 : : * (identity column) of a sequence on a column. (We need the relkind
2867 : : * test because indexes can also have auto dependencies on columns.)
2868 : : */
6940 tgl@sss.pgh.pa.us 2869 [ + + ]: 15 : if (deprec->classid == RelationRelationId &&
7233 2870 [ + - ]: 6 : deprec->objsubid == 0 &&
2403 peter_e@gmx.net 2871 [ + + ]: 6 : (deprec->deptype == DEPENDENCY_AUTO ||
2872 [ + - + - ]: 9 : deprec->deptype == DEPENDENCY_INTERNAL) &&
6365 tgl@sss.pgh.pa.us 2873 : 6 : get_rel_relkind(deprec->objid) == RELKIND_SEQUENCE)
2874 : : {
7233 2875 : 6 : sequenceId = deprec->objid;
2876 : 6 : break;
2877 : : }
2878 : : }
2879 : :
2880 : 6 : systable_endscan(scan);
1910 andres@anarazel.de 2881 : 6 : table_close(depRel, AccessShareLock);
2882 : :
7233 tgl@sss.pgh.pa.us 2883 [ + - ]: 6 : if (OidIsValid(sequenceId))
2884 : : {
2885 : : char *result;
2886 : :
3068 2887 : 6 : result = generate_qualified_relation_name(sequenceId);
2888 : :
7233 2889 : 6 : PG_RETURN_TEXT_P(string_to_text(result));
2890 : : }
2891 : :
7233 tgl@sss.pgh.pa.us 2892 :UBC 0 : PG_RETURN_NULL();
2893 : : }
2894 : :
2895 : :
2896 : : /*
2897 : : * pg_get_functiondef
2898 : : * Returns the complete "CREATE OR REPLACE FUNCTION ..." statement for
2899 : : * the specified function.
2900 : : *
2901 : : * Note: if you change the output format of this function, be careful not
2902 : : * to break psql's rules (in \ef and \sf) for identifying the start of the
2903 : : * function body. To wit: the function body starts on a line that begins with
2904 : : * "AS ", "BEGIN ", or "RETURN ", and no preceding line will look like that.
2905 : : */
2906 : : Datum
5699 tgl@sss.pgh.pa.us 2907 :CBC 82 : pg_get_functiondef(PG_FUNCTION_ARGS)
2908 : : {
2909 : 82 : Oid funcid = PG_GETARG_OID(0);
2910 : : StringInfoData buf;
2911 : : StringInfoData dq;
2912 : : HeapTuple proctup;
2913 : : Form_pg_proc proc;
2914 : : bool isfunction;
2915 : : Datum tmp;
2916 : : bool isnull;
2917 : : const char *prosrc;
2918 : : const char *name;
2919 : : const char *nsp;
2920 : : float4 procost;
2921 : : int oldlen;
2922 : :
2923 : 82 : initStringInfo(&buf);
2924 : :
2925 : : /* Look up the function */
5173 rhaas@postgresql.org 2926 : 82 : proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
5699 tgl@sss.pgh.pa.us 2927 [ + + ]: 82 : if (!HeapTupleIsValid(proctup))
2819 rhaas@postgresql.org 2928 : 3 : PG_RETURN_NULL();
2929 : :
5699 tgl@sss.pgh.pa.us 2930 : 79 : proc = (Form_pg_proc) GETSTRUCT(proctup);
2931 : 79 : name = NameStr(proc->proname);
2932 : :
2235 peter_e@gmx.net 2933 [ - + ]: 79 : if (proc->prokind == PROKIND_AGGREGATE)
5699 tgl@sss.pgh.pa.us 2934 [ # # ]:UBC 0 : ereport(ERROR,
2935 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2936 : : errmsg("\"%s\" is an aggregate function", name)));
2937 : :
2235 peter_e@gmx.net 2938 :CBC 79 : isfunction = (proc->prokind != PROKIND_PROCEDURE);
2939 : :
2940 : : /*
2941 : : * We always qualify the function name, to ensure the right function gets
2942 : : * replaced.
2943 : : */
992 tgl@sss.pgh.pa.us 2944 : 79 : nsp = get_namespace_name_or_temp(proc->pronamespace);
2252 peter_e@gmx.net 2945 [ + + ]: 79 : appendStringInfo(&buf, "CREATE OR REPLACE %s %s(",
2946 : : isfunction ? "FUNCTION" : "PROCEDURE",
2947 : : quote_qualified_identifier(nsp, name));
5610 2948 : 79 : (void) print_function_arguments(&buf, proctup, false, true);
2252 2949 : 79 : appendStringInfoString(&buf, ")\n");
2950 [ + + ]: 79 : if (isfunction)
2951 : : {
2952 : 69 : appendStringInfoString(&buf, " RETURNS ");
2953 : 69 : print_function_rettype(&buf, proctup);
2954 : 69 : appendStringInfoChar(&buf, '\n');
2955 : : }
2956 : :
3276 2957 : 79 : print_function_trftypes(&buf, proctup);
2958 : :
2252 2959 : 79 : appendStringInfo(&buf, " LANGUAGE %s\n",
2489 tgl@sss.pgh.pa.us 2960 : 79 : quote_identifier(get_language_name(proc->prolang, false)));
2961 : :
2962 : : /* Emit some miscellaneous options on one line */
5699 2963 : 79 : oldlen = buf.len;
2964 : :
2235 peter_e@gmx.net 2965 [ - + ]: 79 : if (proc->prokind == PROKIND_WINDOW)
5583 tgl@sss.pgh.pa.us 2966 :UBC 0 : appendStringInfoString(&buf, " WINDOW");
5699 tgl@sss.pgh.pa.us 2967 [ + + + - ]:CBC 79 : switch (proc->provolatile)
2968 : : {
2969 : 6 : case PROVOLATILE_IMMUTABLE:
2970 : 6 : appendStringInfoString(&buf, " IMMUTABLE");
2971 : 6 : break;
2972 : 15 : case PROVOLATILE_STABLE:
2973 : 15 : appendStringInfoString(&buf, " STABLE");
2974 : 15 : break;
2975 : 58 : case PROVOLATILE_VOLATILE:
2976 : 58 : break;
2977 : : }
2978 : :
2910 rhaas@postgresql.org 2979 [ + - + - ]: 79 : switch (proc->proparallel)
2980 : : {
2981 : 14 : case PROPARALLEL_SAFE:
2982 : 14 : appendStringInfoString(&buf, " PARALLEL SAFE");
2983 : 14 : break;
2910 rhaas@postgresql.org 2984 :UBC 0 : case PROPARALLEL_RESTRICTED:
2985 : 0 : appendStringInfoString(&buf, " PARALLEL RESTRICTED");
2986 : 0 : break;
2910 rhaas@postgresql.org 2987 :CBC 65 : case PROPARALLEL_UNSAFE:
2988 : 65 : break;
2989 : : }
2990 : :
5699 tgl@sss.pgh.pa.us 2991 [ + + ]: 79 : if (proc->proisstrict)
2992 : 25 : appendStringInfoString(&buf, " STRICT");
2993 [ + + ]: 79 : if (proc->prosecdef)
2994 : 3 : appendStringInfoString(&buf, " SECURITY DEFINER");
3244 2995 [ - + ]: 79 : if (proc->proleakproof)
3244 tgl@sss.pgh.pa.us 2996 :UBC 0 : appendStringInfoString(&buf, " LEAKPROOF");
2997 : :
2998 : : /* This code for the default cost and rows should match functioncmds.c */
5699 tgl@sss.pgh.pa.us 2999 [ + - ]:CBC 79 : if (proc->prolang == INTERNALlanguageId ||
3000 [ + + ]: 79 : proc->prolang == ClanguageId)
3001 : 5 : procost = 1;
3002 : : else
3003 : 74 : procost = 100;
3004 [ + + ]: 79 : if (proc->procost != procost)
3005 : 3 : appendStringInfo(&buf, " COST %g", proc->procost);
3006 : :
3007 [ + + - + ]: 79 : if (proc->prorows > 0 && proc->prorows != 1000)
5699 tgl@sss.pgh.pa.us 3008 :UBC 0 : appendStringInfo(&buf, " ROWS %g", proc->prorows);
3009 : :
1891 tgl@sss.pgh.pa.us 3010 [ - + ]:CBC 79 : if (proc->prosupport)
3011 : : {
3012 : : Oid argtypes[1];
3013 : :
3014 : : /*
3015 : : * We should qualify the support function's name if it wouldn't be
3016 : : * resolved by lookup in the current search path.
3017 : : */
1891 tgl@sss.pgh.pa.us 3018 :UBC 0 : argtypes[0] = INTERNALOID;
3019 : 0 : appendStringInfo(&buf, " SUPPORT %s",
3020 : : generate_function_name(proc->prosupport, 1,
3021 : : NIL, argtypes,
3022 : : false, NULL, EXPR_KIND_NONE));
3023 : : }
3024 : :
5699 tgl@sss.pgh.pa.us 3025 [ + + ]:CBC 79 : if (oldlen != buf.len)
3026 : 32 : appendStringInfoChar(&buf, '\n');
3027 : :
3028 : : /* Emit any proconfig options, one per line */
3029 : 79 : tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_proconfig, &isnull);
3030 [ + + ]: 79 : if (!isnull)
3031 : : {
5421 bruce@momjian.us 3032 : 3 : ArrayType *a = DatumGetArrayTypeP(tmp);
3033 : : int i;
3034 : :
5699 tgl@sss.pgh.pa.us 3035 [ - + ]: 3 : Assert(ARR_ELEMTYPE(a) == TEXTOID);
3036 [ - + ]: 3 : Assert(ARR_NDIM(a) == 1);
3037 [ - + ]: 3 : Assert(ARR_LBOUND(a)[0] == 1);
3038 : :
3039 [ + + ]: 18 : for (i = 1; i <= ARR_DIMS(a)[0]; i++)
3040 : : {
3041 : : Datum d;
3042 : :
3043 : 15 : d = array_ref(a, 1, &i,
3044 : : -1 /* varlenarray */ ,
3045 : : -1 /* TEXT's typlen */ ,
3046 : : false /* TEXT's typbyval */ ,
3047 : : TYPALIGN_INT /* TEXT's typalign */ ,
3048 : : &isnull);
3049 [ + - ]: 15 : if (!isnull)
3050 : : {
3051 : 15 : char *configitem = TextDatumGetCString(d);
3052 : : char *pos;
3053 : :
3054 : 15 : pos = strchr(configitem, '=');
3055 [ - + ]: 15 : if (pos == NULL)
5699 tgl@sss.pgh.pa.us 3056 :UBC 0 : continue;
5699 tgl@sss.pgh.pa.us 3057 :CBC 15 : *pos++ = '\0';
3058 : :
3059 : 15 : appendStringInfo(&buf, " SET %s TO ",
3060 : : quote_identifier(configitem));
3061 : :
3062 : : /*
3063 : : * Variables that are marked GUC_LIST_QUOTE were already fully
3064 : : * quoted by flatten_set_variable_args() before they were put
3065 : : * into the proconfig array. However, because the quoting
3066 : : * rules used there aren't exactly like SQL's, we have to
3067 : : * break the list value apart and then quote the elements as
3068 : : * string literals. (The elements may be double-quoted as-is,
3069 : : * but we can't just feed them to the SQL parser; it would do
3070 : : * the wrong thing with elements that are zero-length or
3071 : : * longer than NAMEDATALEN.)
3072 : : *
3073 : : * Variables that are not so marked should just be emitted as
3074 : : * simple string literals. If the variable is not known to
3075 : : * guc.c, we'll do that; this makes it unsafe to use
3076 : : * GUC_LIST_QUOTE for extension variables.
3077 : : */
2216 3078 [ + + ]: 15 : if (GetConfigOptionFlags(configitem, true) & GUC_LIST_QUOTE)
3079 : : {
3080 : : List *namelist;
3081 : : ListCell *lc;
3082 : :
3083 : : /* Parse string into list of identifiers */
2084 3084 [ - + ]: 6 : if (!SplitGUCList(pos, ',', &namelist))
3085 : : {
3086 : : /* this shouldn't fail really */
2084 tgl@sss.pgh.pa.us 3087 [ # # ]:UBC 0 : elog(ERROR, "invalid list syntax in proconfig item");
3088 : : }
2084 tgl@sss.pgh.pa.us 3089 [ + - + + :CBC 21 : foreach(lc, namelist)
+ + ]
3090 : : {
3091 : 15 : char *curname = (char *) lfirst(lc);
3092 : :
3093 : 15 : simple_quote_literal(&buf, curname);
1735 3094 [ + + ]: 15 : if (lnext(namelist, lc))
2084 3095 : 9 : appendStringInfoString(&buf, ", ");
3096 : : }
3097 : : }
3098 : : else
5699 3099 : 9 : simple_quote_literal(&buf, pos);
3100 : 15 : appendStringInfoChar(&buf, '\n');
3101 : : }
3102 : : }
3103 : : }
3104 : :
3105 : : /* And finally the function definition ... */
957 3106 : 79 : (void) SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
1103 peter@eisentraut.org 3107 [ + + + + ]: 79 : if (proc->prolang == SQLlanguageId && !isnull)
3108 : : {
3109 : 53 : print_function_sqlbody(&buf, proctup);
3110 : : }
3111 : : else
3112 : : {
1068 tgl@sss.pgh.pa.us 3113 : 26 : appendStringInfoString(&buf, "AS ");
3114 : :
3115 : 26 : tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
3116 [ + + ]: 26 : if (!isnull)
3117 : : {
3118 : 5 : simple_quote_literal(&buf, TextDatumGetCString(tmp));
3119 : 5 : appendStringInfoString(&buf, ", "); /* assume prosrc isn't null */
3120 : : }
3121 : :
386 dgustafsson@postgres 3122 : 26 : tmp = SysCacheGetAttrNotNull(PROCOID, proctup, Anum_pg_proc_prosrc);
1068 tgl@sss.pgh.pa.us 3123 : 26 : prosrc = TextDatumGetCString(tmp);
3124 : :
3125 : : /*
3126 : : * We always use dollar quoting. Figure out a suitable delimiter.
3127 : : *
3128 : : * Since the user is likely to be editing the function body string, we
3129 : : * shouldn't use a short delimiter that he might easily create a
3130 : : * conflict with. Hence prefer "$function$"/"$procedure$", but extend
3131 : : * if needed.
3132 : : */
3133 : 26 : initStringInfo(&dq);
3134 : 26 : appendStringInfoChar(&dq, '$');
3135 [ + + ]: 26 : appendStringInfoString(&dq, (isfunction ? "function" : "procedure"));
3136 [ - + ]: 26 : while (strstr(prosrc, dq.data) != NULL)
1068 tgl@sss.pgh.pa.us 3137 :UBC 0 : appendStringInfoChar(&dq, 'x');
1068 tgl@sss.pgh.pa.us 3138 :CBC 26 : appendStringInfoChar(&dq, '$');
3139 : :
3140 : 26 : appendBinaryStringInfo(&buf, dq.data, dq.len);
3141 : 26 : appendStringInfoString(&buf, prosrc);
3142 : 26 : appendBinaryStringInfo(&buf, dq.data, dq.len);
3143 : : }
3144 : :
3818 rhaas@postgresql.org 3145 : 79 : appendStringInfoChar(&buf, '\n');
3146 : :
5699 tgl@sss.pgh.pa.us 3147 : 79 : ReleaseSysCache(proctup);
3148 : :
3149 : 79 : PG_RETURN_TEXT_P(string_to_text(buf.data));
3150 : : }
3151 : :
3152 : : /*
3153 : : * pg_get_function_arguments
3154 : : * Get a nicely-formatted list of arguments for a function.
3155 : : * This is everything that would go between the parentheses in
3156 : : * CREATE FUNCTION.
3157 : : */
3158 : : Datum
5749 3159 : 2309 : pg_get_function_arguments(PG_FUNCTION_ARGS)
3160 : : {
3161 : 2309 : Oid funcid = PG_GETARG_OID(0);
3162 : : StringInfoData buf;
3163 : : HeapTuple proctup;
3164 : :
5173 rhaas@postgresql.org 3165 : 2309 : proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
5749 tgl@sss.pgh.pa.us 3166 [ + + ]: 2309 : if (!HeapTupleIsValid(proctup))
2816 rhaas@postgresql.org 3167 : 3 : PG_RETURN_NULL();
3168 : :
3169 : 2306 : initStringInfo(&buf);
3170 : :
5610 peter_e@gmx.net 3171 : 2306 : (void) print_function_arguments(&buf, proctup, false, true);
3172 : :
5749 tgl@sss.pgh.pa.us 3173 : 2306 : ReleaseSysCache(proctup);
3174 : :
3175 : 2306 : PG_RETURN_TEXT_P(string_to_text(buf.data));
3176 : : }
3177 : :
3178 : : /*
3179 : : * pg_get_function_identity_arguments
3180 : : * Get a formatted list of arguments for a function.
3181 : : * This is everything that would go between the parentheses in
3182 : : * ALTER FUNCTION, etc. In particular, don't print defaults.
3183 : : */
3184 : : Datum
5610 peter_e@gmx.net 3185 : 2049 : pg_get_function_identity_arguments(PG_FUNCTION_ARGS)
3186 : : {
3187 : 2049 : Oid funcid = PG_GETARG_OID(0);
3188 : : StringInfoData buf;
3189 : : HeapTuple proctup;
3190 : :
5173 rhaas@postgresql.org 3191 : 2049 : proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
5610 peter_e@gmx.net 3192 [ + + ]: 2049 : if (!HeapTupleIsValid(proctup))
2816 rhaas@postgresql.org 3193 : 3 : PG_RETURN_NULL();
3194 : :
3195 : 2046 : initStringInfo(&buf);
3196 : :
5610 peter_e@gmx.net 3197 : 2046 : (void) print_function_arguments(&buf, proctup, false, false);
3198 : :
3199 : 2046 : ReleaseSysCache(proctup);
3200 : :
3201 : 2046 : PG_RETURN_TEXT_P(string_to_text(buf.data));
3202 : : }
3203 : :
3204 : : /*
3205 : : * pg_get_function_result
3206 : : * Get a nicely-formatted version of the result type of a function.
3207 : : * This is what would appear after RETURNS in CREATE FUNCTION.
3208 : : */
3209 : : Datum
5749 tgl@sss.pgh.pa.us 3210 : 2013 : pg_get_function_result(PG_FUNCTION_ARGS)
3211 : : {
3212 : 2013 : Oid funcid = PG_GETARG_OID(0);
3213 : : StringInfoData buf;
3214 : : HeapTuple proctup;
3215 : :
5173 rhaas@postgresql.org 3216 : 2013 : proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
5749 tgl@sss.pgh.pa.us 3217 [ + + ]: 2013 : if (!HeapTupleIsValid(proctup))
2816 rhaas@postgresql.org 3218 : 3 : PG_RETURN_NULL();
3219 : :
2235 peter_e@gmx.net 3220 [ + + ]: 2010 : if (((Form_pg_proc) GETSTRUCT(proctup))->prokind == PROKIND_PROCEDURE)
3221 : : {
2327 3222 : 108 : ReleaseSysCache(proctup);
3223 : 108 : PG_RETURN_NULL();
3224 : : }
3225 : :
2816 rhaas@postgresql.org 3226 : 1902 : initStringInfo(&buf);
3227 : :
5699 tgl@sss.pgh.pa.us 3228 : 1902 : print_function_rettype(&buf, proctup);
3229 : :
3230 : 1902 : ReleaseSysCache(proctup);
3231 : :
3232 : 1902 : PG_RETURN_TEXT_P(string_to_text(buf.data));
3233 : : }
3234 : :
3235 : : /*
3236 : : * Guts of pg_get_function_result: append the function's return type
3237 : : * to the specified buffer.
3238 : : */
3239 : : static void
3240 : 1971 : print_function_rettype(StringInfo buf, HeapTuple proctup)
3241 : : {
3242 : 1971 : Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(proctup);
3243 : 1971 : int ntabargs = 0;
3244 : : StringInfoData rbuf;
3245 : :
3246 : 1971 : initStringInfo(&rbuf);
3247 : :
3248 [ + + ]: 1971 : if (proc->proretset)
3249 : : {
3250 : : /* It might be a table function; try to print the arguments */
3251 : 206 : appendStringInfoString(&rbuf, "TABLE(");
5596 3252 : 206 : ntabargs = print_function_arguments(&rbuf, proctup, true, false);
5749 3253 [ + + ]: 206 : if (ntabargs > 0)
3261 peter_e@gmx.net 3254 : 38 : appendStringInfoChar(&rbuf, ')');
3255 : : else
5699 tgl@sss.pgh.pa.us 3256 : 168 : resetStringInfo(&rbuf);
3257 : : }
3258 : :
5749 3259 [ + + ]: 1971 : if (ntabargs == 0)
3260 : : {
3261 : : /* Not a table function, so do the normal thing */
5699 3262 [ + + ]: 1933 : if (proc->proretset)
3263 : 168 : appendStringInfoString(&rbuf, "SETOF ");
3264 : 1933 : appendStringInfoString(&rbuf, format_type_be(proc->prorettype));
3265 : : }
3266 : :
1727 drowley@postgresql.o 3267 : 1971 : appendBinaryStringInfo(buf, rbuf.data, rbuf.len);
5749 tgl@sss.pgh.pa.us 3268 : 1971 : }
3269 : :
3270 : : /*
3271 : : * Common code for pg_get_function_arguments and pg_get_function_result:
3272 : : * append the desired subset of arguments to buf. We print only TABLE
3273 : : * arguments when print_table_args is true, and all the others when it's false.
3274 : : * We print argument defaults only if print_defaults is true.
3275 : : * Function return value is the number of arguments printed.
3276 : : */
3277 : : static int
3278 : 4637 : print_function_arguments(StringInfo buf, HeapTuple proctup,
3279 : : bool print_table_args, bool print_defaults)
3280 : : {
5596 3281 : 4637 : Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(proctup);
3282 : : int numargs;
3283 : : Oid *argtypes;
3284 : : char **argnames;
3285 : : char *argmodes;
3765 3286 : 4637 : int insertorderbyat = -1;
3287 : : int argsprinted;
3288 : : int inputargno;
3289 : : int nlackdefaults;
1735 3290 : 4637 : List *argdefaults = NIL;
5596 3291 : 4637 : ListCell *nextargdefault = NULL;
3292 : : int i;
3293 : :
5749 3294 : 4637 : numargs = get_func_arg_info(proctup,
3295 : : &argtypes, &argnames, &argmodes);
3296 : :
5596 3297 : 4637 : nlackdefaults = numargs;
3298 [ + + + + ]: 4637 : if (print_defaults && proc->pronargdefaults > 0)
3299 : : {
3300 : : Datum proargdefaults;
3301 : : bool isnull;
3302 : :
3303 : 19 : proargdefaults = SysCacheGetAttr(PROCOID, proctup,
3304 : : Anum_pg_proc_proargdefaults,
3305 : : &isnull);
3306 [ + - ]: 19 : if (!isnull)
3307 : : {
3308 : : char *str;
3309 : :
3310 : 19 : str = TextDatumGetCString(proargdefaults);
2609 peter_e@gmx.net 3311 : 19 : argdefaults = castNode(List, stringToNode(str));
5596 tgl@sss.pgh.pa.us 3312 : 19 : pfree(str);
3313 : 19 : nextargdefault = list_head(argdefaults);
3314 : : /* nlackdefaults counts only *input* arguments lacking defaults */
3315 : 19 : nlackdefaults = proc->pronargs - list_length(argdefaults);
3316 : : }
3317 : : }
3318 : :
3319 : : /* Check for special treatment of ordered-set aggregates */
2235 peter_e@gmx.net 3320 [ + + ]: 4637 : if (proc->prokind == PROKIND_AGGREGATE)
3321 : : {
3322 : : HeapTuple aggtup;
3323 : : Form_pg_aggregate agg;
3324 : :
269 michael@paquier.xyz 3325 :GNC 593 : aggtup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(proc->oid));
3765 tgl@sss.pgh.pa.us 3326 [ - + ]:CBC 593 : if (!HeapTupleIsValid(aggtup))
3765 tgl@sss.pgh.pa.us 3327 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for aggregate %u",
3328 : : proc->oid);
3765 tgl@sss.pgh.pa.us 3329 :CBC 593 : agg = (Form_pg_aggregate) GETSTRUCT(aggtup);
3330 [ + + ]: 593 : if (AGGKIND_IS_ORDERED_SET(agg->aggkind))
3331 : 26 : insertorderbyat = agg->aggnumdirectargs;
3332 : 593 : ReleaseSysCache(aggtup);
3333 : : }
3334 : :
5749 3335 : 4637 : argsprinted = 0;
5596 3336 : 4637 : inputargno = 0;
5749 3337 [ + + ]: 9261 : for (i = 0; i < numargs; i++)
3338 : : {
5421 bruce@momjian.us 3339 : 4624 : Oid argtype = argtypes[i];
3340 [ + + ]: 4624 : char *argname = argnames ? argnames[i] : NULL;
3341 [ + + ]: 4624 : char argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
3342 : : const char *modename;
3343 : : bool isinput;
3344 : :
5749 tgl@sss.pgh.pa.us 3345 [ + + + + : 4624 : switch (argmode)
+ - ]
3346 : : {
3347 : 3881 : case PROARGMODE_IN:
3348 : :
3349 : : /*
3350 : : * For procedures, explicitly mark all argument modes, so as
3351 : : * to avoid ambiguity with the SQL syntax for DROP PROCEDURE.
3352 : : */
1039 3353 [ + + ]: 3881 : if (proc->prokind == PROKIND_PROCEDURE)
3354 : 264 : modename = "IN ";
3355 : : else
3356 : 3617 : modename = "";
5596 3357 : 3881 : isinput = true;
5749 3358 : 3881 : break;
3359 : 20 : case PROARGMODE_INOUT:
3360 : 20 : modename = "INOUT ";
5596 3361 : 20 : isinput = true;
5749 3362 : 20 : break;
3363 : 448 : case PROARGMODE_OUT:
3364 : 448 : modename = "OUT ";
5596 3365 : 448 : isinput = false;
5749 3366 : 448 : break;
3367 : 89 : case PROARGMODE_VARIADIC:
3368 : 89 : modename = "VARIADIC ";
5596 3369 : 89 : isinput = true;
5749 3370 : 89 : break;
3371 : 186 : case PROARGMODE_TABLE:
3372 : 186 : modename = "";
5596 3373 : 186 : isinput = false;
5749 3374 : 186 : break;
5749 tgl@sss.pgh.pa.us 3375 :UBC 0 : default:
3376 [ # # ]: 0 : elog(ERROR, "invalid parameter mode '%c'", argmode);
3377 : : modename = NULL; /* keep compiler quiet */
3378 : : isinput = false;
3379 : : break;
3380 : : }
5596 tgl@sss.pgh.pa.us 3381 [ + + ]:CBC 4624 : if (isinput)
3382 : 3990 : inputargno++; /* this is a 1-based counter */
3383 : :
3384 [ + + ]: 4624 : if (print_table_args != (argmode == PROARGMODE_TABLE))
3385 : 364 : continue;
3386 : :
3765 3387 [ + + ]: 4260 : if (argsprinted == insertorderbyat)
3388 : : {
3389 [ + - ]: 26 : if (argsprinted)
3390 : 26 : appendStringInfoChar(buf, ' ');
3391 : 26 : appendStringInfoString(buf, "ORDER BY ");
3392 : : }
3393 [ + + ]: 4234 : else if (argsprinted)
5749 3394 : 1330 : appendStringInfoString(buf, ", ");
3395 : :
3396 : 4260 : appendStringInfoString(buf, modename);
5596 3397 [ + + + + ]: 4260 : if (argname && argname[0])
5423 3398 : 1473 : appendStringInfo(buf, "%s ", quote_identifier(argname));
5749 3399 : 4260 : appendStringInfoString(buf, format_type_be(argtype));
5596 3400 [ + + + + : 4260 : if (print_defaults && isinput && inputargno > nlackdefaults)
+ + ]
3401 : : {
3402 : : Node *expr;
3403 : :
3404 [ - + ]: 29 : Assert(nextargdefault != NULL);
3405 : 29 : expr = (Node *) lfirst(nextargdefault);
1735 3406 : 29 : nextargdefault = lnext(argdefaults, nextargdefault);
3407 : :
5596 3408 : 29 : appendStringInfo(buf, " DEFAULT %s",
3409 : : deparse_expression(expr, NIL, false, false));
3410 : : }
5749 3411 : 4260 : argsprinted++;
3412 : :
3413 : : /* nasty hack: print the last arg twice for variadic ordered-set agg */
3765 3414 [ + + + + ]: 4260 : if (argsprinted == insertorderbyat && i == numargs - 1)
3415 : : {
3416 : 13 : i--;
3417 : : /* aggs shouldn't have defaults anyway, but just to be sure ... */
3418 : 13 : print_defaults = false;
3419 : : }
3420 : : }
3421 : :
5749 3422 : 4637 : return argsprinted;
3423 : : }
3424 : :
3425 : : static bool
3792 peter_e@gmx.net 3426 : 48 : is_input_argument(int nth, const char *argmodes)
3427 : : {
3428 : : return (!argmodes
3429 [ + + ]: 21 : || argmodes[nth] == PROARGMODE_IN
3430 [ + - ]: 9 : || argmodes[nth] == PROARGMODE_INOUT
3431 [ + + - + ]: 69 : || argmodes[nth] == PROARGMODE_VARIADIC);
3432 : : }
3433 : :
3434 : : /*
3435 : : * Append used transformed types to specified buffer
3436 : : */
3437 : : static void
3276 3438 : 79 : print_function_trftypes(StringInfo buf, HeapTuple proctup)
3439 : : {
3440 : : Oid *trftypes;
3441 : : int ntypes;
3442 : :
3443 : 79 : ntypes = get_func_trftypes(proctup, &trftypes);
3444 [ + + ]: 79 : if (ntypes > 0)
3445 : : {
3446 : : int i;
3447 : :
1175 tgl@sss.pgh.pa.us 3448 : 3 : appendStringInfoString(buf, " TRANSFORM ");
3276 peter_e@gmx.net 3449 [ + + ]: 8 : for (i = 0; i < ntypes; i++)
3450 : : {
3451 [ + + ]: 5 : if (i != 0)
3452 : 2 : appendStringInfoString(buf, ", ");
3265 magnus@hagander.net 3453 : 5 : appendStringInfo(buf, "FOR TYPE %s", format_type_be(trftypes[i]));
3454 : : }
1175 tgl@sss.pgh.pa.us 3455 : 3 : appendStringInfoChar(buf, '\n');
3456 : : }
3276 peter_e@gmx.net 3457 : 79 : }
3458 : :
3459 : : /*
3460 : : * Get textual representation of a function argument's default value. The
3461 : : * second argument of this function is the argument number among all arguments
3462 : : * (i.e. proallargtypes, *not* proargtypes), starting with 1, because that's
3463 : : * how information_schema.sql uses it.
3464 : : */
3465 : : Datum
3792 3466 : 27 : pg_get_function_arg_default(PG_FUNCTION_ARGS)
3467 : : {
3468 : 27 : Oid funcid = PG_GETARG_OID(0);
3469 : 27 : int32 nth_arg = PG_GETARG_INT32(1);
3470 : : HeapTuple proctup;
3471 : : Form_pg_proc proc;
3472 : : int numargs;
3473 : : Oid *argtypes;
3474 : : char **argnames;
3475 : : char *argmodes;
3476 : : int i;
3477 : : List *argdefaults;
3478 : : Node *node;
3479 : : char *str;
3480 : : int nth_inputarg;
3481 : : Datum proargdefaults;
3482 : : bool isnull;
3483 : : int nth_default;
3484 : :
3485 : 27 : proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
3486 [ + + ]: 27 : if (!HeapTupleIsValid(proctup))
2816 rhaas@postgresql.org 3487 : 6 : PG_RETURN_NULL();
3488 : :
3792 peter_e@gmx.net 3489 : 21 : numargs = get_func_arg_info(proctup, &argtypes, &argnames, &argmodes);
3490 [ + - + - : 21 : if (nth_arg < 1 || nth_arg > numargs || !is_input_argument(nth_arg - 1, argmodes))
+ + ]
3491 : : {
3492 : 6 : ReleaseSysCache(proctup);
3493 : 6 : PG_RETURN_NULL();
3494 : : }
3495 : :
3496 : 15 : nth_inputarg = 0;
3497 [ + + ]: 42 : for (i = 0; i < nth_arg; i++)
3498 [ + + ]: 27 : if (is_input_argument(i, argmodes))
3499 : 24 : nth_inputarg++;
3500 : :
3501 : 15 : proargdefaults = SysCacheGetAttr(PROCOID, proctup,
3502 : : Anum_pg_proc_proargdefaults,
3503 : : &isnull);
3504 [ - + ]: 15 : if (isnull)
3505 : : {
3792 peter_e@gmx.net 3506 :UBC 0 : ReleaseSysCache(proctup);
3507 : 0 : PG_RETURN_NULL();
3508 : : }
3509 : :
3792 peter_e@gmx.net 3510 :CBC 15 : str = TextDatumGetCString(proargdefaults);
2609 3511 : 15 : argdefaults = castNode(List, stringToNode(str));
3792 3512 : 15 : pfree(str);
3513 : :
3514 : 15 : proc = (Form_pg_proc) GETSTRUCT(proctup);
3515 : :
3516 : : /*
3517 : : * Calculate index into proargdefaults: proargdefaults corresponds to the
3518 : : * last N input arguments, where N = pronargdefaults.
3519 : : */
3520 : 15 : nth_default = nth_inputarg - 1 - (proc->pronargs - proc->pronargdefaults);
3521 : :
3522 [ + + - + ]: 15 : if (nth_default < 0 || nth_default >= list_length(argdefaults))
3523 : : {
3524 : 3 : ReleaseSysCache(proctup);
3525 : 3 : PG_RETURN_NULL();
3526 : : }
3527 : 12 : node = list_nth(argdefaults, nth_default);
3528 : 12 : str = deparse_expression(node, NIL, false, false);
3529 : :
3530 : 12 : ReleaseSysCache(proctup);
3531 : :
3532 : 12 : PG_RETURN_TEXT_P(string_to_text(str));
3533 : : }
3534 : :
3535 : : static void
1103 peter@eisentraut.org 3536 : 105 : print_function_sqlbody(StringInfo buf, HeapTuple proctup)
3537 : : {
3538 : : int numargs;
3539 : : Oid *argtypes;
3540 : : char **argnames;
3541 : : char *argmodes;
3542 : 105 : deparse_namespace dpns = {0};
3543 : : Datum tmp;
3544 : : Node *n;
3545 : :
3546 : 105 : dpns.funcname = pstrdup(NameStr(((Form_pg_proc) GETSTRUCT(proctup))->proname));
3547 : 105 : numargs = get_func_arg_info(proctup,
3548 : : &argtypes, &argnames, &argmodes);
3549 : 105 : dpns.numargs = numargs;
3550 : 105 : dpns.argnames = argnames;
3551 : :
386 dgustafsson@postgres 3552 : 105 : tmp = SysCacheGetAttrNotNull(PROCOID, proctup, Anum_pg_proc_prosqlbody);
1103 peter@eisentraut.org 3553 : 105 : n = stringToNode(TextDatumGetCString(tmp));
3554 : :
3555 [ + + ]: 105 : if (IsA(n, List))
3556 : : {
3557 : : List *stmts;
3558 : : ListCell *lc;
3559 : :
3560 : 82 : stmts = linitial(castNode(List, n));
3561 : :
3562 : 82 : appendStringInfoString(buf, "BEGIN ATOMIC\n");
3563 : :
3564 [ + + + + : 159 : foreach(lc, stmts)
+ + ]
3565 : : {
3566 : 77 : Query *query = lfirst_node(Query, lc);
3567 : :
3568 : : /* It seems advisable to get at least AccessShareLock on rels */
957 tgl@sss.pgh.pa.us 3569 : 77 : AcquireRewriteLocks(query, false, false);
694 3570 : 77 : get_query_def(query, buf, list_make1(&dpns), NULL, false,
3571 : : PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
1103 peter@eisentraut.org 3572 : 77 : appendStringInfoChar(buf, ';');
3573 : 77 : appendStringInfoChar(buf, '\n');
3574 : : }
3575 : :
3576 : 82 : appendStringInfoString(buf, "END");
3577 : : }
3578 : : else
3579 : : {
957 tgl@sss.pgh.pa.us 3580 : 23 : Query *query = castNode(Query, n);
3581 : :
3582 : : /* It seems advisable to get at least AccessShareLock on rels */
3583 : 23 : AcquireRewriteLocks(query, false, false);
694 3584 : 23 : get_query_def(query, buf, list_make1(&dpns), NULL, false,
3585 : : 0, WRAP_COLUMN_DEFAULT, 0);
3586 : : }
1103 peter@eisentraut.org 3587 : 105 : }
3588 : :
3589 : : Datum
3590 : 1757 : pg_get_function_sqlbody(PG_FUNCTION_ARGS)
3591 : : {
3592 : 1757 : Oid funcid = PG_GETARG_OID(0);
3593 : : StringInfoData buf;
3594 : : HeapTuple proctup;
3595 : : bool isnull;
3596 : :
3597 : 1757 : initStringInfo(&buf);
3598 : :
3599 : : /* Look up the function */
3600 : 1757 : proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
3601 [ - + ]: 1757 : if (!HeapTupleIsValid(proctup))
1103 peter@eisentraut.org 3602 :UBC 0 : PG_RETURN_NULL();
3603 : :
957 tgl@sss.pgh.pa.us 3604 :CBC 1757 : (void) SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
1103 peter@eisentraut.org 3605 [ + + ]: 1757 : if (isnull)
3606 : : {
3607 : 1705 : ReleaseSysCache(proctup);
3608 : 1705 : PG_RETURN_NULL();
3609 : : }
3610 : :
3611 : 52 : print_function_sqlbody(&buf, proctup);
3612 : :
3613 : 52 : ReleaseSysCache(proctup);
3614 : :
586 drowley@postgresql.o 3615 : 52 : PG_RETURN_TEXT_P(cstring_to_text_with_len(buf.data, buf.len));
3616 : : }
3617 : :
3618 : :
3619 : : /*
3620 : : * deparse_expression - General utility for deparsing expressions
3621 : : *
3622 : : * calls deparse_expression_pretty with all prettyPrinting disabled
3623 : : */
3624 : : char *
7564 tgl@sss.pgh.pa.us 3625 : 31414 : deparse_expression(Node *expr, List *dpcontext,
3626 : : bool forceprefix, bool showimplicit)
3627 : : {
7559 bruce@momjian.us 3628 : 31414 : return deparse_expression_pretty(expr, dpcontext, forceprefix,
3629 : : showimplicit, 0, 0);
3630 : : }
3631 : :
3632 : : /* ----------
3633 : : * deparse_expression_pretty - General utility for deparsing expressions
3634 : : *
3635 : : * expr is the node tree to be deparsed. It must be a transformed expression
3636 : : * tree (ie, not the raw output of gram.y).
3637 : : *
3638 : : * dpcontext is a list of deparse_namespace nodes representing the context
3639 : : * for interpreting Vars in the node tree. It can be NIL if no Vars are
3640 : : * expected.
3641 : : *
3642 : : * forceprefix is true to force all Vars to be prefixed with their table names.
3643 : : *
3644 : : * showimplicit is true to force all implicit casts to be shown explicitly.
3645 : : *
3646 : : * Tries to pretty up the output according to prettyFlags and startIndent.
3647 : : *
3648 : : * The result is a palloc'd string.
3649 : : * ----------
3650 : : */
3651 : : static char *
7564 tgl@sss.pgh.pa.us 3652 : 37252 : deparse_expression_pretty(Node *expr, List *dpcontext,
3653 : : bool forceprefix, bool showimplicit,
3654 : : int prettyFlags, int startIndent)
3655 : : {
3656 : : StringInfoData buf;
3657 : : deparse_context context;
3658 : :
8960 3659 : 37252 : initStringInfo(&buf);
3660 : 37252 : context.buf = &buf;
8460 3661 : 37252 : context.namespaces = dpcontext;
5586 3662 : 37252 : context.windowClause = NIL;
3663 : 37252 : context.windowTList = NIL;
8460 3664 : 37252 : context.varprefix = forceprefix;
7564 3665 : 37252 : context.prettyFlags = prettyFlags;
4129 3666 : 37252 : context.wrapColumn = WRAP_COLUMN_DEFAULT;
7564 3667 : 37252 : context.indentLevel = startIndent;
3256 andres@anarazel.de 3668 : 37252 : context.special_exprkind = EXPR_KIND_NONE;
1586 tgl@sss.pgh.pa.us 3669 : 37252 : context.appendparents = NULL;
3670 : :
7878 3671 : 37252 : get_rule_expr(expr, &context, showimplicit);
3672 : :
8960 3673 : 37252 : return buf.data;
3674 : : }
3675 : :
3676 : : /* ----------
3677 : : * deparse_context_for - Build deparse context for a single relation
3678 : : *
3679 : : * Given the reference name (alias) and OID of a relation, build deparsing
3680 : : * context for an expression referencing only that relation (as varno 1,
3681 : : * varlevelsup 0). This is sufficient for many uses of deparse_expression.
3682 : : * ----------
3683 : : */
3684 : : List *
8059 3685 : 10504 : deparse_context_for(const char *aliasname, Oid relid)
3686 : : {
3687 : : deparse_namespace *dpns;
3688 : : RangeTblEntry *rte;
3689 : :
5024 3690 : 10504 : dpns = (deparse_namespace *) palloc0(sizeof(deparse_namespace));
3691 : :
3692 : : /* Build a minimal RTE for the rel */
8460 3693 : 10504 : rte = makeNode(RangeTblEntry);
8069 3694 : 10504 : rte->rtekind = RTE_RELATION;
8460 3695 : 10504 : rte->relid = relid;
4800 3696 : 10504 : rte->relkind = RELKIND_RELATION; /* no need for exactness here */
2023 3697 : 10504 : rte->rellockmode = AccessShareLock;
4223 3698 : 10504 : rte->alias = makeAlias(aliasname, NIL);
3699 : 10504 : rte->eref = rte->alias;
4268 3700 : 10504 : rte->lateral = false;
8460 3701 : 10504 : rte->inh = false;
3702 : 10504 : rte->inFromCl = true;
3703 : :
3704 : : /* Build one-element rtable */
7259 neilc@samurai.com 3705 : 10504 : dpns->rtable = list_make1(rte);
1586 tgl@sss.pgh.pa.us 3706 : 10504 : dpns->subplans = NIL;
5669 3707 : 10504 : dpns->ctes = NIL;
1586 3708 : 10504 : dpns->appendrels = NULL;
4223 3709 : 10504 : set_rtable_names(dpns, NIL, NULL);
4122 3710 : 10504 : set_simple_column_names(dpns);
3711 : :
3712 : : /* Return a one-deep namespace stack */
7259 neilc@samurai.com 3713 : 10504 : return list_make1(dpns);
3714 : : }
3715 : :
3716 : : /*
3717 : : * deparse_context_for_plan_tree - Build deparse context for a Plan tree
3718 : : *
3719 : : * When deparsing an expression in a Plan tree, we use the plan's rangetable
3720 : : * to resolve names of simple Vars. The initialization of column names for
3721 : : * this is rather expensive if the rangetable is large, and it'll be the same
3722 : : * for every expression in the Plan tree; so we do it just once and re-use
3723 : : * the result of this function for each expression. (Note that the result
3724 : : * is not usable until set_deparse_context_plan() is applied to it.)
3725 : : *
3726 : : * In addition to the PlannedStmt, pass the per-RTE alias names
3727 : : * assigned by a previous call to select_rtable_names_for_explain.
3728 : : */
3729 : : List *
1586 tgl@sss.pgh.pa.us 3730 : 11016 : deparse_context_for_plan_tree(PlannedStmt *pstmt, List *rtable_names)
3731 : : {
3732 : : deparse_namespace *dpns;
3733 : :
3377 3734 : 11016 : dpns = (deparse_namespace *) palloc0(sizeof(deparse_namespace));
3735 : :
3736 : : /* Initialize fields that stay the same across the whole plan tree */
1586 3737 : 11016 : dpns->rtable = pstmt->rtable;
3377 3738 : 11016 : dpns->rtable_names = rtable_names;
1586 3739 : 11016 : dpns->subplans = pstmt->subplans;
3377 3740 : 11016 : dpns->ctes = NIL;
1586 3741 [ + + ]: 11016 : if (pstmt->appendRelations)
3742 : : {
3743 : : /* Set up the array, indexed by child relid */
3744 : 1785 : int ntables = list_length(dpns->rtable);
3745 : : ListCell *lc;
3746 : :
3747 : 1785 : dpns->appendrels = (AppendRelInfo **)
3748 : 1785 : palloc0((ntables + 1) * sizeof(AppendRelInfo *));
3749 [ + - + + : 9806 : foreach(lc, pstmt->appendRelations)
+ + ]
3750 : : {
3751 : 8021 : AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
3752 : 8021 : Index crelid = appinfo->child_relid;
3753 : :
3754 [ + - - + ]: 8021 : Assert(crelid > 0 && crelid <= ntables);
3755 [ - + ]: 8021 : Assert(dpns->appendrels[crelid] == NULL);
3756 : 8021 : dpns->appendrels[crelid] = appinfo;
3757 : : }
3758 : : }
3759 : : else
3760 : 9231 : dpns->appendrels = NULL; /* don't need it */
3761 : :
3762 : : /*
3763 : : * Set up column name aliases. We will get rather bogus results for join
3764 : : * RTEs, but that doesn't matter because plan trees don't contain any join
3765 : : * alias Vars.
3766 : : */
3377 3767 : 11016 : set_simple_column_names(dpns);
3768 : :
3769 : : /* Return a one-deep namespace stack */
3770 : 11016 : return list_make1(dpns);
3771 : : }
3772 : :
3773 : : /*
3774 : : * set_deparse_context_plan - Specify Plan node containing expression
3775 : : *
3776 : : * When deparsing an expression in a Plan tree, we might have to resolve
3777 : : * OUTER_VAR, INNER_VAR, or INDEX_VAR references. To do this, the caller must
3778 : : * provide the parent Plan node. Then OUTER_VAR and INNER_VAR references
3779 : : * can be resolved by drilling down into the left and right child plans.
3780 : : * Similarly, INDEX_VAR references can be resolved by reference to the
3781 : : * indextlist given in a parent IndexOnlyScan node, or to the scan tlist in
3782 : : * ForeignScan and CustomScan nodes. (Note that we don't currently support
3783 : : * deparsing of indexquals in regular IndexScan or BitmapIndexScan nodes;
3784 : : * for those, we can only deparse the indexqualorig fields, which won't
3785 : : * contain INDEX_VAR Vars.)
3786 : : *
3787 : : * The ancestors list is a list of the Plan's parent Plan and SubPlan nodes,
3788 : : * the most-closely-nested first. This is needed to resolve PARAM_EXEC
3789 : : * Params. Note we assume that all the Plan nodes share the same rtable.
3790 : : *
3791 : : * Once this function has been called, deparse_expression() can be called on
3792 : : * subsidiary expression(s) of the specified Plan node. To deparse
3793 : : * expressions of a different Plan node in the same Plan tree, re-call this
3794 : : * function to identify the new parent Plan node.
3795 : : *
3796 : : * The result is the same List passed in; this is a notational convenience.
3797 : : */
3798 : : List *
1586 3799 : 23718 : set_deparse_context_plan(List *dpcontext, Plan *plan, List *ancestors)
3800 : : {
3801 : : deparse_namespace *dpns;
3802 : :
3803 : : /* Should always have one-entry namespace list for Plan deparsing */
3377 3804 [ - + ]: 23718 : Assert(list_length(dpcontext) == 1);
3805 : 23718 : dpns = (deparse_namespace *) linitial(dpcontext);
3806 : :
3807 : : /* Set our attention on the specific plan node passed in */
5024 3808 : 23718 : dpns->ancestors = ancestors;
916 3809 : 23718 : set_deparse_plan(dpns, plan);
3810 : :
3377 3811 : 23718 : return dpcontext;
3812 : : }
3813 : :
3814 : : /*
3815 : : * select_rtable_names_for_explain - Select RTE aliases for EXPLAIN
3816 : : *
3817 : : * Determine the relation aliases we'll use during an EXPLAIN operation.
3818 : : * This is just a frontend to set_rtable_names. We have to expose the aliases
3819 : : * to EXPLAIN because EXPLAIN needs to know the right alias names to print.
3820 : : */
3821 : : List *
4223 3822 : 11016 : select_rtable_names_for_explain(List *rtable, Bitmapset *rels_used)
3823 : : {
3824 : : deparse_namespace dpns;
3825 : :
3826 : 11016 : memset(&dpns, 0, sizeof(dpns));
3827 : 11016 : dpns.rtable = rtable;
1586 3828 : 11016 : dpns.subplans = NIL;
4223 3829 : 11016 : dpns.ctes = NIL;
1586 3830 : 11016 : dpns.appendrels = NULL;
4223 3831 : 11016 : set_rtable_names(&dpns, NIL, rels_used);
3832 : : /* We needn't bother computing column aliases yet */
3833 : :
3834 : 11016 : return dpns.rtable_names;
3835 : : }
3836 : :
3837 : : /*
3838 : : * set_rtable_names: select RTE aliases to be used in printing a query
3839 : : *
3840 : : * We fill in dpns->rtable_names with a list of names that is one-for-one with
3841 : : * the already-filled dpns->rtable list. Each RTE name is unique among those
3842 : : * in the new namespace plus any ancestor namespaces listed in
3843 : : * parent_namespaces.
3844 : : *
3845 : : * If rels_used isn't NULL, only RTE indexes listed in it are given aliases.
3846 : : *
3847 : : * Note that this function is only concerned with relation names, not column
3848 : : * names.
3849 : : */
3850 : : static void
3851 : 24219 : set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
3852 : : Bitmapset *rels_used)
3853 : : {
3854 : : HASHCTL hash_ctl;
3855 : : HTAB *names_hash;
3856 : : NameHashEntry *hentry;
3857 : : bool found;
3858 : : int rtindex;
3859 : : ListCell *lc;
3860 : :
3861 : 24219 : dpns->rtable_names = NIL;
3862 : : /* nothing more to do if empty rtable */
3072 3863 [ + + ]: 24219 : if (dpns->rtable == NIL)
3864 : 257 : return;
3865 : :
3866 : : /*
3867 : : * We use a hash table to hold known names, so that this process is O(N)
3868 : : * not O(N^2) for N names.
3869 : : */
3870 : 23962 : hash_ctl.keysize = NAMEDATALEN;
3871 : 23962 : hash_ctl.entrysize = sizeof(NameHashEntry);
3872 : 23962 : hash_ctl.hcxt = CurrentMemoryContext;
3873 : 23962 : names_hash = hash_create("set_rtable_names names",
3874 : 23962 : list_length(dpns->rtable),
3875 : : &hash_ctl,
3876 : : HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);
3877 : :
3878 : : /* Preload the hash table with names appearing in parent_namespaces */
3879 [ + + + + : 24720 : foreach(lc, parent_namespaces)
+ + ]
3880 : : {
3881 : 758 : deparse_namespace *olddpns = (deparse_namespace *) lfirst(lc);
3882 : : ListCell *lc2;
3883 : :
3884 [ + + + + : 2562 : foreach(lc2, olddpns->rtable_names)
+ + ]
3885 : : {
3886 : 1804 : char *oldname = (char *) lfirst(lc2);
3887 : :
3888 [ + + ]: 1804 : if (oldname == NULL)
3889 : 142 : continue;
3890 : 1662 : hentry = (NameHashEntry *) hash_search(names_hash,
3891 : : oldname,
3892 : : HASH_ENTER,
3893 : : &found);
3894 : : /* we do not complain about duplicate names in parent namespaces */
3895 : 1662 : hentry->counter = 0;
3896 : : }
3897 : : }
3898 : :
3899 : : /* Now we can scan the rtable */
3900 : 23962 : rtindex = 1;
4223 3901 [ + - + + : 67995 : foreach(lc, dpns->rtable)
+ + ]
3902 : : {
3903 : 44033 : RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
3904 : : char *refname;
3905 : :
3906 : : /* Just in case this takes an unreasonable amount of time ... */
3072 3907 [ + + ]: 44033 : CHECK_FOR_INTERRUPTS();
3908 : :
4223 3909 [ + + + + ]: 44033 : if (rels_used && !bms_is_member(rtindex, rels_used))
3910 : : {
3911 : : /* Ignore unreferenced RTE */
3912 : 7255 : refname = NULL;
3913 : : }
3914 [ + + ]: 36778 : else if (rte->alias)
3915 : : {
3916 : : /* If RTE has a user-defined alias, prefer that */
3917 : 24537 : refname = rte->alias->aliasname;
3918 : : }
3919 [ + + ]: 12241 : else if (rte->rtekind == RTE_RELATION)
3920 : : {
3921 : : /* Use the current actual name of the relation */
3922 : 10357 : refname = get_rel_name(rte->relid);
3923 : : }
3924 [ + + ]: 1884 : else if (rte->rtekind == RTE_JOIN)
3925 : : {
3926 : : /* Unnamed join has no refname */
3927 : 754 : refname = NULL;
3928 : : }
3929 : : else
3930 : : {
3931 : : /* Otherwise use whatever the parser assigned */
3932 : 1130 : refname = rte->eref->aliasname;
3933 : : }
3934 : :
3935 : : /*
3936 : : * If the selected name isn't unique, append digits to make it so, and
3937 : : * make a new hash entry for it once we've got a unique name. For a
3938 : : * very long input name, we might have to truncate to stay within
3939 : : * NAMEDATALEN.
3940 : : */
3072 3941 [ + + ]: 44033 : if (refname)
3942 : : {
3943 : 36024 : hentry = (NameHashEntry *) hash_search(names_hash,
3944 : : refname,
3945 : : HASH_ENTER,
3946 : : &found);
3947 [ + + ]: 36024 : if (found)
3948 : : {
3949 : : /* Name already in use, must choose a new one */
3950 : 6544 : int refnamelen = strlen(refname);
3951 : 6544 : char *modname = (char *) palloc(refnamelen + 16);
3952 : : NameHashEntry *hentry2;
3953 : :
3954 : : do
3955 : : {
3956 : 6547 : hentry->counter++;
3957 : : for (;;)
3958 : : {
3959 : 6553 : memcpy(modname, refname, refnamelen);
3960 : 6553 : sprintf(modname + refnamelen, "_%d", hentry->counter);
3961 [ + + ]: 6553 : if (strlen(modname) < NAMEDATALEN)
3962 : 6547 : break;
3963 : : /* drop chars from refname to keep all the digits */
3964 : 6 : refnamelen = pg_mbcliplen(refname, refnamelen,
3965 : : refnamelen - 1);
3966 : : }
3967 : 6547 : hentry2 = (NameHashEntry *) hash_search(names_hash,
3968 : : modname,
3969 : : HASH_ENTER,
3970 : : &found);
3971 [ + + ]: 6547 : } while (found);
3972 : 6544 : hentry2->counter = 0; /* init new hash entry */
3973 : 6544 : refname = modname;
3974 : : }
3975 : : else
3976 : : {
3977 : : /* Name not previously used, need only initialize hentry */
3978 : 29480 : hentry->counter = 0;
3979 : : }
3980 : : }
3981 : :
4223 3982 : 44033 : dpns->rtable_names = lappend(dpns->rtable_names, refname);
3983 : 44033 : rtindex++;
3984 : : }
3985 : :
3072 3986 : 23962 : hash_destroy(names_hash);
3987 : : }
3988 : :
3989 : : /*
3990 : : * set_deparse_for_query: set up deparse_namespace for deparsing a Query tree
3991 : : *
3992 : : * For convenience, this is defined to initialize the deparse_namespace struct
3993 : : * from scratch.
3994 : : */
3995 : : static void
4122 3996 : 2627 : set_deparse_for_query(deparse_namespace *dpns, Query *query,
3997 : : List *parent_namespaces)
3998 : : {
3999 : : ListCell *lc;
4000 : : ListCell *lc2;
4001 : :
4002 : : /* Initialize *dpns and fill rtable/ctes links */
4003 : 2627 : memset(dpns, 0, sizeof(deparse_namespace));
4004 : 2627 : dpns->rtable = query->rtable;
1586 4005 : 2627 : dpns->subplans = NIL;
4122 4006 : 2627 : dpns->ctes = query->cteList;
1586 4007 : 2627 : dpns->appendrels = NULL;
4008 : :
4009 : : /* Assign a unique relation alias to each RTE */
4122 4010 : 2627 : set_rtable_names(dpns, parent_namespaces, NULL);
4011 : :
4012 : : /* Initialize dpns->rtable_columns to contain zeroed structs */
4013 : 2627 : dpns->rtable_columns = NIL;
4014 [ + + ]: 7122 : while (list_length(dpns->rtable_columns) < list_length(dpns->rtable))
4015 : 4495 : dpns->rtable_columns = lappend(dpns->rtable_columns,
4016 : : palloc0(sizeof(deparse_columns)));
4017 : :
4018 : : /* If it's a utility query, it won't have a jointree */
3986 4019 [ + + ]: 2627 : if (query->jointree)
4020 : : {
4021 : : /* Detect whether global uniqueness of USING names is needed */
4022 : 2619 : dpns->unique_using =
3918 4023 : 2619 : has_dangerous_join_using(dpns, (Node *) query->jointree);
4024 : :
4025 : : /*
4026 : : * Select names for columns merged by USING, via a recursive pass over
4027 : : * the query jointree.
4028 : : */
3636 4029 : 2619 : set_using_names(dpns, (Node *) query->jointree, NIL);
4030 : : }
4031 : :
4032 : : /*
4033 : : * Now assign remaining column aliases for each RTE. We do this in a
4034 : : * linear scan of the rtable, so as to process RTEs whether or not they
4035 : : * are in the jointree (we mustn't miss NEW.*, INSERT target relations,
4036 : : * etc). JOIN RTEs must be processed after their children, but this is
4037 : : * okay because they appear later in the rtable list than their children
4038 : : * (cf Asserts in identify_join_columns()).
4039 : : */
4122 4040 [ + + + + : 7122 : forboth(lc, dpns->rtable, lc2, dpns->rtable_columns)
+ + + + +
+ + - +
+ ]
4041 : : {
4042 : 4495 : RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
4043 : 4495 : deparse_columns *colinfo = (deparse_columns *) lfirst(lc2);
4044 : :
4045 [ + + ]: 4495 : if (rte->rtekind == RTE_JOIN)
4046 : 585 : set_join_column_names(dpns, rte, colinfo);
4047 : : else
4048 : 3910 : set_relation_column_names(dpns, rte, colinfo);
4049 : : }
4050 : 2627 : }
4051 : :
4052 : : /*
4053 : : * set_simple_column_names: fill in column aliases for non-query situations
4054 : : *
4055 : : * This handles EXPLAIN and cases where we only have relation RTEs. Without
4056 : : * a join tree, we can't do anything smart about join RTEs, but we don't
4057 : : * need to (note that EXPLAIN should never see join alias Vars anyway).
4058 : : * If we do hit a join RTE we'll just process it like a non-table base RTE.
4059 : : */
4060 : : static void
4061 : 21592 : set_simple_column_names(deparse_namespace *dpns)
4062 : : {
4063 : : ListCell *lc;
4064 : : ListCell *lc2;
4065 : :
4066 : : /* Initialize dpns->rtable_columns to contain zeroed structs */
4067 : 21592 : dpns->rtable_columns = NIL;
4068 [ + + ]: 61130 : while (list_length(dpns->rtable_columns) < list_length(dpns->rtable))
4069 : 39538 : dpns->rtable_columns = lappend(dpns->rtable_columns,
4070 : : palloc0(sizeof(deparse_columns)));
4071 : :
4072 : : /* Assign unique column aliases within each RTE */
4073 [ + - + + : 61130 : forboth(lc, dpns->rtable, lc2, dpns->rtable_columns)
+ - + + +
+ + - +
+ ]
4074 : : {
4075 : 39538 : RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
4076 : 39538 : deparse_columns *colinfo = (deparse_columns *) lfirst(lc2);
4077 : :
4078 : 39538 : set_relation_column_names(dpns, rte, colinfo);
4079 : : }
4080 : 21592 : }
4081 : :
4082 : : /*
4083 : : * has_dangerous_join_using: search jointree for unnamed JOIN USING
4084 : : *
4085 : : * Merged columns of a JOIN USING may act differently from either of the input
4086 : : * columns, either because they are merged with COALESCE (in a FULL JOIN) or
4087 : : * because an implicit coercion of the underlying input column is required.
4088 : : * In such a case the column must be referenced as a column of the JOIN not as
4089 : : * a column of either input. And this is problematic if the join is unnamed
4090 : : * (alias-less): we cannot qualify the column's name with an RTE name, since
4091 : : * there is none. (Forcibly assigning an alias to the join is not a solution,
4092 : : * since that will prevent legal references to tables below the join.)
4093 : : * To ensure that every column in the query is unambiguously referenceable,
4094 : : * we must assign such merged columns names that are globally unique across
4095 : : * the whole query, aliasing other columns out of the way as necessary.
4096 : : *
4097 : : * Because the ensuing re-aliasing is fairly damaging to the readability of
4098 : : * the query, we don't do this unless we have to. So, we must pre-scan
4099 : : * the join tree to see if we have to, before starting set_using_names().
4100 : : */
4101 : : static bool
3918 4102 : 6010 : has_dangerous_join_using(deparse_namespace *dpns, Node *jtnode)
4103 : : {
4122 4104 [ + + ]: 6010 : if (IsA(jtnode, RangeTblRef))
4105 : : {
4106 : : /* nothing to do here */
4107 : : }
4108 [ + + ]: 3171 : else if (IsA(jtnode, FromExpr))
4109 : : {
4110 : 2619 : FromExpr *f = (FromExpr *) jtnode;
4111 : : ListCell *lc;
4112 : :
4113 [ + + + + : 4942 : foreach(lc, f->fromlist)
+ + ]
4114 : : {
3918 4115 [ + + ]: 2359 : if (has_dangerous_join_using(dpns, (Node *) lfirst(lc)))
4122 4116 : 36 : return true;
4117 : : }
4118 : : }
4119 [ + - ]: 552 : else if (IsA(jtnode, JoinExpr))
4120 : : {
4121 : 552 : JoinExpr *j = (JoinExpr *) jtnode;
4122 : :
4123 : : /* Is it an unnamed JOIN with USING? */
3918 4124 [ + + + + ]: 552 : if (j->alias == NULL && j->usingClause)
4125 : : {
4126 : : /*
4127 : : * Yes, so check each join alias var to see if any of them are not
4128 : : * simple references to underlying columns. If so, we have a
4129 : : * dangerous situation and must pick unique aliases.
4130 : : */
4131 : 143 : RangeTblEntry *jrte = rt_fetch(j->rtindex, dpns->rtable);
4132 : :
4133 : : /* We need only examine the merged columns */
1557 4134 [ + + ]: 298 : for (int i = 0; i < jrte->joinmergedcols; i++)
4135 : : {
4136 : 191 : Node *aliasvar = list_nth(jrte->joinaliasvars, i);
4137 : :
4138 [ + + ]: 191 : if (!IsA(aliasvar, Var))
3918 4139 : 36 : return true;
4140 : : }
4141 : : }
4142 : :
4143 : : /* Nope, but inspect children */
4144 [ - + ]: 516 : if (has_dangerous_join_using(dpns, j->larg))
4122 tgl@sss.pgh.pa.us 4145 :UBC 0 : return true;
3918 tgl@sss.pgh.pa.us 4146 [ - + ]:CBC 516 : if (has_dangerous_join_using(dpns, j->rarg))
4122 tgl@sss.pgh.pa.us 4147 :UBC 0 : return true;
4148 : : }
4149 : : else
4150 [ # # ]: 0 : elog(ERROR, "unrecognized node type: %d",
4151 : : (int) nodeTag(jtnode));
4122 tgl@sss.pgh.pa.us 4152 :CBC 5938 : return false;
4153 : : }
4154 : :
4155 : : /*
4156 : : * set_using_names: select column aliases to be used for merged USING columns
4157 : : *
4158 : : * We do this during a recursive descent of the query jointree.
4159 : : * dpns->unique_using must already be set to determine the global strategy.
4160 : : *
4161 : : * Column alias info is saved in the dpns->rtable_columns list, which is
4162 : : * assumed to be filled with pre-zeroed deparse_columns structs.
4163 : : *
4164 : : * parentUsing is a list of all USING aliases assigned in parent joins of
4165 : : * the current jointree node. (The passed-in list must not be modified.)
4166 : : */
4167 : : static void
3636 4168 : 6169 : set_using_names(deparse_namespace *dpns, Node *jtnode, List *parentUsing)
4169 : : {
4122 4170 [ + + ]: 6169 : if (IsA(jtnode, RangeTblRef))
4171 : : {
4172 : : /* nothing to do now */
4173 : : }
4174 [ + + ]: 3204 : else if (IsA(jtnode, FromExpr))
4175 : : {
4176 : 2619 : FromExpr *f = (FromExpr *) jtnode;
4177 : : ListCell *lc;
4178 : :
4179 [ + + + + : 4999 : foreach(lc, f->fromlist)
+ + ]
3636 4180 : 2380 : set_using_names(dpns, (Node *) lfirst(lc), parentUsing);
4181 : : }
4122 4182 [ + - ]: 585 : else if (IsA(jtnode, JoinExpr))
4183 : : {
4184 : 585 : JoinExpr *j = (JoinExpr *) jtnode;
4185 : 585 : RangeTblEntry *rte = rt_fetch(j->rtindex, dpns->rtable);
4186 : 585 : deparse_columns *colinfo = deparse_columns_fetch(j->rtindex, dpns);
4187 : : int *leftattnos;
4188 : : int *rightattnos;
4189 : : deparse_columns *leftcolinfo;
4190 : : deparse_columns *rightcolinfo;
4191 : : int i;
4192 : : ListCell *lc;
4193 : :
4194 : : /* Get info about the shape of the join */
4195 : 585 : identify_join_columns(j, rte, colinfo);
4196 : 585 : leftattnos = colinfo->leftattnos;
4197 : 585 : rightattnos = colinfo->rightattnos;
4198 : :
4199 : : /* Look up the not-yet-filled-in child deparse_columns structs */
4200 : 585 : leftcolinfo = deparse_columns_fetch(colinfo->leftrti, dpns);
4201 : 585 : rightcolinfo = deparse_columns_fetch(colinfo->rightrti, dpns);
4202 : :
4203 : : /*
4204 : : * If this join is unnamed, then we cannot substitute new aliases at
4205 : : * this level, so any name requirements pushed down to here must be
4206 : : * pushed down again to the children.
4207 : : */
4208 [ + + ]: 585 : if (rte->alias == NULL)
4209 : : {
4210 [ + + ]: 600 : for (i = 0; i < colinfo->num_cols; i++)
4211 : : {
4212 : 69 : char *colname = colinfo->colnames[i];
4213 : :
4214 [ + + ]: 69 : if (colname == NULL)
4215 : 12 : continue;
4216 : :
4217 : : /* Push down to left column, unless it's a system column */
4218 [ + + ]: 57 : if (leftattnos[i] > 0)
4219 : : {
4220 : 51 : expand_colnames_array_to(leftcolinfo, leftattnos[i]);
4221 : 51 : leftcolinfo->colnames[leftattnos[i] - 1] = colname;
4222 : : }
4223 : :
4224 : : /* Same on the righthand side */
4225 [ + - ]: 57 : if (rightattnos[i] > 0)
4226 : : {
4227 : 57 : expand_colnames_array_to(rightcolinfo, rightattnos[i]);
4228 : 57 : rightcolinfo->colnames[rightattnos[i] - 1] = colname;
4229 : : }
4230 : : }
4231 : : }
4232 : :
4233 : : /*
4234 : : * If there's a USING clause, select the USING column names and push
4235 : : * those names down to the children. We have two strategies:
4236 : : *
4237 : : * If dpns->unique_using is true, we force all USING names to be
4238 : : * unique across the whole query level. In principle we'd only need
4239 : : * the names of dangerous USING columns to be globally unique, but to
4240 : : * safely assign all USING names in a single pass, we have to enforce
4241 : : * the same uniqueness rule for all of them. However, if a USING
4242 : : * column's name has been pushed down from the parent, we should use
4243 : : * it as-is rather than making a uniqueness adjustment. This is
4244 : : * necessary when we're at an unnamed join, and it creates no risk of
4245 : : * ambiguity. Also, if there's a user-written output alias for a
4246 : : * merged column, we prefer to use that rather than the input name;
4247 : : * this simplifies the logic and seems likely to lead to less aliasing
4248 : : * overall.
4249 : : *
4250 : : * If dpns->unique_using is false, we only need USING names to be
4251 : : * unique within their own join RTE. We still need to honor
4252 : : * pushed-down names, though.
4253 : : *
4254 : : * Though significantly different in results, these two strategies are
4255 : : * implemented by the same code, with only the difference of whether
4256 : : * to put assigned names into dpns->using_names.
4257 : : */
4258 [ + + ]: 585 : if (j->usingClause)
4259 : : {
4260 : : /* Copy the input parentUsing list so we don't modify it */
3636 4261 : 212 : parentUsing = list_copy(parentUsing);
4262 : :
4263 : : /* USING names must correspond to the first join output columns */
4122 4264 : 212 : expand_colnames_array_to(colinfo, list_length(j->usingClause));
4265 : 212 : i = 0;
4266 [ + - + + : 502 : foreach(lc, j->usingClause)
+ + ]
4267 : : {
4268 : 290 : char *colname = strVal(lfirst(lc));
4269 : :
4270 : : /* Assert it's a merged column */
4271 [ + - - + ]: 290 : Assert(leftattnos[i] != 0 && rightattnos[i] != 0);
4272 : :
4273 : : /* Adopt passed-down name if any, else select unique name */
4274 [ + + ]: 290 : if (colinfo->colnames[i] != NULL)
4275 : 51 : colname = colinfo->colnames[i];
4276 : : else
4277 : : {
4278 : : /* Prefer user-written output alias if any */
4279 [ + + - + ]: 239 : if (rte->alias && i < list_length(rte->alias->colnames))
4122 tgl@sss.pgh.pa.us 4280 :UBC 0 : colname = strVal(list_nth(rte->alias->colnames, i));
4281 : : /* Make it appropriately unique */
4122 tgl@sss.pgh.pa.us 4282 :CBC 239 : colname = make_colname_unique(colname, dpns, colinfo);
4283 [ + + ]: 239 : if (dpns->unique_using)
4284 : 63 : dpns->using_names = lappend(dpns->using_names,
4285 : : colname);
4286 : : /* Save it as output column name, too */
4287 : 239 : colinfo->colnames[i] = colname;
4288 : : }
4289 : :
4290 : : /* Remember selected names for use later */
4291 : 290 : colinfo->usingNames = lappend(colinfo->usingNames, colname);
3636 4292 : 290 : parentUsing = lappend(parentUsing, colname);
4293 : :
4294 : : /* Push down to left column, unless it's a system column */
4122 4295 [ + - ]: 290 : if (leftattnos[i] > 0)
4296 : : {
4297 : 290 : expand_colnames_array_to(leftcolinfo, leftattnos[i]);
4298 : 290 : leftcolinfo->colnames[leftattnos[i] - 1] = colname;
4299 : : }
4300 : :
4301 : : /* Same on the righthand side */
4302 [ + - ]: 290 : if (rightattnos[i] > 0)
4303 : : {
4304 : 290 : expand_colnames_array_to(rightcolinfo, rightattnos[i]);
4305 : 290 : rightcolinfo->colnames[rightattnos[i] - 1] = colname;
4306 : : }
4307 : :
4308 : 290 : i++;
4309 : : }
4310 : : }
4311 : :
4312 : : /* Mark child deparse_columns structs with correct parentUsing info */
3636 4313 : 585 : leftcolinfo->parentUsing = parentUsing;
4314 : 585 : rightcolinfo->parentUsing = parentUsing;
4315 : :
4316 : : /* Now recursively assign USING column names in children */
4317 : 585 : set_using_names(dpns, j->larg, parentUsing);
4318 : 585 : set_using_names(dpns, j->rarg, parentUsing);
4319 : : }
4320 : : else
4122 tgl@sss.pgh.pa.us 4321 [ # # ]:UBC 0 : elog(ERROR, "unrecognized node type: %d",
4322 : : (int) nodeTag(jtnode));
4122 tgl@sss.pgh.pa.us 4323 :CBC 6169 : }
4324 : :
4325 : : /*
4326 : : * set_relation_column_names: select column aliases for a non-join RTE
4327 : : *
4328 : : * Column alias info is saved in *colinfo, which is assumed to be pre-zeroed.
4329 : : * If any colnames entries are already filled in, those override local
4330 : : * choices.
4331 : : */
4332 : : static void
4333 : 43448 : set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
4334 : : deparse_columns *colinfo)
4335 : : {
4336 : : int ncolumns;
4337 : : char **real_colnames;
4338 : : bool changed_any;
4339 : : int noldcolumns;
4340 : : int i;
4341 : : int j;
4342 : :
4343 : : /*
4344 : : * Construct an array of the current "real" column names of the RTE.
4345 : : * real_colnames[] will be indexed by physical column number, with NULL
4346 : : * entries for dropped columns.
4347 : : */
4348 [ + + ]: 43448 : if (rte->rtekind == RTE_RELATION)
4349 : : {
4350 : : /* Relation --- look to the system catalogs for up-to-date info */
4351 : : Relation rel;
4352 : : TupleDesc tupdesc;
4353 : :
4354 : 35818 : rel = relation_open(rte->relid, AccessShareLock);
4355 : 35818 : tupdesc = RelationGetDescr(rel);
4356 : :
4357 : 35818 : ncolumns = tupdesc->natts;
4358 : 35818 : real_colnames = (char **) palloc(ncolumns * sizeof(char *));
4359 : :
4360 [ + + ]: 228413 : for (i = 0; i < ncolumns; i++)
4361 : : {
2429 andres@anarazel.de 4362 : 192595 : Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
4363 : :
4364 [ + + ]: 192595 : if (attr->attisdropped)
4122 tgl@sss.pgh.pa.us 4365 : 1523 : real_colnames[i] = NULL;
4366 : : else
2429 andres@anarazel.de 4367 : 191072 : real_colnames[i] = pstrdup(NameStr(attr->attname));
4368 : : }
4122 tgl@sss.pgh.pa.us 4369 : 35818 : relation_close(rel, AccessShareLock);
4370 : : }
4371 : : else
4372 : : {
4373 : : /* Otherwise get the column names from eref or expandRTE() */
4374 : : List *colnames;
4375 : : ListCell *lc;
4376 : :
4377 : : /*
4378 : : * Functions returning composites have the annoying property that some
4379 : : * of the composite type's columns might have been dropped since the
4380 : : * query was parsed. If possible, use expandRTE() to handle that
4381 : : * case, since it has the tedious logic needed to find out about
4382 : : * dropped columns. However, if we're explaining a plan, then we
4383 : : * don't have rte->functions because the planner thinks that won't be
4384 : : * needed later, and that breaks expandRTE(). So in that case we have
4385 : : * to rely on rte->eref, which may lead us to report a dropped
4386 : : * column's old name; that seems close enough for EXPLAIN's purposes.
4387 : : *
4388 : : * For non-RELATION, non-FUNCTION RTEs, we can just look at rte->eref,
4389 : : * which should be sufficiently up-to-date: no other RTE types can
4390 : : * have columns get dropped from under them after parsing.
4391 : : */
633 4392 [ + + + + ]: 7630 : if (rte->rtekind == RTE_FUNCTION && rte->functions != NIL)
4393 : : {
4394 : : /* Since we're not creating Vars, rtindex etc. don't matter */
4395 : 315 : expandRTE(rte, 1, 0, -1, true /* include dropped */ ,
4396 : : &colnames, NULL);
4397 : : }
4398 : : else
4399 : 7315 : colnames = rte->eref->colnames;
4400 : :
4401 : 7630 : ncolumns = list_length(colnames);
4122 4402 : 7630 : real_colnames = (char **) palloc(ncolumns * sizeof(char *));
4403 : :
4404 : 7630 : i = 0;
633 4405 [ + + + + : 44395 : foreach(lc, colnames)
+ + ]
4406 : : {
4407 : : /*
4408 : : * If the column name we find here is an empty string, then it's a
4409 : : * dropped column, so change to NULL.
4410 : : */
3557 4411 : 36765 : char *cname = strVal(lfirst(lc));
4412 : :
4413 [ + + ]: 36765 : if (cname[0] == '\0')
4414 : 27 : cname = NULL;
4415 : 36765 : real_colnames[i] = cname;
4122 4416 : 36765 : i++;
4417 : : }
4418 : : }
4419 : :
4420 : : /*
4421 : : * Ensure colinfo->colnames has a slot for each column. (It could be long
4422 : : * enough already, if we pushed down a name for the last column.) Note:
4423 : : * it's possible that there are now more columns than there were when the
4424 : : * query was parsed, ie colnames could be longer than rte->eref->colnames.
4425 : : * We must assign unique aliases to the new columns too, else there could
4426 : : * be unresolved conflicts when the view/rule is reloaded.
4427 : : */
4428 : 43448 : expand_colnames_array_to(colinfo, ncolumns);
4429 [ - + ]: 43448 : Assert(colinfo->num_cols == ncolumns);
4430 : :
4431 : : /*
4432 : : * Make sufficiently large new_colnames and is_new_col arrays, too.
4433 : : *
4434 : : * Note: because we leave colinfo->num_new_cols zero until after the loop,
4435 : : * colname_is_unique will not consult that array, which is fine because it
4436 : : * would only be duplicate effort.
4437 : : */
4438 : 43448 : colinfo->new_colnames = (char **) palloc(ncolumns * sizeof(char *));
4439 : 43448 : colinfo->is_new_col = (bool *) palloc(ncolumns * sizeof(bool));
4440 : :
4441 : : /*
4442 : : * Scan the columns, select a unique alias for each one, and store it in
4443 : : * colinfo->colnames and colinfo->new_colnames. The former array has NULL
4444 : : * entries for dropped columns, the latter omits them. Also mark
4445 : : * new_colnames entries as to whether they are new since parse time; this
4446 : : * is the case for entries beyond the length of rte->eref->colnames.
4447 : : */
4448 : 43448 : noldcolumns = list_length(rte->eref->colnames);
4449 : 43448 : changed_any = false;
4450 : 43448 : j = 0;
4451 [ + + ]: 272808 : for (i = 0; i < ncolumns; i++)
4452 : : {
4453 : 229360 : char *real_colname = real_colnames[i];
4454 : 229360 : char *colname = colinfo->colnames[i];
4455 : :
4456 : : /* Skip dropped columns */
4457 [ + + ]: 229360 : if (real_colname == NULL)
4458 : : {
4459 [ - + ]: 1550 : Assert(colname == NULL); /* colnames[i] is already NULL */
4460 : 1550 : continue;
4461 : : }
4462 : :
4463 : : /* If alias already assigned, that's what to use */
4464 [ + + ]: 227810 : if (colname == NULL)
4465 : : {
4466 : : /* If user wrote an alias, prefer that over real column name */
4467 [ + + + + ]: 227281 : if (rte->alias && i < list_length(rte->alias->colnames))
4468 : 20174 : colname = strVal(list_nth(rte->alias->colnames, i));
4469 : : else
4470 : 207107 : colname = real_colname;
4471 : :
4472 : : /* Unique-ify and insert into colinfo */
4473 : 227281 : colname = make_colname_unique(colname, dpns, colinfo);
4474 : :
4475 : 227281 : colinfo->colnames[i] = colname;
4476 : : }
4477 : :
4478 : : /* Put names of non-dropped columns in new_colnames[] too */
4479 : 227810 : colinfo->new_colnames[j] = colname;
4480 : : /* And mark them as new or not */
4481 : 227810 : colinfo->is_new_col[j] = (i >= noldcolumns);
4482 : 227810 : j++;
4483 : :
4484 : : /* Remember if any assigned aliases differ from "real" name */
4485 [ + + + + ]: 227810 : if (!changed_any && strcmp(colname, real_colname) != 0)
4486 : 2309 : changed_any = true;
4487 : : }
4488 : :
4489 : : /*
4490 : : * Set correct length for new_colnames[] array. (Note: if columns have
4491 : : * been added, colinfo->num_cols includes them, which is not really quite
4492 : : * right but is harmless, since any new columns must be at the end where
4493 : : * they won't affect varattnos of pre-existing columns.)
4494 : : */
4495 : 43448 : colinfo->num_new_cols = j;
4496 : :
4497 : : /*
4498 : : * For a relation RTE, we need only print the alias column names if any
4499 : : * are different from the underlying "real" names. For a function RTE,
4500 : : * always emit a complete column alias list; this is to protect against
4501 : : * possible instability of the default column names (eg, from altering
4502 : : * parameter names). For tablefunc RTEs, we never print aliases, because
4503 : : * the column names are part of the clause itself. For other RTE types,
4504 : : * print if we changed anything OR if there were user-written column
4505 : : * aliases (since the latter would be part of the underlying "reality").
4506 : : */
4507 [ + + ]: 43448 : if (rte->rtekind == RTE_RELATION)
4508 : 35818 : colinfo->printaliases = changed_any;
4509 [ + + ]: 7630 : else if (rte->rtekind == RTE_FUNCTION)
4510 : 525 : colinfo->printaliases = true;
2594 alvherre@alvh.no-ip. 4511 [ + + ]: 7105 : else if (rte->rtekind == RTE_TABLEFUNC)
4512 : 73 : colinfo->printaliases = false;
4122 tgl@sss.pgh.pa.us 4513 [ + + + + ]: 7032 : else if (rte->alias && rte->alias->colnames != NIL)
4514 : 366 : colinfo->printaliases = true;
4515 : : else
4516 : 6666 : colinfo->printaliases = changed_any;
4517 : 43448 : }
4518 : :
4519 : : /*
4520 : : * set_join_column_names: select column aliases for a join RTE
4521 : : *
4522 : : * Column alias info is saved in *colinfo, which is assumed to be pre-zeroed.
4523 : : * If any colnames entries are already filled in, those override local
4524 : : * choices. Also, names for USING columns were already chosen by
4525 : : * set_using_names(). We further expect that column alias selection has been
4526 : : * completed for both input RTEs.
4527 : : */
4528 : : static void
4529 : 585 : set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
4530 : : deparse_columns *colinfo)
4531 : : {
4532 : : deparse_columns *leftcolinfo;
4533 : : deparse_columns *rightcolinfo;
4534 : : bool changed_any;
4535 : : int noldcolumns;
4536 : : int nnewcolumns;
4537 : 585 : Bitmapset *leftmerged = NULL;
4538 : 585 : Bitmapset *rightmerged = NULL;
4539 : : int i;
4540 : : int j;
4541 : : int ic;
4542 : : int jc;
4543 : :
4544 : : /* Look up the previously-filled-in child deparse_columns structs */
4545 : 585 : leftcolinfo = deparse_columns_fetch(colinfo->leftrti, dpns);
4546 : 585 : rightcolinfo = deparse_columns_fetch(colinfo->rightrti, dpns);
4547 : :
4548 : : /*
4549 : : * Ensure colinfo->colnames has a slot for each column. (It could be long
4550 : : * enough already, if we pushed down a name for the last column.) Note:
4551 : : * it's possible that one or both inputs now have more columns than there
4552 : : * were when the query was parsed, but we'll deal with that below. We
4553 : : * only need entries in colnames for pre-existing columns.
4554 : : */
4555 : 585 : noldcolumns = list_length(rte->eref->colnames);
4556 : 585 : expand_colnames_array_to(colinfo, noldcolumns);
4557 [ - + ]: 585 : Assert(colinfo->num_cols == noldcolumns);
4558 : :
4559 : : /*
4560 : : * Scan the join output columns, select an alias for each one, and store
4561 : : * it in colinfo->colnames. If there are USING columns, set_using_names()
4562 : : * already selected their names, so we can start the loop at the first
4563 : : * non-merged column.
4564 : : */
4565 : 585 : changed_any = false;
4566 [ + + ]: 16820 : for (i = list_length(colinfo->usingNames); i < noldcolumns; i++)
4567 : : {
4568 : 16235 : char *colname = colinfo->colnames[i];
4569 : : char *real_colname;
4570 : :
4571 : : /* Join column must refer to at least one input column */
1557 4572 [ + + - + ]: 16235 : Assert(colinfo->leftattnos[i] != 0 || colinfo->rightattnos[i] != 0);
4573 : :
4574 : : /* Get the child column name */
4122 4575 [ + + ]: 16235 : if (colinfo->leftattnos[i] > 0)
4576 : 11303 : real_colname = leftcolinfo->colnames[colinfo->leftattnos[i] - 1];
4577 [ + - ]: 4932 : else if (colinfo->rightattnos[i] > 0)
4578 : 4932 : real_colname = rightcolinfo->colnames[colinfo->rightattnos[i] - 1];
4579 : : else
4580 : : {
4581 : : /* We're joining system columns --- use eref name */
3918 tgl@sss.pgh.pa.us 4582 :UBC 0 : real_colname = strVal(list_nth(rte->eref->colnames, i));
4583 : : }
4584 : :
4585 : : /* If child col has been dropped, no need to assign a join colname */
1557 tgl@sss.pgh.pa.us 4586 [ + + ]:CBC 16235 : if (real_colname == NULL)
4587 : : {
4588 : 3 : colinfo->colnames[i] = NULL;
4589 : 3 : continue;
4590 : : }
4591 : :
4592 : : /* In an unnamed join, just report child column names as-is */
4122 4593 [ + + ]: 16232 : if (rte->alias == NULL)
4594 : : {
4595 : 16043 : colinfo->colnames[i] = real_colname;
4596 : 16043 : continue;
4597 : : }
4598 : :
4599 : : /* If alias already assigned, that's what to use */
4600 [ + - ]: 189 : if (colname == NULL)
4601 : : {
4602 : : /* If user wrote an alias, prefer that over real column name */
4603 [ + - + + ]: 189 : if (rte->alias && i < list_length(rte->alias->colnames))
4604 : 48 : colname = strVal(list_nth(rte->alias->colnames, i));
4605 : : else
4606 : 141 : colname = real_colname;
4607 : :
4608 : : /* Unique-ify and insert into colinfo */
4609 : 189 : colname = make_colname_unique(colname, dpns, colinfo);
4610 : :
4611 : 189 : colinfo->colnames[i] = colname;
4612 : : }
4613 : :
4614 : : /* Remember if any assigned aliases differ from "real" name */
4615 [ + + + + ]: 189 : if (!changed_any && strcmp(colname, real_colname) != 0)
4616 : 12 : changed_any = true;
4617 : : }
4618 : :
4619 : : /*
4620 : : * Calculate number of columns the join would have if it were re-parsed
4621 : : * now, and create storage for the new_colnames and is_new_col arrays.
4622 : : *
4623 : : * Note: colname_is_unique will be consulting new_colnames[] during the
4624 : : * loops below, so its not-yet-filled entries must be zeroes.
4625 : : */
4626 : 1170 : nnewcolumns = leftcolinfo->num_new_cols + rightcolinfo->num_new_cols -
4627 : 585 : list_length(colinfo->usingNames);
4628 : 585 : colinfo->num_new_cols = nnewcolumns;
4629 : 585 : colinfo->new_colnames = (char **) palloc0(nnewcolumns * sizeof(char *));
4630 : 585 : colinfo->is_new_col = (bool *) palloc0(nnewcolumns * sizeof(bool));
4631 : :
4632 : : /*
4633 : : * Generating the new_colnames array is a bit tricky since any new columns
4634 : : * added since parse time must be inserted in the right places. This code
4635 : : * must match the parser, which will order a join's columns as merged
4636 : : * columns first (in USING-clause order), then non-merged columns from the
4637 : : * left input (in attnum order), then non-merged columns from the right
4638 : : * input (ditto). If one of the inputs is itself a join, its columns will
4639 : : * be ordered according to the same rule, which means newly-added columns
4640 : : * might not be at the end. We can figure out what's what by consulting
4641 : : * the leftattnos and rightattnos arrays plus the input is_new_col arrays.
4642 : : *
4643 : : * In these loops, i indexes leftattnos/rightattnos (so it's join varattno
4644 : : * less one), j indexes new_colnames/is_new_col, and ic/jc have similar
4645 : : * meanings for the current child RTE.
4646 : : */
4647 : :
4648 : : /* Handle merged columns; they are first and can't be new */
4649 : 585 : i = j = 0;
4650 : 585 : while (i < noldcolumns &&
4651 [ + - + - ]: 875 : colinfo->leftattnos[i] != 0 &&
4652 [ + + ]: 875 : colinfo->rightattnos[i] != 0)
4653 : : {
4654 : : /* column name is already determined and known unique */
4655 : 290 : colinfo->new_colnames[j] = colinfo->colnames[i];
4656 : 290 : colinfo->is_new_col[j] = false;
4657 : :
4658 : : /* build bitmapsets of child attnums of merged columns */
4659 [ + - ]: 290 : if (colinfo->leftattnos[i] > 0)
4660 : 290 : leftmerged = bms_add_member(leftmerged, colinfo->leftattnos[i]);
4661 [ + - ]: 290 : if (colinfo->rightattnos[i] > 0)
4662 : 290 : rightmerged = bms_add_member(rightmerged, colinfo->rightattnos[i]);
4663 : :
4664 : 290 : i++, j++;
4665 : : }
4666 : :
4667 : : /* Handle non-merged left-child columns */
4668 : 585 : ic = 0;
4669 [ + + ]: 12421 : for (jc = 0; jc < leftcolinfo->num_new_cols; jc++)
4670 : : {
4671 : 11836 : char *child_colname = leftcolinfo->new_colnames[jc];
4672 : :
4673 [ + + ]: 11836 : if (!leftcolinfo->is_new_col[jc])
4674 : : {
4675 : : /* Advance ic to next non-dropped old column of left child */
4676 [ + - ]: 11632 : while (ic < leftcolinfo->num_cols &&
4677 [ + + ]: 11632 : leftcolinfo->colnames[ic] == NULL)
4678 : 42 : ic++;
4679 [ - + ]: 11590 : Assert(ic < leftcolinfo->num_cols);
4680 : 11590 : ic++;
4681 : : /* If it is a merged column, we already processed it */
4682 [ + + ]: 11590 : if (bms_is_member(ic, leftmerged))
4683 : 290 : continue;
4684 : : /* Else, advance i to the corresponding existing join column */
4685 [ + - ]: 11303 : while (i < colinfo->num_cols &&
4686 [ + + ]: 11303 : colinfo->colnames[i] == NULL)
4687 : 3 : i++;
4688 [ - + ]: 11300 : Assert(i < colinfo->num_cols);
4689 [ - + ]: 11300 : Assert(ic == colinfo->leftattnos[i]);
4690 : : /* Use the already-assigned name of this column */
4691 : 11300 : colinfo->new_colnames[j] = colinfo->colnames[i];
4692 : 11300 : i++;
4693 : : }
4694 : : else
4695 : : {
4696 : : /*
4697 : : * Unique-ify the new child column name and assign, unless we're
4698 : : * in an unnamed join, in which case just copy
4699 : : */
4700 [ + + ]: 246 : if (rte->alias != NULL)
4701 : : {
4702 : 132 : colinfo->new_colnames[j] =
4703 : 66 : make_colname_unique(child_colname, dpns, colinfo);
4704 [ + + ]: 66 : if (!changed_any &&
4705 [ + + ]: 54 : strcmp(colinfo->new_colnames[j], child_colname) != 0)
4706 : 6 : changed_any = true;
4707 : : }
4708 : : else
4709 : 180 : colinfo->new_colnames[j] = child_colname;
4710 : : }
4711 : :
4712 : 11546 : colinfo->is_new_col[j] = leftcolinfo->is_new_col[jc];
4713 : 11546 : j++;
4714 : : }
4715 : :
4716 : : /* Handle non-merged right-child columns in exactly the same way */
4717 : 585 : ic = 0;
4718 [ + + ]: 5891 : for (jc = 0; jc < rightcolinfo->num_new_cols; jc++)
4719 : : {
4720 : 5306 : char *child_colname = rightcolinfo->new_colnames[jc];
4721 : :
4722 [ + + ]: 5306 : if (!rightcolinfo->is_new_col[jc])
4723 : : {
4724 : : /* Advance ic to next non-dropped old column of right child */
4725 [ + - ]: 5222 : while (ic < rightcolinfo->num_cols &&
4726 [ - + ]: 5222 : rightcolinfo->colnames[ic] == NULL)
4122 tgl@sss.pgh.pa.us 4727 :UBC 0 : ic++;
4122 tgl@sss.pgh.pa.us 4728 [ - + ]:CBC 5222 : Assert(ic < rightcolinfo->num_cols);
4729 : 5222 : ic++;
4730 : : /* If it is a merged column, we already processed it */
4731 [ + + ]: 5222 : if (bms_is_member(ic, rightmerged))
4732 : 290 : continue;
4733 : : /* Else, advance i to the corresponding existing join column */
4734 [ + - ]: 4932 : while (i < colinfo->num_cols &&
4735 [ - + ]: 4932 : colinfo->colnames[i] == NULL)
4122 tgl@sss.pgh.pa.us 4736 :UBC 0 : i++;
4122 tgl@sss.pgh.pa.us 4737 [ - + ]:CBC 4932 : Assert(i < colinfo->num_cols);
4738 [ - + ]: 4932 : Assert(ic == colinfo->rightattnos[i]);
4739 : : /* Use the already-assigned name of this column */
4740 : 4932 : colinfo->new_colnames[j] = colinfo->colnames[i];
4741 : 4932 : i++;
4742 : : }
4743 : : else
4744 : : {
4745 : : /*
4746 : : * Unique-ify the new child column name and assign, unless we're
4747 : : * in an unnamed join, in which case just copy
4748 : : */
4749 [ + + ]: 84 : if (rte->alias != NULL)
4750 : : {
4751 : 24 : colinfo->new_colnames[j] =
4752 : 12 : make_colname_unique(child_colname, dpns, colinfo);
4753 [ + - ]: 12 : if (!changed_any &&
4754 [ + + ]: 12 : strcmp(colinfo->new_colnames[j], child_colname) != 0)
4755 : 6 : changed_any = true;
4756 : : }
4757 : : else
4758 : 72 : colinfo->new_colnames[j] = child_colname;
4759 : : }
4760 : :
4761 : 5016 : colinfo->is_new_col[j] = rightcolinfo->is_new_col[jc];
4762 : 5016 : j++;
4763 : : }
4764 : :
4765 : : /* Assert we processed the right number of columns */
4766 : : #ifdef USE_ASSERT_CHECKING
4767 [ - + - - ]: 585 : while (i < colinfo->num_cols && colinfo->colnames[i] == NULL)
4122 tgl@sss.pgh.pa.us 4768 :UBC 0 : i++;
4122 tgl@sss.pgh.pa.us 4769 [ - + ]:CBC 585 : Assert(i == colinfo->num_cols);
4770 [ - + ]: 585 : Assert(j == nnewcolumns);
4771 : : #endif
4772 : :
4773 : : /*
4774 : : * For a named join, print column aliases if we changed any from the child
4775 : : * names. Unnamed joins cannot print aliases.
4776 : : */
4777 [ + + ]: 585 : if (rte->alias != NULL)
4778 : 54 : colinfo->printaliases = changed_any;
4779 : : else
4780 : 531 : colinfo->printaliases = false;
4781 : 585 : }
4782 : :
4783 : : /*
4784 : : * colname_is_unique: is colname distinct from already-chosen column names?
4785 : : *
4786 : : * dpns is query-wide info, colinfo is for the column's RTE
4787 : : */
4788 : : static bool
2357 peter_e@gmx.net 4789 : 240850 : colname_is_unique(const char *colname, deparse_namespace *dpns,
4790 : : deparse_columns *colinfo)
4791 : : {
4792 : : int i;
4793 : : ListCell *lc;
4794 : :
4795 : : /* Check against already-assigned column aliases within RTE */
4122 tgl@sss.pgh.pa.us 4796 [ + + ]: 3605196 : for (i = 0; i < colinfo->num_cols; i++)
4797 : : {
4798 : 3377373 : char *oldname = colinfo->colnames[i];
4799 : :
4800 [ + + + + ]: 3377373 : if (oldname && strcmp(oldname, colname) == 0)
4801 : 13027 : return false;
4802 : : }
4803 : :
4804 : : /*
4805 : : * If we're building a new_colnames array, check that too (this will be
4806 : : * partially but not completely redundant with the previous checks)
4807 : : */
4808 [ + + ]: 228459 : for (i = 0; i < colinfo->num_new_cols; i++)
4809 : : {
4810 : 648 : char *oldname = colinfo->new_colnames[i];
4811 : :
4812 [ + + + + ]: 648 : if (oldname && strcmp(oldname, colname) == 0)
4813 : 12 : return false;
4814 : : }
4815 : :
4816 : : /* Also check against USING-column names that must be globally unique */
4817 [ + + + + : 228231 : foreach(lc, dpns->using_names)
+ + ]
4818 : : {
4819 : 441 : char *oldname = (char *) lfirst(lc);
4820 : :
4821 [ + + ]: 441 : if (strcmp(oldname, colname) == 0)
4822 : 21 : return false;
4823 : : }
4824 : :
4825 : : /* Also check against names already assigned for parent-join USING cols */
3636 4826 [ + + + + : 229086 : foreach(lc, colinfo->parentUsing)
+ + ]
4827 : : {
4828 : 1299 : char *oldname = (char *) lfirst(lc);
4829 : :
4830 [ + + ]: 1299 : if (strcmp(oldname, colname) == 0)
4831 : 3 : return false;
4832 : : }
4833 : :
4122 4834 : 227787 : return true;
4835 : : }
4836 : :
4837 : : /*
4838 : : * make_colname_unique: modify colname if necessary to make it unique
4839 : : *
4840 : : * dpns is query-wide info, colinfo is for the column's RTE
4841 : : */
4842 : : static char *
4843 : 227787 : make_colname_unique(char *colname, deparse_namespace *dpns,
4844 : : deparse_columns *colinfo)
4845 : : {
4846 : : /*
4847 : : * If the selected name isn't unique, append digits to make it so. For a
4848 : : * very long input name, we might have to truncate to stay within
4849 : : * NAMEDATALEN.
4850 : : */
4851 [ + + ]: 227787 : if (!colname_is_unique(colname, dpns, colinfo))
4852 : : {
3072 4853 : 11001 : int colnamelen = strlen(colname);
4854 : 11001 : char *modname = (char *) palloc(colnamelen + 16);
4122 4855 : 11001 : int i = 0;
4856 : :
4857 : : do
4858 : : {
3072 4859 : 13063 : i++;
4860 : : for (;;)
4861 : : {
4862 : 13063 : memcpy(modname, colname, colnamelen);
4863 : 13063 : sprintf(modname + colnamelen, "_%d", i);
4864 [ + - ]: 13063 : if (strlen(modname) < NAMEDATALEN)
4865 : 13063 : break;
4866 : : /* drop chars from colname to keep all the digits */
3072 tgl@sss.pgh.pa.us 4867 :UBC 0 : colnamelen = pg_mbcliplen(colname, colnamelen,
4868 : : colnamelen - 1);
4869 : : }
4122 tgl@sss.pgh.pa.us 4870 [ + + ]:CBC 13063 : } while (!colname_is_unique(modname, dpns, colinfo));
4871 : 11001 : colname = modname;
4872 : : }
4873 : 227787 : return colname;
4874 : : }
4875 : :
4876 : : /*
4877 : : * expand_colnames_array_to: make colinfo->colnames at least n items long
4878 : : *
4879 : : * Any added array entries are initialized to zero.
4880 : : */
4881 : : static void
4882 : 44933 : expand_colnames_array_to(deparse_columns *colinfo, int n)
4883 : : {
4884 [ + + ]: 44933 : if (n > colinfo->num_cols)
4885 : : {
4886 [ + + ]: 43743 : if (colinfo->colnames == NULL)
519 peter@eisentraut.org 4887 : 43035 : colinfo->colnames = palloc0_array(char *, n);
4888 : : else
4889 : 708 : colinfo->colnames = repalloc0_array(colinfo->colnames, char *, colinfo->num_cols, n);
4122 tgl@sss.pgh.pa.us 4890 : 43743 : colinfo->num_cols = n;
4891 : : }
4892 : 44933 : }
4893 : :
4894 : : /*
4895 : : * identify_join_columns: figure out where columns of a join come from
4896 : : *
4897 : : * Fills the join-specific fields of the colinfo struct, except for
4898 : : * usingNames which is filled later.
4899 : : */
4900 : : static void
4901 : 585 : identify_join_columns(JoinExpr *j, RangeTblEntry *jrte,
4902 : : deparse_columns *colinfo)
4903 : : {
4904 : : int numjoincols;
4905 : : int jcolno;
4906 : : int rcolno;
4907 : : ListCell *lc;
4908 : :
4909 : : /* Extract left/right child RT indexes */
4910 [ + + ]: 585 : if (IsA(j->larg, RangeTblRef))
4911 : 384 : colinfo->leftrti = ((RangeTblRef *) j->larg)->rtindex;
4912 [ + - ]: 201 : else if (IsA(j->larg, JoinExpr))
4913 : 201 : colinfo->leftrti = ((JoinExpr *) j->larg)->rtindex;
4914 : : else
4122 tgl@sss.pgh.pa.us 4915 [ # # ]:UBC 0 : elog(ERROR, "unrecognized node type in jointree: %d",
4916 : : (int) nodeTag(j->larg));
4122 tgl@sss.pgh.pa.us 4917 [ + - ]:CBC 585 : if (IsA(j->rarg, RangeTblRef))
4918 : 585 : colinfo->rightrti = ((RangeTblRef *) j->rarg)->rtindex;
4122 tgl@sss.pgh.pa.us 4919 [ # # ]:UBC 0 : else if (IsA(j->rarg, JoinExpr))
4920 : 0 : colinfo->rightrti = ((JoinExpr *) j->rarg)->rtindex;
4921 : : else
4922 [ # # ]: 0 : elog(ERROR, "unrecognized node type in jointree: %d",
4923 : : (int) nodeTag(j->rarg));
4924 : :
4925 : : /* Assert children will be processed earlier than join in second pass */
4122 tgl@sss.pgh.pa.us 4926 [ - + ]:CBC 585 : Assert(colinfo->leftrti < j->rtindex);
4927 [ - + ]: 585 : Assert(colinfo->rightrti < j->rtindex);
4928 : :
4929 : : /* Initialize result arrays with zeroes */
4930 : 585 : numjoincols = list_length(jrte->joinaliasvars);
4931 [ - + ]: 585 : Assert(numjoincols == list_length(jrte->eref->colnames));
4932 : 585 : colinfo->leftattnos = (int *) palloc0(numjoincols * sizeof(int));
4933 : 585 : colinfo->rightattnos = (int *) palloc0(numjoincols * sizeof(int));
4934 : :
4935 : : /*
4936 : : * Deconstruct RTE's joinleftcols/joinrightcols into desired format.
4937 : : * Recall that the column(s) merged due to USING are the first column(s)
4938 : : * of the join output. We need not do anything special while scanning
4939 : : * joinleftcols, but while scanning joinrightcols we must distinguish
4940 : : * merged from unmerged columns.
4941 : : */
1557 4942 : 585 : jcolno = 0;
4943 [ + - + + : 12178 : foreach(lc, jrte->joinleftcols)
+ + ]
4944 : : {
4945 : 11593 : int leftattno = lfirst_int(lc);
4946 : :
4947 : 11593 : colinfo->leftattnos[jcolno++] = leftattno;
4948 : : }
4949 : 585 : rcolno = 0;
4950 [ + - + + : 5807 : foreach(lc, jrte->joinrightcols)
+ + ]
4951 : : {
4952 : 5222 : int rightattno = lfirst_int(lc);
4953 : :
4954 [ + + ]: 5222 : if (rcolno < jrte->joinmergedcols) /* merged column? */
4955 : 290 : colinfo->rightattnos[rcolno] = rightattno;
4956 : : else
4957 : 4932 : colinfo->rightattnos[jcolno++] = rightattno;
4958 : 5222 : rcolno++;
4959 : : }
4960 [ - + ]: 585 : Assert(jcolno == numjoincols);
4122 4961 : 585 : }
4962 : :
4963 : : /*
4964 : : * get_rtable_name: convenience function to get a previously assigned RTE alias
4965 : : *
4966 : : * The RTE must belong to the topmost namespace level in "context".
4967 : : */
4968 : : static char *
4223 4969 : 2892 : get_rtable_name(int rtindex, deparse_context *context)
4970 : : {
4971 : 2892 : deparse_namespace *dpns = (deparse_namespace *) linitial(context->namespaces);
4972 : :
4973 [ + - - + ]: 2892 : Assert(rtindex > 0 && rtindex <= list_length(dpns->rtable_names));
4974 : 2892 : return (char *) list_nth(dpns->rtable_names, rtindex - 1);
4975 : : }
4976 : :
4977 : : /*
4978 : : * set_deparse_plan: set up deparse_namespace to parse subexpressions
4979 : : * of a given Plan node
4980 : : *
4981 : : * This sets the plan, outer_plan, inner_plan, outer_tlist, inner_tlist,
4982 : : * and index_tlist fields. Caller must already have adjusted the ancestors
4983 : : * list if necessary. Note that the rtable, subplans, and ctes fields do
4984 : : * not need to change when shifting attention to different plan nodes in a
4985 : : * single plan tree.
4986 : : */
4987 : : static void
1586 4988 : 54979 : set_deparse_plan(deparse_namespace *dpns, Plan *plan)
4989 : : {
4990 : 54979 : dpns->plan = plan;
4991 : :
4992 : : /*
4993 : : * We special-case Append and MergeAppend to pretend that the first child
4994 : : * plan is the OUTER referent; we have to interpret OUTER Vars in their
4995 : : * tlists according to one of the children, and the first one is the most
4996 : : * natural choice.
4997 : : */
4998 [ + + ]: 54979 : if (IsA(plan, Append))
4999 : 1899 : dpns->outer_plan = linitial(((Append *) plan)->appendplans);
5000 [ + + ]: 53080 : else if (IsA(plan, MergeAppend))
5001 : 234 : dpns->outer_plan = linitial(((MergeAppend *) plan)->mergeplans);
5002 : : else
5003 : 52846 : dpns->outer_plan = outerPlan(plan);
5004 : :
5005 [ + + ]: 54979 : if (dpns->outer_plan)
5006 : 24532 : dpns->outer_tlist = dpns->outer_plan->targetlist;
5007 : : else
4569 5008 : 30447 : dpns->outer_tlist = NIL;
5009 : :
5010 : : /*
5011 : : * For a SubqueryScan, pretend the subplan is INNER referent. (We don't
5012 : : * use OUTER because that could someday conflict with the normal meaning.)
5013 : : * Likewise, for a CteScan, pretend the subquery's plan is INNER referent.
5014 : : * For a WorkTableScan, locate the parent RecursiveUnion plan node and use
5015 : : * that as INNER referent.
5016 : : *
5017 : : * For MERGE, pretend the ModifyTable's source plan (its outer plan) is
5018 : : * INNER referent. This is the join from the target relation to the data
5019 : : * source, and all INNER_VAR Vars in other parts of the query refer to its
5020 : : * targetlist.
5021 : : *
5022 : : * For ON CONFLICT .. UPDATE we just need the inner tlist to point to the
5023 : : * excluded expression's tlist. (Similar to the SubqueryScan we don't want
5024 : : * to reuse OUTER, it's used for RETURNING in some modify table cases,
5025 : : * although not INSERT .. CONFLICT).
5026 : : */
1586 5027 [ + + ]: 54979 : if (IsA(plan, SubqueryScan))
5028 : 307 : dpns->inner_plan = ((SubqueryScan *) plan)->subplan;
5029 [ + + ]: 54672 : else if (IsA(plan, CteScan))
5030 : 273 : dpns->inner_plan = list_nth(dpns->subplans,
5031 : 273 : ((CteScan *) plan)->ctePlanId - 1);
941 5032 [ + + ]: 54399 : else if (IsA(plan, WorkTableScan))
5033 : 87 : dpns->inner_plan = find_recursive_union(dpns,
5034 : : (WorkTableScan *) plan);
1586 5035 [ + + ]: 54312 : else if (IsA(plan, ModifyTable))
5036 : : {
748 alvherre@alvh.no-ip. 5037 [ + + ]: 134 : if (((ModifyTable *) plan)->operation == CMD_MERGE)
28 dean.a.rasheed@gmail 5038 : 30 : dpns->inner_plan = outerPlan(plan);
5039 : : else
5040 : 104 : dpns->inner_plan = plan;
5041 : : }
5042 : : else
5043 : 54178 : dpns->inner_plan = innerPlan(plan);
5044 : :
5045 [ + + + + ]: 54979 : if (IsA(plan, ModifyTable) && ((ModifyTable *) plan)->operation == CMD_INSERT)
5046 : 70 : dpns->inner_tlist = ((ModifyTable *) plan)->exclRelTlist;
1586 tgl@sss.pgh.pa.us 5047 [ + + ]: 54909 : else if (dpns->inner_plan)
5048 : 9166 : dpns->inner_tlist = dpns->inner_plan->targetlist;
5049 : : else
4569 5050 : 45743 : dpns->inner_tlist = NIL;
5051 : :
5052 : : /* Set up referent for INDEX_VAR Vars, if needed */
1586 5053 [ + + ]: 54979 : if (IsA(plan, IndexOnlyScan))
5054 : 1426 : dpns->index_tlist = ((IndexOnlyScan *) plan)->indextlist;
5055 [ + + ]: 53553 : else if (IsA(plan, ForeignScan))
5056 : 1353 : dpns->index_tlist = ((ForeignScan *) plan)->fdw_scan_tlist;
5057 [ - + ]: 52200 : else if (IsA(plan, CustomScan))
1586 tgl@sss.pgh.pa.us 5058 :UBC 0 : dpns->index_tlist = ((CustomScan *) plan)->custom_scan_tlist;
5059 : : else
4569 tgl@sss.pgh.pa.us 5060 :CBC 52200 : dpns->index_tlist = NIL;
5024 5061 : 54979 : }
5062 : :
5063 : : /*
5064 : : * Locate the ancestor plan node that is the RecursiveUnion generating
5065 : : * the WorkTableScan's work table. We can match on wtParam, since that
5066 : : * should be unique within the plan tree.
5067 : : */
5068 : : static Plan *
941 5069 : 87 : find_recursive_union(deparse_namespace *dpns, WorkTableScan *wtscan)
5070 : : {
5071 : : ListCell *lc;
5072 : :
5073 [ + - + - : 219 : foreach(lc, dpns->ancestors)
+ - ]
5074 : : {
5075 : 219 : Plan *ancestor = (Plan *) lfirst(lc);
5076 : :
5077 [ + + ]: 219 : if (IsA(ancestor, RecursiveUnion) &&
5078 [ + - ]: 87 : ((RecursiveUnion *) ancestor)->wtParam == wtscan->wtParam)
5079 : 87 : return ancestor;
5080 : : }
941 tgl@sss.pgh.pa.us 5081 [ # # ]:UBC 0 : elog(ERROR, "could not find RecursiveUnion for WorkTableScan with wtParam %d",
5082 : : wtscan->wtParam);
5083 : : return NULL;
5084 : : }
5085 : :
5086 : : /*
5087 : : * push_child_plan: temporarily transfer deparsing attention to a child plan
5088 : : *
5089 : : * When expanding an OUTER_VAR or INNER_VAR reference, we must adjust the
5090 : : * deparse context in case the referenced expression itself uses
5091 : : * OUTER_VAR/INNER_VAR. We modify the top stack entry in-place to avoid
5092 : : * affecting levelsup issues (although in a Plan tree there really shouldn't
5093 : : * be any).
5094 : : *
5095 : : * Caller must provide a local deparse_namespace variable to save the
5096 : : * previous state for pop_child_plan.
5097 : : */
5098 : : static void
1586 tgl@sss.pgh.pa.us 5099 :CBC 29371 : push_child_plan(deparse_namespace *dpns, Plan *plan,
5100 : : deparse_namespace *save_dpns)
5101 : : {
5102 : : /* Save state for restoration later */
5024 5103 : 29371 : *save_dpns = *dpns;
5104 : :
5105 : : /* Link current plan node into ancestors list */
1586 5106 : 29371 : dpns->ancestors = lcons(dpns->plan, dpns->ancestors);
5107 : :
5108 : : /* Set attention on selected child */
5109 : 29371 : set_deparse_plan(dpns, plan);
8460 5110 : 29371 : }
5111 : :
5112 : : /*
5113 : : * pop_child_plan: undo the effects of push_child_plan
5114 : : */
5115 : : static void
5024 5116 : 29371 : pop_child_plan(deparse_namespace *dpns, deparse_namespace *save_dpns)
5117 : : {
5118 : : List *ancestors;
5119 : :
5120 : : /* Get rid of ancestors list cell added by push_child_plan */
4245 5121 : 29371 : ancestors = list_delete_first(dpns->ancestors);
5122 : :
5123 : : /* Restore fields changed by push_child_plan */
5024 5124 : 29371 : *dpns = *save_dpns;
5125 : :
5126 : : /* Make sure dpns->ancestors is right (may be unnecessary) */
4245 5127 : 29371 : dpns->ancestors = ancestors;
5024 5128 : 29371 : }
5129 : :
5130 : : /*
5131 : : * push_ancestor_plan: temporarily transfer deparsing attention to an
5132 : : * ancestor plan
5133 : : *
5134 : : * When expanding a Param reference, we must adjust the deparse context
5135 : : * to match the plan node that contains the expression being printed;
5136 : : * otherwise we'd fail if that expression itself contains a Param or
5137 : : * OUTER_VAR/INNER_VAR/INDEX_VAR variable.
5138 : : *
5139 : : * The target ancestor is conveniently identified by the ListCell holding it
5140 : : * in dpns->ancestors.
5141 : : *
5142 : : * Caller must provide a local deparse_namespace variable to save the
5143 : : * previous state for pop_ancestor_plan.
5144 : : */
5145 : : static void
5146 : 1890 : push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell,
5147 : : deparse_namespace *save_dpns)
5148 : : {
1586 5149 : 1890 : Plan *plan = (Plan *) lfirst(ancestor_cell);
5150 : :
5151 : : /* Save state for restoration later */
5024 5152 : 1890 : *save_dpns = *dpns;
5153 : :
5154 : : /* Build a new ancestor list with just this node's ancestors */
1735 5155 : 1890 : dpns->ancestors =
5156 : 1890 : list_copy_tail(dpns->ancestors,
5157 : 1890 : list_cell_number(dpns->ancestors, ancestor_cell) + 1);
5158 : :
5159 : : /* Set attention on selected ancestor */
1586 5160 : 1890 : set_deparse_plan(dpns, plan);
5024 5161 : 1890 : }
5162 : :
5163 : : /*
5164 : : * pop_ancestor_plan: undo the effects of push_ancestor_plan
5165 : : */
5166 : : static void
5167 : 1890 : pop_ancestor_plan(deparse_namespace *dpns, deparse_namespace *save_dpns)
5168 : : {
5169 : : /* Free the ancestor list made in push_ancestor_plan */
5170 : 1890 : list_free(dpns->ancestors);
5171 : :
5172 : : /* Restore fields changed by push_ancestor_plan */
5173 : 1890 : *dpns = *save_dpns;
5174 : 1890 : }
5175 : :
5176 : :
5177 : : /* ----------
5178 : : * make_ruledef - reconstruct the CREATE RULE command
5179 : : * for a given pg_rewrite tuple
5180 : : * ----------
5181 : : */
5182 : : static void
7564 5183 : 275 : make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
5184 : : int prettyFlags)
5185 : : {
5186 : : char *rulename;
5187 : : char ev_type;
5188 : : Oid ev_class;
5189 : : bool is_instead;
5190 : : char *ev_qual;
5191 : : char *ev_action;
5192 : : List *actions;
5193 : : Relation ev_relation;
2456 5194 : 275 : TupleDesc viewResultDesc = NULL;
5195 : : int fno;
5196 : : Datum dat;
5197 : : bool isnull;
5198 : :
5199 : : /*
5200 : : * Get the attribute values from the rules tuple
5201 : : */
8032 5202 : 275 : fno = SPI_fnumber(rulettc, "rulename");
5203 : 275 : dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
5204 [ - + ]: 275 : Assert(!isnull);
5205 : 275 : rulename = NameStr(*(DatumGetName(dat)));
5206 : :
9357 bruce@momjian.us 5207 : 275 : fno = SPI_fnumber(rulettc, "ev_type");
8032 tgl@sss.pgh.pa.us 5208 : 275 : dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
5209 [ - + ]: 275 : Assert(!isnull);
5210 : 275 : ev_type = DatumGetChar(dat);
5211 : :
9357 bruce@momjian.us 5212 : 275 : fno = SPI_fnumber(rulettc, "ev_class");
8032 tgl@sss.pgh.pa.us 5213 : 275 : dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
5214 [ - + ]: 275 : Assert(!isnull);
5215 : 275 : ev_class = DatumGetObjectId(dat);
5216 : :
9357 bruce@momjian.us 5217 : 275 : fno = SPI_fnumber(rulettc, "is_instead");
8032 tgl@sss.pgh.pa.us 5218 : 275 : dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
5219 [ - + ]: 275 : Assert(!isnull);
5220 : 275 : is_instead = DatumGetBool(dat);
5221 : :
9357 bruce@momjian.us 5222 : 275 : fno = SPI_fnumber(rulettc, "ev_qual");
5223 : 275 : ev_qual = SPI_getvalue(ruletup, rulettc, fno);
1259 tgl@sss.pgh.pa.us 5224 [ - + ]: 275 : Assert(ev_qual != NULL);
5225 : :
9357 bruce@momjian.us 5226 : 275 : fno = SPI_fnumber(rulettc, "ev_action");
5227 : 275 : ev_action = SPI_getvalue(ruletup, rulettc, fno);
1259 tgl@sss.pgh.pa.us 5228 [ - + ]: 275 : Assert(ev_action != NULL);
5229 : 275 : actions = (List *) stringToNode(ev_action);
5230 [ - + ]: 275 : if (actions == NIL)
1259 tgl@sss.pgh.pa.us 5231 [ # # ]:UBC 0 : elog(ERROR, "invalid empty ev_action list");
5232 : :
1910 andres@anarazel.de 5233 :CBC 275 : ev_relation = table_open(ev_class, AccessShareLock);
5234 : :
5235 : : /*
5236 : : * Build the rules definition text
5237 : : */
7564 tgl@sss.pgh.pa.us 5238 : 275 : appendStringInfo(buf, "CREATE RULE %s AS",
5239 : : quote_identifier(rulename));
5240 : :
5241 [ + - ]: 275 : if (prettyFlags & PRETTYFLAG_INDENT)
7559 bruce@momjian.us 5242 : 275 : appendStringInfoString(buf, "\n ON ");
5243 : : else
7559 bruce@momjian.us 5244 :UBC 0 : appendStringInfoString(buf, " ON ");
5245 : :
5246 : : /* The event the rule is fired for */
9357 bruce@momjian.us 5247 [ + + + + :CBC 275 : switch (ev_type)
- ]
5248 : : {
5249 : 3 : case '1':
3818 rhaas@postgresql.org 5250 : 3 : appendStringInfoString(buf, "SELECT");
2456 tgl@sss.pgh.pa.us 5251 : 3 : viewResultDesc = RelationGetDescr(ev_relation);
9365 bruce@momjian.us 5252 : 3 : break;
5253 : :
9357 5254 : 73 : case '2':
3818 rhaas@postgresql.org 5255 : 73 : appendStringInfoString(buf, "UPDATE");
9365 bruce@momjian.us 5256 : 73 : break;
5257 : :
9357 5258 : 147 : case '3':
3818 rhaas@postgresql.org 5259 : 147 : appendStringInfoString(buf, "INSERT");
9365 bruce@momjian.us 5260 : 147 : break;
5261 : :
9357 5262 : 52 : case '4':
3818 rhaas@postgresql.org 5263 : 52 : appendStringInfoString(buf, "DELETE");
9365 bruce@momjian.us 5264 : 52 : break;
5265 : :
9357 bruce@momjian.us 5266 :UBC 0 : default:
7567 tgl@sss.pgh.pa.us 5267 [ # # ]: 0 : ereport(ERROR,
5268 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5269 : : errmsg("rule \"%s\" has unsupported event type %d",
5270 : : rulename, ev_type)));
5271 : : break;
5272 : : }
5273 : :
5274 : : /* The relation the rule is fired on */
2239 tgl@sss.pgh.pa.us 5275 :CBC 275 : appendStringInfo(buf, " TO %s",
5276 [ + + ]: 275 : (prettyFlags & PRETTYFLAG_SCHEMA) ?
5277 : 57 : generate_relation_name(ev_class, NIL) :
5278 : 218 : generate_qualified_relation_name(ev_class));
5279 : :
5280 : : /* If the rule has an event qualification, add it */
1259 5281 [ + + ]: 275 : if (strcmp(ev_qual, "<>") != 0)
5282 : : {
5283 : : Node *qual;
5284 : : Query *query;
5285 : : deparse_context context;
5286 : : deparse_namespace dpns;
5287 : :
7564 5288 [ + - ]: 59 : if (prettyFlags & PRETTYFLAG_INDENT)
7559 bruce@momjian.us 5289 : 59 : appendStringInfoString(buf, "\n ");
3818 rhaas@postgresql.org 5290 : 59 : appendStringInfoString(buf, " WHERE ");
5291 : :
9357 bruce@momjian.us 5292 : 59 : qual = stringToNode(ev_qual);
5293 : :
5294 : : /*
5295 : : * We need to make a context for recognizing any Vars in the qual
5296 : : * (which can only be references to OLD and NEW). Use the rtable of
5297 : : * the first query in the action list for this purpose.
5298 : : */
7263 neilc@samurai.com 5299 : 59 : query = (Query *) linitial(actions);
5300 : :
5301 : : /*
5302 : : * If the action is INSERT...SELECT, OLD/NEW have been pushed down
5303 : : * into the SELECT, and that's what we need to look at. (Ugly kluge
5304 : : * ... try to fix this when we redesign querytrees.)
5305 : : */
8175 tgl@sss.pgh.pa.us 5306 : 59 : query = getInsertSelectQuery(query, NULL);
5307 : :
5308 : : /* Must acquire locks right away; see notes in get_query_def() */
3692 5309 : 59 : AcquireRewriteLocks(query, false, false);
5310 : :
8960 5311 : 59 : context.buf = buf;
7259 neilc@samurai.com 5312 : 59 : context.namespaces = list_make1(&dpns);
5586 tgl@sss.pgh.pa.us 5313 : 59 : context.windowClause = NIL;
5314 : 59 : context.windowTList = NIL;
7259 neilc@samurai.com 5315 : 59 : context.varprefix = (list_length(query->rtable) != 1);
7564 tgl@sss.pgh.pa.us 5316 : 59 : context.prettyFlags = prettyFlags;
4129 5317 : 59 : context.wrapColumn = WRAP_COLUMN_DEFAULT;
7564 5318 : 59 : context.indentLevel = PRETTYINDENT_STD;
3256 andres@anarazel.de 5319 : 59 : context.special_exprkind = EXPR_KIND_NONE;
1586 tgl@sss.pgh.pa.us 5320 : 59 : context.appendparents = NULL;
5321 : :
4122 5322 : 59 : set_deparse_for_query(&dpns, query, NIL);
5323 : :
7878 5324 : 59 : get_rule_expr(qual, &context, false);
5325 : : }
5326 : :
3818 rhaas@postgresql.org 5327 : 275 : appendStringInfoString(buf, " DO ");
5328 : :
5329 : : /* The INSTEAD keyword (if so) */
9357 bruce@momjian.us 5330 [ + + ]: 275 : if (is_instead)
3818 rhaas@postgresql.org 5331 : 163 : appendStringInfoString(buf, "INSTEAD ");
5332 : :
5333 : : /* Finally the rules actions */
7259 neilc@samurai.com 5334 [ + + ]: 275 : if (list_length(actions) > 1)
5335 : : {
5336 : : ListCell *action;
5337 : : Query *query;
5338 : :
3818 rhaas@postgresql.org 5339 : 10 : appendStringInfoChar(buf, '(');
9357 bruce@momjian.us 5340 [ + - + + : 30 : foreach(action, actions)
+ + ]
5341 : : {
5342 : 20 : query = (Query *) lfirst(action);
694 tgl@sss.pgh.pa.us 5343 : 20 : get_query_def(query, buf, NIL, viewResultDesc, true,
5344 : : prettyFlags, WRAP_COLUMN_DEFAULT, 0);
7564 5345 [ + - ]: 20 : if (prettyFlags)
3818 rhaas@postgresql.org 5346 : 20 : appendStringInfoString(buf, ";\n");
5347 : : else
3818 rhaas@postgresql.org 5348 :UBC 0 : appendStringInfoString(buf, "; ");
5349 : : }
3818 rhaas@postgresql.org 5350 :CBC 10 : appendStringInfoString(buf, ");");
5351 : : }
5352 : : else
5353 : : {
5354 : : Query *query;
5355 : :
7263 neilc@samurai.com 5356 : 265 : query = (Query *) linitial(actions);
694 tgl@sss.pgh.pa.us 5357 : 265 : get_query_def(query, buf, NIL, viewResultDesc, true,
5358 : : prettyFlags, WRAP_COLUMN_DEFAULT, 0);
3818 rhaas@postgresql.org 5359 : 265 : appendStringInfoChar(buf, ';');
5360 : : }
5361 : :
1910 andres@anarazel.de 5362 : 275 : table_close(ev_relation, AccessShareLock);
9365 bruce@momjian.us 5363 : 275 : }
5364 : :
5365 : :
5366 : : /* ----------
5367 : : * make_viewdef - reconstruct the SELECT part of a
5368 : : * view rewrite rule
5369 : : * ----------
5370 : : */
5371 : : static void
7564 tgl@sss.pgh.pa.us 5372 : 1549 : make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
5373 : : int prettyFlags, int wrapColumn)
5374 : : {
5375 : : Query *query;
5376 : : char ev_type;
5377 : : Oid ev_class;
5378 : : bool is_instead;
5379 : : char *ev_qual;
5380 : : char *ev_action;
5381 : : List *actions;
5382 : : Relation ev_relation;
5383 : : int fno;
5384 : : Datum dat;
5385 : : bool isnull;
5386 : :
5387 : : /*
5388 : : * Get the attribute values from the rules tuple
5389 : : */
9357 bruce@momjian.us 5390 : 1549 : fno = SPI_fnumber(rulettc, "ev_type");
2456 tgl@sss.pgh.pa.us 5391 : 1549 : dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
5392 [ - + ]: 1549 : Assert(!isnull);
5393 : 1549 : ev_type = DatumGetChar(dat);
5394 : :
9357 bruce@momjian.us 5395 : 1549 : fno = SPI_fnumber(rulettc, "ev_class");
2456 tgl@sss.pgh.pa.us 5396 : 1549 : dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
5397 [ - + ]: 1549 : Assert(!isnull);
5398 : 1549 : ev_class = DatumGetObjectId(dat);
5399 : :
9357 bruce@momjian.us 5400 : 1549 : fno = SPI_fnumber(rulettc, "is_instead");
2456 tgl@sss.pgh.pa.us 5401 : 1549 : dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
5402 [ - + ]: 1549 : Assert(!isnull);
5403 : 1549 : is_instead = DatumGetBool(dat);
5404 : :
9357 bruce@momjian.us 5405 : 1549 : fno = SPI_fnumber(rulettc, "ev_qual");
5406 : 1549 : ev_qual = SPI_getvalue(ruletup, rulettc, fno);
1259 tgl@sss.pgh.pa.us 5407 [ - + ]: 1549 : Assert(ev_qual != NULL);
5408 : :
9357 bruce@momjian.us 5409 : 1549 : fno = SPI_fnumber(rulettc, "ev_action");
5410 : 1549 : ev_action = SPI_getvalue(ruletup, rulettc, fno);
1259 tgl@sss.pgh.pa.us 5411 [ - + ]: 1549 : Assert(ev_action != NULL);
5412 : 1549 : actions = (List *) stringToNode(ev_action);
5413 : :
7259 neilc@samurai.com 5414 [ - + ]: 1549 : if (list_length(actions) != 1)
5415 : : {
5416 : : /* keep output buffer empty and leave */
8961 tgl@sss.pgh.pa.us 5417 :UBC 0 : return;
5418 : : }
5419 : :
7263 neilc@samurai.com 5420 :CBC 1549 : query = (Query *) linitial(actions);
5421 : :
3874 kgrittn@postgresql.o 5422 [ + - + - ]: 1549 : if (ev_type != '1' || !is_instead ||
7920 tgl@sss.pgh.pa.us 5423 [ + - - + ]: 1549 : strcmp(ev_qual, "<>") != 0 || query->commandType != CMD_SELECT)
5424 : : {
5425 : : /* keep output buffer empty and leave */
8961 tgl@sss.pgh.pa.us 5426 :UBC 0 : return;
5427 : : }
5428 : :
1910 andres@anarazel.de 5429 :CBC 1549 : ev_relation = table_open(ev_class, AccessShareLock);
5430 : :
694 tgl@sss.pgh.pa.us 5431 : 1549 : get_query_def(query, buf, NIL, RelationGetDescr(ev_relation), true,
5432 : : prettyFlags, wrapColumn, 0);
3818 rhaas@postgresql.org 5433 : 1549 : appendStringInfoChar(buf, ';');
5434 : :
1910 andres@anarazel.de 5435 : 1549 : table_close(ev_relation, AccessShareLock);
5436 : : }
5437 : :
5438 : :
5439 : : /* ----------
5440 : : * get_query_def - Parse back one query parsetree
5441 : : *
5442 : : * query: parsetree to be displayed
5443 : : * buf: output text is appended to buf
5444 : : * parentnamespace: list (initially empty) of outer-level deparse_namespace's
5445 : : * resultDesc: if not NULL, the output tuple descriptor for the view
5446 : : * represented by a SELECT query. We use the column names from it
5447 : : * to label SELECT output columns, in preference to names in the query
5448 : : * colNamesVisible: true if the surrounding context cares about the output
5449 : : * column names at all (as, for example, an EXISTS() context does not);
5450 : : * when false, we can suppress dummy column labels such as "?column?"
5451 : : * prettyFlags: bitmask of PRETTYFLAG_XXX options
5452 : : * wrapColumn: maximum line length, or -1 to disable wrapping
5453 : : * startIndent: initial indentation amount
5454 : : * ----------
5455 : : */
5456 : : static void
7920 tgl@sss.pgh.pa.us 5457 : 2550 : get_query_def(Query *query, StringInfo buf, List *parentnamespace,
5458 : : TupleDesc resultDesc, bool colNamesVisible,
5459 : : int prettyFlags, int wrapColumn, int startIndent)
5460 : : {
5461 : : deparse_context context;
5462 : : deparse_namespace dpns;
5463 : :
5464 : : /* Guard against excessively long or deeply-nested queries */
3637 5465 [ - + ]: 2550 : CHECK_FOR_INTERRUPTS();
5466 : 2550 : check_stack_depth();
5467 : :
5468 : : /*
5469 : : * Before we begin to examine the query, acquire locks on referenced
5470 : : * relations, and fix up deleted columns in JOIN RTEs. This ensures
5471 : : * consistent results. Note we assume it's OK to scribble on the passed
5472 : : * querytree!
5473 : : *
5474 : : * We are only deparsing the query (we are not about to execute it), so we
5475 : : * only need AccessShareLock on the relations it mentions.
5476 : : */
3692 5477 : 2550 : AcquireRewriteLocks(query, false, false);
5478 : :
8960 5479 : 2550 : context.buf = buf;
7263 neilc@samurai.com 5480 : 2550 : context.namespaces = lcons(&dpns, list_copy(parentnamespace));
5586 tgl@sss.pgh.pa.us 5481 : 2550 : context.windowClause = NIL;
5482 : 2550 : context.windowTList = NIL;
8460 5483 [ + + + + ]: 4384 : context.varprefix = (parentnamespace != NIL ||
7259 neilc@samurai.com 5484 : 1834 : list_length(query->rtable) != 1);
7564 tgl@sss.pgh.pa.us 5485 : 2550 : context.prettyFlags = prettyFlags;
4129 5486 : 2550 : context.wrapColumn = wrapColumn;
7564 5487 : 2550 : context.indentLevel = startIndent;
3256 andres@anarazel.de 5488 : 2550 : context.special_exprkind = EXPR_KIND_NONE;
1586 tgl@sss.pgh.pa.us 5489 : 2550 : context.appendparents = NULL;
5490 : :
4122 5491 : 2550 : set_deparse_for_query(&dpns, query, parentnamespace);
5492 : :
9357 bruce@momjian.us 5493 [ + + + + : 2550 : switch (query->commandType)
+ + + - ]
5494 : : {
9091 5495 : 2244 : case CMD_SELECT:
694 tgl@sss.pgh.pa.us 5496 : 2244 : get_select_query_def(query, &context, resultDesc, colNamesVisible);
9357 bruce@momjian.us 5497 : 2244 : break;
5498 : :
5499 : 65 : case CMD_UPDATE:
694 tgl@sss.pgh.pa.us 5500 : 65 : get_update_query_def(query, &context, colNamesVisible);
9357 bruce@momjian.us 5501 : 65 : break;
5502 : :
5503 : 170 : case CMD_INSERT:
694 tgl@sss.pgh.pa.us 5504 : 170 : get_insert_query_def(query, &context, colNamesVisible);
9357 bruce@momjian.us 5505 : 170 : break;
5506 : :
5507 : 38 : case CMD_DELETE:
694 tgl@sss.pgh.pa.us 5508 : 38 : get_delete_query_def(query, &context, colNamesVisible);
9357 bruce@momjian.us 5509 : 38 : break;
5510 : :
343 tgl@sss.pgh.pa.us 5511 : 6 : case CMD_MERGE:
5512 : 6 : get_merge_query_def(query, &context, colNamesVisible);
5513 : 6 : break;
5514 : :
9357 bruce@momjian.us 5515 : 19 : case CMD_NOTHING:
3818 rhaas@postgresql.org 5516 : 19 : appendStringInfoString(buf, "NOTHING");
9357 bruce@momjian.us 5517 : 19 : break;
5518 : :
8502 tgl@sss.pgh.pa.us 5519 : 8 : case CMD_UTILITY:
5520 : 8 : get_utility_query_def(query, &context);
5521 : 8 : break;
5522 : :
9357 bruce@momjian.us 5523 :UBC 0 : default:
7567 tgl@sss.pgh.pa.us 5524 [ # # ]: 0 : elog(ERROR, "unrecognized query command type: %d",
5525 : : query->commandType);
5526 : : break;
5527 : : }
9365 bruce@momjian.us 5528 :CBC 2550 : }
5529 : :
5530 : : /* ----------
5531 : : * get_values_def - Parse back a VALUES list
5532 : : * ----------
5533 : : */
5534 : : static void
6465 mail@joeconway.com 5535 : 136 : get_values_def(List *values_lists, deparse_context *context)
5536 : : {
5537 : 136 : StringInfo buf = context->buf;
5538 : 136 : bool first_list = true;
5539 : : ListCell *vtl;
5540 : :
5541 : 136 : appendStringInfoString(buf, "VALUES ");
5542 : :
5543 [ + - + + : 389 : foreach(vtl, values_lists)
+ + ]
5544 : : {
5545 : 253 : List *sublist = (List *) lfirst(vtl);
5546 : 253 : bool first_col = true;
5547 : : ListCell *lc;
5548 : :
5549 [ + + ]: 253 : if (first_list)
5550 : 136 : first_list = false;
5551 : : else
5552 : 117 : appendStringInfoString(buf, ", ");
5553 : :
5554 : 253 : appendStringInfoChar(buf, '(');
5555 [ + - + + : 979 : foreach(lc, sublist)
+ + ]
5556 : : {
6402 bruce@momjian.us 5557 : 726 : Node *col = (Node *) lfirst(lc);
5558 : :
6465 mail@joeconway.com 5559 [ + + ]: 726 : if (first_col)
5560 : 253 : first_col = false;
5561 : : else
5562 : 473 : appendStringInfoChar(buf, ',');
5563 : :
5564 : : /*
5565 : : * Print the value. Whole-row Vars need special treatment.
5566 : : */
2811 tgl@sss.pgh.pa.us 5567 : 726 : get_rule_expr_toplevel(col, context, false);
5568 : : }
6465 mail@joeconway.com 5569 : 253 : appendStringInfoChar(buf, ')');
5570 : : }
5571 : 136 : }
5572 : :
5573 : : /* ----------
5574 : : * get_with_clause - Parse back a WITH clause
5575 : : * ----------
5576 : : */
5577 : : static void
5671 tgl@sss.pgh.pa.us 5578 : 2523 : get_with_clause(Query *query, deparse_context *context)
5579 : : {
5580 : 2523 : StringInfo buf = context->buf;
5581 : : const char *sep;
5582 : : ListCell *l;
5583 : :
5584 [ + + ]: 2523 : if (query->cteList == NIL)
5585 : 2478 : return;
5586 : :
5587 [ + - ]: 45 : if (PRETTY_INDENT(context))
5588 : : {
5589 : 45 : context->indentLevel += PRETTYINDENT_STD;
5590 : 45 : appendStringInfoChar(buf, ' ');
5591 : : }
5592 : :
5593 [ + + ]: 45 : if (query->hasRecursive)
5594 : 28 : sep = "WITH RECURSIVE ";
5595 : : else
5596 : 17 : sep = "WITH ";
5597 [ + - + + : 109 : foreach(l, query->cteList)
+ + ]
5598 : : {
5599 : 64 : CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
5600 : :
5601 : 64 : appendStringInfoString(buf, sep);
5602 : 64 : appendStringInfoString(buf, quote_identifier(cte->ctename));
5603 [ + + ]: 64 : if (cte->aliascolnames)
5604 : : {
5605 : 28 : bool first = true;
5606 : : ListCell *col;
5607 : :
5608 : 28 : appendStringInfoChar(buf, '(');
5609 [ + - + + : 74 : foreach(col, cte->aliascolnames)
+ + ]
5610 : : {
5611 [ + + ]: 46 : if (first)
5612 : 28 : first = false;
5613 : : else
5614 : 18 : appendStringInfoString(buf, ", ");
5615 : 46 : appendStringInfoString(buf,
5616 : 46 : quote_identifier(strVal(lfirst(col))));
5617 : : }
5618 : 28 : appendStringInfoChar(buf, ')');
5619 : : }
1884 5620 : 64 : appendStringInfoString(buf, " AS ");
5621 [ + + - - ]: 64 : switch (cte->ctematerialized)
5622 : : {
5623 : 55 : case CTEMaterializeDefault:
5624 : 55 : break;
5625 : 9 : case CTEMaterializeAlways:
5626 : 9 : appendStringInfoString(buf, "MATERIALIZED ");
5627 : 9 : break;
1884 tgl@sss.pgh.pa.us 5628 :UBC 0 : case CTEMaterializeNever:
5629 : 0 : appendStringInfoString(buf, "NOT MATERIALIZED ");
5630 : 0 : break;
5631 : : }
1884 tgl@sss.pgh.pa.us 5632 :CBC 64 : appendStringInfoChar(buf, '(');
5671 5633 [ + - ]: 64 : if (PRETTY_INDENT(context))
5634 : 64 : appendContextKeyword(context, "", 0, 0, 0);
5635 : 64 : get_query_def((Query *) cte->ctequery, buf, context->namespaces, NULL,
5636 : : true,
5637 : : context->prettyFlags, context->wrapColumn,
5638 : : context->indentLevel);
5639 [ + - ]: 64 : if (PRETTY_INDENT(context))
5640 : 64 : appendContextKeyword(context, "", 0, 0, 0);
5641 : 64 : appendStringInfoChar(buf, ')');
5642 : :
1168 peter@eisentraut.org 5643 [ + + ]: 64 : if (cte->search_clause)
5644 : : {
5645 : 3 : bool first = true;
5646 : : ListCell *lc;
5647 : :
1168 peter@eisentraut.org 5648 :UBC 0 : appendStringInfo(buf, " SEARCH %s FIRST BY ",
1168 peter@eisentraut.org 5649 [ - + ]:CBC 3 : cte->search_clause->search_breadth_first ? "BREADTH" : "DEPTH");
5650 : :
5651 [ + - + + : 9 : foreach(lc, cte->search_clause->search_col_list)
+ + ]
5652 : : {
5653 [ + + ]: 6 : if (first)
5654 : 3 : first = false;
5655 : : else
5656 : 3 : appendStringInfoString(buf, ", ");
5657 : 6 : appendStringInfoString(buf,
5658 : 6 : quote_identifier(strVal(lfirst(lc))));
5659 : : }
5660 : :
5661 : 3 : appendStringInfo(buf, " SET %s", quote_identifier(cte->search_clause->search_seq_column));
5662 : : }
5663 : :
5664 [ + + ]: 64 : if (cte->cycle_clause)
5665 : : {
5666 : 6 : bool first = true;
5667 : : ListCell *lc;
5668 : :
5669 : 6 : appendStringInfoString(buf, " CYCLE ");
5670 : :
5671 [ + - + + : 18 : foreach(lc, cte->cycle_clause->cycle_col_list)
+ + ]
5672 : : {
5673 [ + + ]: 12 : if (first)
5674 : 6 : first = false;
5675 : : else
5676 : 6 : appendStringInfoString(buf, ", ");
5677 : 12 : appendStringInfoString(buf,
5678 : 12 : quote_identifier(strVal(lfirst(lc))));
5679 : : }
5680 : :
5681 : 6 : appendStringInfo(buf, " SET %s", quote_identifier(cte->cycle_clause->cycle_mark_column));
5682 : :
5683 : : {
1142 5684 : 6 : Const *cmv = castNode(Const, cte->cycle_clause->cycle_mark_value);
5685 : 6 : Const *cmd = castNode(Const, cte->cycle_clause->cycle_mark_default);
5686 : :
5687 [ + + + - : 9 : if (!(cmv->consttype == BOOLOID && !cmv->constisnull && DatumGetBool(cmv->constvalue) == true &&
+ - - + ]
5688 [ + - + - ]: 3 : cmd->consttype == BOOLOID && !cmd->constisnull && DatumGetBool(cmd->constvalue) == false))
5689 : : {
5690 : 3 : appendStringInfoString(buf, " TO ");
5691 : 3 : get_rule_expr(cte->cycle_clause->cycle_mark_value, context, false);
5692 : 3 : appendStringInfoString(buf, " DEFAULT ");
5693 : 3 : get_rule_expr(cte->cycle_clause->cycle_mark_default, context, false);
5694 : : }
5695 : : }
5696 : :
1168 5697 : 6 : appendStringInfo(buf, " USING %s", quote_identifier(cte->cycle_clause->cycle_path_column));
5698 : : }
5699 : :
5671 tgl@sss.pgh.pa.us 5700 : 64 : sep = ", ";
5701 : : }
5702 : :
5703 [ + - ]: 45 : if (PRETTY_INDENT(context))
5704 : : {
5705 : 45 : context->indentLevel -= PRETTYINDENT_STD;
5706 : 45 : appendContextKeyword(context, "", 0, 0, 0);
5707 : : }
5708 : : else
5671 tgl@sss.pgh.pa.us 5709 :UBC 0 : appendStringInfoChar(buf, ' ');
5710 : : }
5711 : :
5712 : : /* ----------
5713 : : * get_select_query_def - Parse back a SELECT parsetree
5714 : : * ----------
5715 : : */
5716 : : static void
7920 tgl@sss.pgh.pa.us 5717 :CBC 2244 : get_select_query_def(Query *query, deparse_context *context,
5718 : : TupleDesc resultDesc, bool colNamesVisible)
5719 : : {
8592 5720 : 2244 : StringInfo buf = context->buf;
5721 : : List *save_windowclause;
5722 : : List *save_windowtlist;
5723 : : bool force_colno;
5724 : : ListCell *l;
5725 : :
5726 : : /* Insert the WITH clause if given */
5671 5727 : 2244 : get_with_clause(query, context);
5728 : :
5729 : : /* Set up context for possible window functions */
5586 5730 : 2244 : save_windowclause = context->windowClause;
5731 : 2244 : context->windowClause = query->windowClause;
5732 : 2244 : save_windowtlist = context->windowTList;
5733 : 2244 : context->windowTList = query->targetList;
5734 : :
5735 : : /*
5736 : : * If the Query node has a setOperations tree, then it's the top level of
5737 : : * a UNION/INTERSECT/EXCEPT query; only the WITH, ORDER BY and LIMIT
5738 : : * fields are interesting in the top query itself.
5739 : : */
8592 5740 [ + + ]: 2244 : if (query->setOperations)
5741 : : {
694 5742 : 72 : get_setop_query(query->setOperations, query, context, resultDesc,
5743 : : colNamesVisible);
5744 : : /* ORDER BY clauses must be simple in this case */
8400 5745 : 72 : force_colno = true;
5746 : : }
5747 : : else
5748 : : {
694 5749 : 2172 : get_basic_select_query(query, context, resultDesc, colNamesVisible);
8400 5750 : 2172 : force_colno = false;
5751 : : }
5752 : :
5753 : : /* Add the ORDER BY clause if given */
8592 5754 [ + + ]: 2244 : if (query->sortClause != NIL)
5755 : : {
7559 bruce@momjian.us 5756 : 65 : appendContextKeyword(context, " ORDER BY ",
5757 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
5586 tgl@sss.pgh.pa.us 5758 : 65 : get_rule_orderby(query->sortClause, query->targetList,
5759 : : force_colno, context);
5760 : : }
5761 : :
5762 : : /*
5763 : : * Add the LIMIT/OFFSET clauses if given. If non-default options, use the
5764 : : * standard spelling of LIMIT.
5765 : : */
8571 5766 [ + + ]: 2244 : if (query->limitOffset != NULL)
5767 : : {
7564 5768 : 16 : appendContextKeyword(context, " OFFSET ",
5769 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
7878 5770 : 16 : get_rule_expr(query->limitOffset, context, false);
5771 : : }
8571 5772 [ + + ]: 2244 : if (query->limitCount != NULL)
5773 : : {
1468 alvherre@alvh.no-ip. 5774 [ + + ]: 35 : if (query->limitOption == LIMIT_OPTION_WITH_TIES)
5775 : : {
5776 : 16 : appendContextKeyword(context, " FETCH FIRST ",
5777 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
7878 tgl@sss.pgh.pa.us 5778 : 16 : get_rule_expr(query->limitCount, context, false);
1277 drowley@postgresql.o 5779 : 16 : appendStringInfoString(buf, " ROWS WITH TIES");
5780 : : }
5781 : : else
5782 : : {
1468 alvherre@alvh.no-ip. 5783 : 19 : appendContextKeyword(context, " LIMIT ",
5784 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
5785 [ + + ]: 19 : if (IsA(query->limitCount, Const) &&
5786 [ + - ]: 8 : ((Const *) query->limitCount)->constisnull)
5787 : 8 : appendStringInfoString(buf, "ALL");
5788 : : else
5789 : 11 : get_rule_expr(query->limitCount, context, false);
5790 : : }
5791 : : }
5792 : :
5793 : : /* Add FOR [KEY] UPDATE/SHARE clauses if present */
5282 tgl@sss.pgh.pa.us 5794 [ + + ]: 2244 : if (query->hasForUpdate)
5795 : : {
5796 [ + - + + : 6 : foreach(l, query->rowMarks)
+ + ]
5797 : : {
5798 : 3 : RowMarkClause *rc = (RowMarkClause *) lfirst(l);
5799 : :
5800 : : /* don't print implicit clauses */
5801 [ - + ]: 3 : if (rc->pushedDown)
5282 tgl@sss.pgh.pa.us 5802 :UBC 0 : continue;
5803 : :
4099 alvherre@alvh.no-ip. 5804 [ - - - - :CBC 3 : switch (rc->strength)
+ - ]
5805 : : {
3318 tgl@sss.pgh.pa.us 5806 :UBC 0 : case LCS_NONE:
5807 : : /* we intentionally throw an error for LCS_NONE */
5808 [ # # ]: 0 : elog(ERROR, "unrecognized LockClauseStrength %d",
5809 : : (int) rc->strength);
5810 : : break;
4099 alvherre@alvh.no-ip. 5811 : 0 : case LCS_FORKEYSHARE:
5812 : 0 : appendContextKeyword(context, " FOR KEY SHARE",
5813 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
5814 : 0 : break;
5815 : 0 : case LCS_FORSHARE:
5816 : 0 : appendContextKeyword(context, " FOR SHARE",
5817 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
5818 : 0 : break;
5819 : 0 : case LCS_FORNOKEYUPDATE:
5820 : 0 : appendContextKeyword(context, " FOR NO KEY UPDATE",
5821 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
5822 : 0 : break;
4099 alvherre@alvh.no-ip. 5823 :CBC 3 : case LCS_FORUPDATE:
5824 : 3 : appendContextKeyword(context, " FOR UPDATE",
5825 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
5826 : 3 : break;
5827 : : }
5828 : :
5282 tgl@sss.pgh.pa.us 5829 : 3 : appendStringInfo(buf, " OF %s",
4223 5830 : 3 : quote_identifier(get_rtable_name(rc->rti,
5831 : : context)));
3477 alvherre@alvh.no-ip. 5832 [ - + ]: 3 : if (rc->waitPolicy == LockWaitError)
3818 rhaas@postgresql.org 5833 :UBC 0 : appendStringInfoString(buf, " NOWAIT");
3477 alvherre@alvh.no-ip. 5834 [ - + ]:CBC 3 : else if (rc->waitPolicy == LockWaitSkip)
3477 alvherre@alvh.no-ip. 5835 :UBC 0 : appendStringInfoString(buf, " SKIP LOCKED");
5836 : : }
5837 : : }
5838 : :
5586 tgl@sss.pgh.pa.us 5839 :CBC 2244 : context->windowClause = save_windowclause;
5840 : 2244 : context->windowTList = save_windowtlist;
8592 5841 : 2244 : }
5842 : :
5843 : : /*
5844 : : * Detect whether query looks like SELECT ... FROM VALUES(),
5845 : : * with no need to rename the output columns of the VALUES RTE.
5846 : : * If so, return the VALUES RTE. Otherwise return NULL.
5847 : : */
5848 : : static RangeTblEntry *
1611 5849 : 2172 : get_simple_values_rte(Query *query, TupleDesc resultDesc)
5850 : : {
4256 5851 : 2172 : RangeTblEntry *result = NULL;
5852 : : ListCell *lc;
5853 : :
5854 : : /*
5855 : : * We want to detect a match even if the Query also contains OLD or NEW
5856 : : * rule RTEs. So the idea is to scan the rtable and see if there is only
5857 : : * one inFromCl RTE that is a VALUES RTE.
5858 : : */
5859 [ + + + + : 2354 : foreach(lc, query->rtable)
+ + ]
5860 : : {
5861 : 1976 : RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
5862 : :
5863 [ + + + - ]: 1976 : if (rte->rtekind == RTE_VALUES && rte->inFromCl)
5864 : : {
5865 [ - + ]: 114 : if (result)
4256 tgl@sss.pgh.pa.us 5866 :UBC 0 : return NULL; /* multiple VALUES (probably not possible) */
4256 tgl@sss.pgh.pa.us 5867 :CBC 114 : result = rte;
5868 : : }
5869 [ + + + + ]: 1862 : else if (rte->rtekind == RTE_RELATION && !rte->inFromCl)
5870 : 68 : continue; /* ignore rule entries */
5871 : : else
5872 : 1794 : return NULL; /* something else -> not simple VALUES */
5873 : : }
5874 : :
5875 : : /*
5876 : : * We don't need to check the targetlist in any great detail, because
5877 : : * parser/analyze.c will never generate a "bare" VALUES RTE --- they only
5878 : : * appear inside auto-generated sub-queries with very restricted
5879 : : * structure. However, DefineView might have modified the tlist by
5880 : : * injecting new column aliases, or we might have some other column
5881 : : * aliases forced by a resultDesc. We can only simplify if the RTE's
5882 : : * column names match the names that get_target_list() would select.
5883 : : */
3336 5884 [ + + ]: 378 : if (result)
5885 : : {
5886 : : ListCell *lcn;
5887 : : int colno;
5888 : :
5889 [ - + ]: 114 : if (list_length(query->targetList) != list_length(result->eref->colnames))
3336 tgl@sss.pgh.pa.us 5890 :UBC 0 : return NULL; /* this probably cannot happen */
1611 tgl@sss.pgh.pa.us 5891 :CBC 114 : colno = 0;
3336 5892 [ + - + + : 421 : forboth(lc, query->targetList, lcn, result->eref->colnames)
+ - + + +
+ + - +
+ ]
5893 : : {
5894 : 313 : TargetEntry *tle = (TargetEntry *) lfirst(lc);
5895 : 313 : char *cname = strVal(lfirst(lcn));
5896 : : char *colname;
5897 : :
5898 [ - + ]: 313 : if (tle->resjunk)
3336 tgl@sss.pgh.pa.us 5899 :UBC 0 : return NULL; /* this probably cannot happen */
5900 : :
5901 : : /* compute name that get_target_list would use for column */
1611 tgl@sss.pgh.pa.us 5902 :CBC 313 : colno++;
5903 [ + + + - ]: 313 : if (resultDesc && colno <= resultDesc->natts)
5904 : 15 : colname = NameStr(TupleDescAttr(resultDesc, colno - 1)->attname);
5905 : : else
5906 : 298 : colname = tle->resname;
5907 : :
5908 : : /* does it match the VALUES RTE? */
5909 [ + - + + ]: 313 : if (colname == NULL || strcmp(colname, cname) != 0)
3336 5910 : 6 : return NULL; /* column name has been changed */
5911 : : }
5912 : : }
5913 : :
4256 5914 : 372 : return result;
5915 : : }
5916 : :
5917 : : static void
7920 5918 : 2172 : get_basic_select_query(Query *query, deparse_context *context,
5919 : : TupleDesc resultDesc, bool colNamesVisible)
5920 : : {
8960 5921 : 2172 : StringInfo buf = context->buf;
5922 : : RangeTblEntry *values_rte;
5923 : : char *sep;
5924 : : ListCell *l;
5925 : :
7564 5926 [ + + ]: 2172 : if (PRETTY_INDENT(context))
5927 : : {
7559 bruce@momjian.us 5928 : 2149 : context->indentLevel += PRETTYINDENT_STD;
5929 : 2149 : appendStringInfoChar(buf, ' ');
5930 : : }
5931 : :
5932 : : /*
5933 : : * If the query looks like SELECT * FROM (VALUES ...), then print just the
5934 : : * VALUES part. This reverses what transformValuesClause() did at parse
5935 : : * time.
5936 : : */
1611 tgl@sss.pgh.pa.us 5937 : 2172 : values_rte = get_simple_values_rte(query, resultDesc);
4256 5938 [ + + ]: 2172 : if (values_rte)
5939 : : {
5940 : 108 : get_values_def(values_rte->values_lists, context);
5941 : 108 : return;
5942 : : }
5943 : :
5944 : : /*
5945 : : * Build up the query string - first we say SELECT
5946 : : */
1103 peter@eisentraut.org 5947 [ + + ]: 2064 : if (query->isReturn)
5948 : 26 : appendStringInfoString(buf, "RETURN");
5949 : : else
5950 : 2038 : appendStringInfoString(buf, "SELECT");
5951 : :
5952 : : /* Add the DISTINCT clause if given */
8592 tgl@sss.pgh.pa.us 5953 [ - + ]: 2064 : if (query->distinctClause != NIL)
5954 : : {
5734 tgl@sss.pgh.pa.us 5955 [ # # ]:UBC 0 : if (query->hasDistinctOn)
5956 : : {
3818 rhaas@postgresql.org 5957 : 0 : appendStringInfoString(buf, " DISTINCT ON (");
8592 tgl@sss.pgh.pa.us 5958 : 0 : sep = "";
5959 [ # # # # : 0 : foreach(l, query->distinctClause)
# # ]
5960 : : {
5734 5961 : 0 : SortGroupClause *srt = (SortGroupClause *) lfirst(l);
5962 : :
6924 neilc@samurai.com 5963 : 0 : appendStringInfoString(buf, sep);
3256 andres@anarazel.de 5964 : 0 : get_rule_sortgroupclause(srt->tleSortGroupRef, query->targetList,
5965 : : false, context);
8592 tgl@sss.pgh.pa.us 5966 : 0 : sep = ", ";
5967 : : }
3818 rhaas@postgresql.org 5968 : 0 : appendStringInfoChar(buf, ')');
5969 : : }
5970 : : else
5971 : 0 : appendStringInfoString(buf, " DISTINCT");
5972 : : }
5973 : :
5974 : : /* Then we tell what to select (the targetlist) */
694 tgl@sss.pgh.pa.us 5975 :CBC 2064 : get_target_list(query->targetList, context, resultDesc, colNamesVisible);
5976 : :
5977 : : /* Add the FROM clause if needed */
6455 5978 : 2064 : get_from_clause(query, " FROM ", context);
5979 : :
5980 : : /* Add the WHERE clause if given */
5981 [ + + ]: 2064 : if (query->jointree->quals != NULL)
5982 : : {
5983 : 600 : appendContextKeyword(context, " WHERE ",
5984 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
5985 : 600 : get_rule_expr(query->jointree->quals, context, false);
5986 : : }
5987 : :
5988 : : /* Add the GROUP BY clause if given */
3256 andres@anarazel.de 5989 [ + + - + ]: 2064 : if (query->groupClause != NULL || query->groupingSets != NULL)
5990 : : {
5991 : : ParseExprKind save_exprkind;
5992 : :
6455 tgl@sss.pgh.pa.us 5993 : 111 : appendContextKeyword(context, " GROUP BY ",
5994 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
1123 tomas.vondra@postgre 5995 [ - + ]: 111 : if (query->groupDistinct)
1123 tomas.vondra@postgre 5996 :UBC 0 : appendStringInfoString(buf, "DISTINCT ");
5997 : :
3256 andres@anarazel.de 5998 :CBC 111 : save_exprkind = context->special_exprkind;
5999 : 111 : context->special_exprkind = EXPR_KIND_GROUP_BY;
6000 : :
6001 [ + + ]: 111 : if (query->groupingSets == NIL)
6002 : : {
6003 : 108 : sep = "";
6004 [ + - + + : 237 : foreach(l, query->groupClause)
+ + ]
6005 : : {
6006 : 129 : SortGroupClause *grp = (SortGroupClause *) lfirst(l);
6007 : :
6008 : 129 : appendStringInfoString(buf, sep);
6009 : 129 : get_rule_sortgroupclause(grp->tleSortGroupRef, query->targetList,
6010 : : false, context);
6011 : 129 : sep = ", ";
6012 : : }
6013 : : }
6014 : : else
6015 : : {
6016 : 3 : sep = "";
6017 [ + - + + : 6 : foreach(l, query->groupingSets)
+ + ]
6018 : : {
6019 : 3 : GroupingSet *grp = lfirst(l);
6020 : :
6021 : 3 : appendStringInfoString(buf, sep);
6022 : 3 : get_rule_groupingset(grp, query->targetList, true, context);
6023 : 3 : sep = ", ";
6024 : : }
6025 : : }
6026 : :
6027 : 111 : context->special_exprkind = save_exprkind;
6028 : : }
6029 : :
6030 : : /* Add the HAVING clause if given */
6455 tgl@sss.pgh.pa.us 6031 [ + + ]: 2064 : if (query->havingQual != NULL)
6032 : : {
6033 : 5 : appendContextKeyword(context, " HAVING ",
6034 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
6035 : 5 : get_rule_expr(query->havingQual, context, false);
6036 : : }
6037 : :
6038 : : /* Add the WINDOW clause if needed */
5586 6039 [ + + ]: 2064 : if (query->windowClause != NIL)
6040 : 21 : get_rule_windowclause(query, context);
6041 : : }
6042 : :
6043 : : /* ----------
6044 : : * get_target_list - Parse back a SELECT target list
6045 : : *
6046 : : * This is also used for RETURNING lists in INSERT/UPDATE/DELETE.
6047 : : *
6048 : : * resultDesc and colNamesVisible are as for get_query_def()
6049 : : * ----------
6050 : : */
6051 : : static void
6455 6052 : 2125 : get_target_list(List *targetList, deparse_context *context,
6053 : : TupleDesc resultDesc, bool colNamesVisible)
6054 : : {
6055 : 2125 : StringInfo buf = context->buf;
6056 : : StringInfoData targetbuf;
4129 6057 : 2125 : bool last_was_multiline = false;
6058 : : char *sep;
6059 : : int colno;
6060 : : ListCell *l;
6061 : :
6062 : : /* we use targetbuf to hold each TLE's text temporarily */
6063 : 2125 : initStringInfo(&targetbuf);
6064 : :
9365 bruce@momjian.us 6065 : 2125 : sep = " ";
7920 tgl@sss.pgh.pa.us 6066 : 2125 : colno = 0;
6455 6067 [ + - + + : 9682 : foreach(l, targetList)
+ + ]
6068 : : {
8615 6069 : 7557 : TargetEntry *tle = (TargetEntry *) lfirst(l);
6070 : : char *colname;
6071 : : char *attname;
6072 : :
6948 6073 [ + + ]: 7557 : if (tle->resjunk)
8598 6074 : 17 : continue; /* ignore junk entries */
6075 : :
6924 neilc@samurai.com 6076 : 7540 : appendStringInfoString(buf, sep);
9357 bruce@momjian.us 6077 : 7540 : sep = ", ";
7920 tgl@sss.pgh.pa.us 6078 : 7540 : colno++;
6079 : :
6080 : : /*
6081 : : * Put the new field text into targetbuf so we can decide after we've
6082 : : * got it whether or not it needs to go on a new line.
6083 : : */
4129 6084 : 7540 : resetStringInfo(&targetbuf);
4438 andrew@dunslane.net 6085 : 7540 : context->buf = &targetbuf;
6086 : :
6087 : : /*
6088 : : * We special-case Var nodes rather than using get_rule_expr. This is
6089 : : * needed because get_rule_expr will display a whole-row Var as
6090 : : * "foo.*", which is the preferred notation in most contexts, but at
6091 : : * the top level of a SELECT list it's not right (the parser will
6092 : : * expand that notation into multiple columns, yielding behavior
6093 : : * different from a whole-row Var). We need to call get_variable
6094 : : * directly so that we can tell it to do the right thing, and so that
6095 : : * we can get the attribute name which is the default AS label.
6096 : : */
3256 andres@anarazel.de 6097 [ + - + + ]: 7540 : if (tle->expr && (IsA(tle->expr, Var)))
6098 : : {
4370 tgl@sss.pgh.pa.us 6099 : 5808 : attname = get_variable((Var *) tle->expr, 0, true, context);
6100 : : }
6101 : : else
6102 : : {
6653 6103 : 1732 : get_rule_expr((Node *) tle->expr, context, true);
6104 : :
6105 : : /*
6106 : : * When colNamesVisible is true, we should always show the
6107 : : * assigned column name explicitly. Otherwise, show it only if
6108 : : * it's not FigureColname's fallback.
6109 : : */
694 6110 [ + + ]: 1732 : attname = colNamesVisible ? NULL : "?column?";
6111 : : }
6112 : :
6113 : : /*
6114 : : * Figure out what the result column should be called. In the context
6115 : : * of a view, use the view's tuple descriptor (so as to pick up the
6116 : : * effects of any column RENAME that's been done on the view).
6117 : : * Otherwise, just use what we can find in the TLE.
6118 : : */
7920 6119 [ + + + - ]: 7540 : if (resultDesc && colno <= resultDesc->natts)
2429 andres@anarazel.de 6120 : 6801 : colname = NameStr(TupleDescAttr(resultDesc, colno - 1)->attname);
6121 : : else
6948 tgl@sss.pgh.pa.us 6122 : 739 : colname = tle->resname;
6123 : :
6124 : : /* Show AS unless the column's name is correct as-is */
7552 6125 [ + + ]: 7540 : if (colname) /* resname could be NULL */
6126 : : {
6653 6127 [ + + + + ]: 7514 : if (attname == NULL || strcmp(attname, colname) != 0)
4438 andrew@dunslane.net 6128 : 2374 : appendStringInfo(&targetbuf, " AS %s", quote_identifier(colname));
6129 : : }
6130 : :
6131 : : /* Restore context's output buffer */
6132 : 7540 : context->buf = buf;
6133 : :
6134 : : /* Consider line-wrapping if enabled */
4129 tgl@sss.pgh.pa.us 6135 [ + + + - ]: 7540 : if (PRETTY_INDENT(context) && context->wrapColumn >= 0)
6136 : : {
6137 : : int leading_nl_pos;
6138 : :
6139 : : /* Does the new field start with a new line? */
3807 6140 [ + - + + ]: 7517 : if (targetbuf.len > 0 && targetbuf.data[0] == '\n')
6141 : 159 : leading_nl_pos = 0;
6142 : : else
6143 : 7358 : leading_nl_pos = -1;
6144 : :
6145 : : /* If so, we shouldn't add anything */
6146 [ + + ]: 7517 : if (leading_nl_pos >= 0)
6147 : : {
6148 : : /* instead, remove any trailing spaces currently in buf */
6149 : 159 : removeStringInfoSpaces(buf);
6150 : : }
6151 : : else
6152 : : {
6153 : : char *trailing_nl;
6154 : :
6155 : : /* Locate the start of the current line in the output buffer */
6156 : 7358 : trailing_nl = strrchr(buf->data, '\n');
6157 [ + + ]: 7358 : if (trailing_nl == NULL)
6158 : 2507 : trailing_nl = buf->data;
6159 : : else
6160 : 4851 : trailing_nl++;
6161 : :
6162 : : /*
6163 : : * Add a newline, plus some indentation, if the new field is
6164 : : * not the first and either the new field would cause an
6165 : : * overflow or the last field used more than one line.
6166 : : */
6167 [ + + ]: 7358 : if (colno > 1 &&
6168 [ - + - - ]: 5264 : ((strlen(trailing_nl) + targetbuf.len > context->wrapColumn) ||
6169 : : last_was_multiline))
6170 : 5264 : appendContextKeyword(context, "", -PRETTYINDENT_STD,
6171 : : PRETTYINDENT_STD, PRETTYINDENT_VAR);
6172 : : }
6173 : :
6174 : : /* Remember this field's multiline status for next iteration */
4129 6175 : 7517 : last_was_multiline =
6176 : 7517 : (strchr(targetbuf.data + leading_nl_pos + 1, '\n') != NULL);
6177 : : }
6178 : :
6179 : : /* Add the new field */
1727 drowley@postgresql.o 6180 : 7540 : appendBinaryStringInfo(buf, targetbuf.data, targetbuf.len);
6181 : : }
6182 : :
6183 : : /* clean up */
4129 tgl@sss.pgh.pa.us 6184 : 2125 : pfree(targetbuf.data);
8592 6185 : 2125 : }
6186 : :
6187 : : static void
7920 6188 : 304 : get_setop_query(Node *setOp, Query *query, deparse_context *context,
6189 : : TupleDesc resultDesc, bool colNamesVisible)
6190 : : {
8592 6191 : 304 : StringInfo buf = context->buf;
6192 : : bool need_paren;
6193 : :
6194 : : /* Guard against excessively long or deeply-nested queries */
3637 6195 [ - + ]: 304 : CHECK_FOR_INTERRUPTS();
6196 : 304 : check_stack_depth();
6197 : :
8592 6198 [ + + ]: 304 : if (IsA(setOp, RangeTblRef))
6199 : : {
6200 : 188 : RangeTblRef *rtr = (RangeTblRef *) setOp;
6201 : 188 : RangeTblEntry *rte = rt_fetch(rtr->rtindex, query->rtable);
8424 bruce@momjian.us 6202 : 188 : Query *subquery = rte->subquery;
6203 : :
8592 tgl@sss.pgh.pa.us 6204 [ - + ]: 188 : Assert(subquery != NULL);
7222 6205 [ - + ]: 188 : Assert(subquery->setOperations == NULL);
6206 : : /* Need parens if WITH, ORDER BY, FOR UPDATE, or LIMIT; see gram.y */
5671 6207 : 564 : need_paren = (subquery->cteList ||
6208 [ + - ]: 188 : subquery->sortClause ||
7222 6209 [ + - ]: 188 : subquery->rowMarks ||
6210 [ + - + - ]: 564 : subquery->limitOffset ||
6211 [ - + ]: 188 : subquery->limitCount);
6212 [ - + ]: 188 : if (need_paren)
7222 tgl@sss.pgh.pa.us 6213 :UBC 0 : appendStringInfoChar(buf, '(');
7564 tgl@sss.pgh.pa.us 6214 :CBC 188 : get_query_def(subquery, buf, context->namespaces, resultDesc,
6215 : : colNamesVisible,
6216 : : context->prettyFlags, context->wrapColumn,
6217 : : context->indentLevel);
7222 6218 [ - + ]: 188 : if (need_paren)
7222 tgl@sss.pgh.pa.us 6219 :UBC 0 : appendStringInfoChar(buf, ')');
6220 : : }
8592 tgl@sss.pgh.pa.us 6221 [ + - ]:CBC 116 : else if (IsA(setOp, SetOperationStmt))
6222 : : {
6223 : 116 : SetOperationStmt *op = (SetOperationStmt *) setOp;
6224 : : int subindent;
6225 : :
6226 : : /*
6227 : : * We force parens when nesting two SetOperationStmts, except when the
6228 : : * lefthand input is another setop of the same kind. Syntactically,
6229 : : * we could omit parens in rather more cases, but it seems best to use
6230 : : * parens to flag cases where the setop operator changes. If we use
6231 : : * parens, we also increase the indentation level for the child query.
6232 : : *
6233 : : * There are some cases in which parens are needed around a leaf query
6234 : : * too, but those are more easily handled at the next level down (see
6235 : : * code above).
6236 : : */
3637 6237 [ + + ]: 116 : if (IsA(op->larg, SetOperationStmt))
6238 : : {
6239 : 44 : SetOperationStmt *lop = (SetOperationStmt *) op->larg;
6240 : :
6241 [ + - + - ]: 44 : if (op->op == lop->op && op->all == lop->all)
6242 : 44 : need_paren = false;
6243 : : else
3637 tgl@sss.pgh.pa.us 6244 :UBC 0 : need_paren = true;
6245 : : }
6246 : : else
3637 tgl@sss.pgh.pa.us 6247 :CBC 72 : need_paren = false;
6248 : :
7222 6249 [ - + ]: 116 : if (need_paren)
6250 : : {
7222 tgl@sss.pgh.pa.us 6251 :UBC 0 : appendStringInfoChar(buf, '(');
3637 6252 : 0 : subindent = PRETTYINDENT_STD;
6253 : 0 : appendContextKeyword(context, "", subindent, 0, 0);
6254 : : }
6255 : : else
3637 tgl@sss.pgh.pa.us 6256 :CBC 116 : subindent = 0;
6257 : :
694 6258 : 116 : get_setop_query(op->larg, query, context, resultDesc, colNamesVisible);
6259 : :
3637 6260 [ - + ]: 116 : if (need_paren)
3637 tgl@sss.pgh.pa.us 6261 :UBC 0 : appendContextKeyword(context, ") ", -subindent, 0, 0);
3637 tgl@sss.pgh.pa.us 6262 [ + - ]:CBC 116 : else if (PRETTY_INDENT(context))
6263 : 116 : appendContextKeyword(context, "", -subindent, 0, 0);
6264 : : else
7559 bruce@momjian.us 6265 :UBC 0 : appendStringInfoChar(buf, ' ');
6266 : :
8592 tgl@sss.pgh.pa.us 6267 [ + - - - ]:CBC 116 : switch (op->op)
6268 : : {
6269 : 116 : case SETOP_UNION:
3637 6270 : 116 : appendStringInfoString(buf, "UNION ");
8592 6271 : 116 : break;
8592 tgl@sss.pgh.pa.us 6272 :UBC 0 : case SETOP_INTERSECT:
3637 6273 : 0 : appendStringInfoString(buf, "INTERSECT ");
8592 6274 : 0 : break;
6275 : 0 : case SETOP_EXCEPT:
3637 6276 : 0 : appendStringInfoString(buf, "EXCEPT ");
8592 6277 : 0 : break;
6278 : 0 : default:
7567 6279 [ # # ]: 0 : elog(ERROR, "unrecognized set op: %d",
6280 : : (int) op->op);
6281 : : }
8592 tgl@sss.pgh.pa.us 6282 [ + + ]:CBC 116 : if (op->all)
3818 rhaas@postgresql.org 6283 : 110 : appendStringInfoString(buf, "ALL ");
6284 : :
6285 : : /* Always parenthesize if RHS is another setop */
3637 tgl@sss.pgh.pa.us 6286 : 116 : need_paren = IsA(op->rarg, SetOperationStmt);
6287 : :
6288 : : /*
6289 : : * The indentation code here is deliberately a bit different from that
6290 : : * for the lefthand input, because we want the line breaks in
6291 : : * different places.
6292 : : */
7222 6293 [ - + ]: 116 : if (need_paren)
6294 : : {
7222 tgl@sss.pgh.pa.us 6295 :UBC 0 : appendStringInfoChar(buf, '(');
3637 6296 : 0 : subindent = PRETTYINDENT_STD;
6297 : : }
6298 : : else
3637 tgl@sss.pgh.pa.us 6299 :CBC 116 : subindent = 0;
6300 : 116 : appendContextKeyword(context, "", subindent, 0, 0);
6301 : :
694 6302 : 116 : get_setop_query(op->rarg, query, context, resultDesc, false);
6303 : :
5671 6304 [ + - ]: 116 : if (PRETTY_INDENT(context))
3637 6305 : 116 : context->indentLevel -= subindent;
6306 [ - + ]: 116 : if (need_paren)
3637 tgl@sss.pgh.pa.us 6307 :UBC 0 : appendContextKeyword(context, ")", 0, 0, 0);
6308 : : }
6309 : : else
6310 : : {
7567 6311 [ # # ]: 0 : elog(ERROR, "unrecognized node type: %d",
6312 : : (int) nodeTag(setOp));
6313 : : }
8592 tgl@sss.pgh.pa.us 6314 :CBC 304 : }
6315 : :
6316 : : /*
6317 : : * Display a sort/group clause.
6318 : : *
6319 : : * Also returns the expression tree, so caller need not find it again.
6320 : : */
6321 : : static Node *
3256 andres@anarazel.de 6322 : 297 : get_rule_sortgroupclause(Index ref, List *tlist, bool force_colno,
6323 : : deparse_context *context)
6324 : : {
8400 tgl@sss.pgh.pa.us 6325 : 297 : StringInfo buf = context->buf;
6326 : : TargetEntry *tle;
6327 : : Node *expr;
6328 : :
3256 andres@anarazel.de 6329 : 297 : tle = get_sortgroupref_tle(ref, tlist);
7794 tgl@sss.pgh.pa.us 6330 : 297 : expr = (Node *) tle->expr;
6331 : :
6332 : : /*
6333 : : * Use column-number form if requested by caller. Otherwise, if
6334 : : * expression is a constant, force it to be dumped with an explicit cast
6335 : : * as decoration --- this is because a simple integer constant is
6336 : : * ambiguous (and will be misinterpreted by findTargetlistEntry()) if we
6337 : : * dump it without any decoration. If it's anything more complex than a
6338 : : * simple Var, then force extra parens around it, to ensure it can't be
6339 : : * misinterpreted as a cube() or rollup() construct.
6340 : : */
5943 6341 [ - + ]: 297 : if (force_colno)
6342 : : {
6948 tgl@sss.pgh.pa.us 6343 [ # # ]:UBC 0 : Assert(!tle->resjunk);
6344 : 0 : appendStringInfo(buf, "%d", tle->resno);
6345 : : }
5943 tgl@sss.pgh.pa.us 6346 [ + - - + ]:CBC 297 : else if (expr && IsA(expr, Const))
5943 tgl@sss.pgh.pa.us 6347 :UBC 0 : get_const_expr((Const *) expr, context, 1);
3256 andres@anarazel.de 6348 [ + - + + ]:CBC 297 : else if (!expr || IsA(expr, Var))
6349 : 283 : get_rule_expr(expr, context, true);
6350 : : else
6351 : : {
6352 : : /*
6353 : : * We must force parens for function-like expressions even if
6354 : : * PRETTY_PAREN is off, since those are the ones in danger of
6355 : : * misparsing. For other expressions we need to force them only if
6356 : : * PRETTY_PAREN is on, since otherwise the expression will output them
6357 : : * itself. (We can't skip the parens.)
6358 : : */
3249 bruce@momjian.us 6359 : 28 : bool need_paren = (PRETTY_PAREN(context)
6360 [ + + ]: 14 : || IsA(expr, FuncExpr)
1429 tgl@sss.pgh.pa.us 6361 [ + - ]: 12 : || IsA(expr, Aggref)
382 alvherre@alvh.no-ip. 6362 [ + - ]: 12 : || IsA(expr, WindowFunc)
6363 [ + - - + ]: 28 : || IsA(expr, JsonConstructorExpr));
6364 : :
3256 andres@anarazel.de 6365 [ + + ]: 14 : if (need_paren)
2434 peter_e@gmx.net 6366 : 2 : appendStringInfoChar(context->buf, '(');
7878 tgl@sss.pgh.pa.us 6367 : 14 : get_rule_expr(expr, context, true);
3256 andres@anarazel.de 6368 [ + + ]: 14 : if (need_paren)
2434 peter_e@gmx.net 6369 : 2 : appendStringInfoChar(context->buf, ')');
6370 : : }
6371 : :
8017 tgl@sss.pgh.pa.us 6372 : 297 : return expr;
6373 : : }
6374 : :
6375 : : /*
6376 : : * Display a GroupingSet
6377 : : */
6378 : : static void
3256 andres@anarazel.de 6379 : 9 : get_rule_groupingset(GroupingSet *gset, List *targetlist,
6380 : : bool omit_parens, deparse_context *context)
6381 : : {
6382 : : ListCell *l;
6383 : 9 : StringInfo buf = context->buf;
6384 : 9 : bool omit_child_parens = true;
6385 : 9 : char *sep = "";
6386 : :
6387 [ - + + - : 9 : switch (gset->kind)
- - ]
6388 : : {
3256 andres@anarazel.de 6389 :UBC 0 : case GROUPING_SET_EMPTY:
6390 : 0 : appendStringInfoString(buf, "()");
6391 : 0 : return;
6392 : :
3256 andres@anarazel.de 6393 :CBC 6 : case GROUPING_SET_SIMPLE:
6394 : : {
6395 [ + - + - ]: 6 : if (!omit_parens || list_length(gset->content) != 1)
2434 peter_e@gmx.net 6396 : 6 : appendStringInfoChar(buf, '(');
6397 : :
3256 andres@anarazel.de 6398 [ + - + + : 21 : foreach(l, gset->content)
+ + ]
6399 : : {
3249 bruce@momjian.us 6400 : 15 : Index ref = lfirst_int(l);
6401 : :
3256 andres@anarazel.de 6402 : 15 : appendStringInfoString(buf, sep);
6403 : 15 : get_rule_sortgroupclause(ref, targetlist,
6404 : : false, context);
6405 : 15 : sep = ", ";
6406 : : }
6407 : :
6408 [ + - + - ]: 6 : if (!omit_parens || list_length(gset->content) != 1)
2434 peter_e@gmx.net 6409 : 6 : appendStringInfoChar(buf, ')');
6410 : : }
3256 andres@anarazel.de 6411 : 6 : return;
6412 : :
6413 : 3 : case GROUPING_SET_ROLLUP:
6414 : 3 : appendStringInfoString(buf, "ROLLUP(");
6415 : 3 : break;
3256 andres@anarazel.de 6416 :UBC 0 : case GROUPING_SET_CUBE:
6417 : 0 : appendStringInfoString(buf, "CUBE(");
6418 : 0 : break;
6419 : 0 : case GROUPING_SET_SETS:
6420 : 0 : appendStringInfoString(buf, "GROUPING SETS (");
6421 : 0 : omit_child_parens = false;
6422 : 0 : break;
6423 : : }
6424 : :
3256 andres@anarazel.de 6425 [ + - + + :CBC 9 : foreach(l, gset->content)
+ + ]
6426 : : {
6427 : 6 : appendStringInfoString(buf, sep);
6428 : 6 : get_rule_groupingset(lfirst(l), targetlist, omit_child_parens, context);
6429 : 6 : sep = ", ";
6430 : : }
6431 : :
2434 peter_e@gmx.net 6432 : 3 : appendStringInfoChar(buf, ')');
6433 : : }
6434 : :
6435 : : /*
6436 : : * Display an ORDER BY list.
6437 : : */
6438 : : static void
5586 tgl@sss.pgh.pa.us 6439 : 137 : get_rule_orderby(List *orderList, List *targetList,
6440 : : bool force_colno, deparse_context *context)
6441 : : {
6442 : 137 : StringInfo buf = context->buf;
6443 : : const char *sep;
6444 : : ListCell *l;
6445 : :
6446 : 137 : sep = "";
6447 [ + - + + : 290 : foreach(l, orderList)
+ + ]
6448 : : {
6449 : 153 : SortGroupClause *srt = (SortGroupClause *) lfirst(l);
6450 : : Node *sortexpr;
6451 : : Oid sortcoltype;
6452 : : TypeCacheEntry *typentry;
6453 : :
6454 : 153 : appendStringInfoString(buf, sep);
3256 andres@anarazel.de 6455 : 153 : sortexpr = get_rule_sortgroupclause(srt->tleSortGroupRef, targetList,
6456 : : force_colno, context);
5586 tgl@sss.pgh.pa.us 6457 : 153 : sortcoltype = exprType(sortexpr);
6458 : : /* See whether operator is default < or > for datatype */
6459 : 153 : typentry = lookup_type_cache(sortcoltype,
6460 : : TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
6461 [ + + ]: 153 : if (srt->sortop == typentry->lt_opr)
6462 : : {
6463 : : /* ASC is default, so emit nothing for it */
6464 [ - + ]: 139 : if (srt->nulls_first)
3818 rhaas@postgresql.org 6465 :UBC 0 : appendStringInfoString(buf, " NULLS FIRST");
6466 : : }
5586 tgl@sss.pgh.pa.us 6467 [ + + ]:CBC 14 : else if (srt->sortop == typentry->gt_opr)
6468 : : {
3818 rhaas@postgresql.org 6469 : 5 : appendStringInfoString(buf, " DESC");
6470 : : /* DESC defaults to NULLS FIRST */
5586 tgl@sss.pgh.pa.us 6471 [ + + ]: 5 : if (!srt->nulls_first)
3818 rhaas@postgresql.org 6472 : 1 : appendStringInfoString(buf, " NULLS LAST");
6473 : : }
6474 : : else
6475 : : {
5586 tgl@sss.pgh.pa.us 6476 : 9 : appendStringInfo(buf, " USING %s",
6477 : : generate_operator_name(srt->sortop,
6478 : : sortcoltype,
6479 : : sortcoltype));
6480 : : /* be specific to eliminate ambiguity */
6481 [ - + ]: 9 : if (srt->nulls_first)
3818 rhaas@postgresql.org 6482 :UBC 0 : appendStringInfoString(buf, " NULLS FIRST");
6483 : : else
3818 rhaas@postgresql.org 6484 :CBC 9 : appendStringInfoString(buf, " NULLS LAST");
6485 : : }
5586 tgl@sss.pgh.pa.us 6486 : 153 : sep = ", ";
6487 : : }
6488 : 137 : }
6489 : :
6490 : : /*
6491 : : * Display a WINDOW clause.
6492 : : *
6493 : : * Note that the windowClause list might contain only anonymous window
6494 : : * specifications, in which case we should print nothing here.
6495 : : */
6496 : : static void
6497 : 21 : get_rule_windowclause(Query *query, deparse_context *context)
6498 : : {
6499 : 21 : StringInfo buf = context->buf;
6500 : : const char *sep;
6501 : : ListCell *l;
6502 : :
6503 : 21 : sep = NULL;
6504 [ + - + + : 42 : foreach(l, query->windowClause)
+ + ]
6505 : : {
6506 : 21 : WindowClause *wc = (WindowClause *) lfirst(l);
6507 : :
6508 [ + - ]: 21 : if (wc->name == NULL)
6509 : 21 : continue; /* ignore anonymous windows */
6510 : :
5586 tgl@sss.pgh.pa.us 6511 [ # # ]:UBC 0 : if (sep == NULL)
6512 : 0 : appendContextKeyword(context, " WINDOW ",
6513 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
6514 : : else
6515 : 0 : appendStringInfoString(buf, sep);
6516 : :
6517 : 0 : appendStringInfo(buf, "%s AS ", quote_identifier(wc->name));
6518 : :
6519 : 0 : get_rule_windowspec(wc, query->targetList, context);
6520 : :
6521 : 0 : sep = ", ";
6522 : : }
5586 tgl@sss.pgh.pa.us 6523 :CBC 21 : }
6524 : :
6525 : : /*
6526 : : * Display a window definition
6527 : : */
6528 : : static void
6529 : 21 : get_rule_windowspec(WindowClause *wc, List *targetList,
6530 : : deparse_context *context)
6531 : : {
6532 : 21 : StringInfo buf = context->buf;
6533 : 21 : bool needspace = false;
6534 : : const char *sep;
6535 : : ListCell *l;
6536 : :
6537 : 21 : appendStringInfoChar(buf, '(');
6538 [ - + ]: 21 : if (wc->refname)
6539 : : {
5586 tgl@sss.pgh.pa.us 6540 :UBC 0 : appendStringInfoString(buf, quote_identifier(wc->refname));
6541 : 0 : needspace = true;
6542 : : }
6543 : : /* partition clauses are always inherited, so only print if no refname */
5586 tgl@sss.pgh.pa.us 6544 [ - + - - ]:CBC 21 : if (wc->partitionClause && !wc->refname)
6545 : : {
5586 tgl@sss.pgh.pa.us 6546 [ # # ]:UBC 0 : if (needspace)
6547 : 0 : appendStringInfoChar(buf, ' ');
6548 : 0 : appendStringInfoString(buf, "PARTITION BY ");
6549 : 0 : sep = "";
6550 [ # # # # : 0 : foreach(l, wc->partitionClause)
# # ]
6551 : : {
6552 : 0 : SortGroupClause *grp = (SortGroupClause *) lfirst(l);
6553 : :
6554 : 0 : appendStringInfoString(buf, sep);
3256 andres@anarazel.de 6555 : 0 : get_rule_sortgroupclause(grp->tleSortGroupRef, targetList,
6556 : : false, context);
5586 tgl@sss.pgh.pa.us 6557 : 0 : sep = ", ";
6558 : : }
6559 : 0 : needspace = true;
6560 : : }
6561 : : /* print ordering clause only if not inherited */
5586 tgl@sss.pgh.pa.us 6562 [ + - + - ]:CBC 21 : if (wc->orderClause && !wc->copiedOrder)
6563 : : {
6564 [ - + ]: 21 : if (needspace)
5586 tgl@sss.pgh.pa.us 6565 :UBC 0 : appendStringInfoChar(buf, ' ');
5586 tgl@sss.pgh.pa.us 6566 :CBC 21 : appendStringInfoString(buf, "ORDER BY ");
6567 : 21 : get_rule_orderby(wc->orderClause, targetList, false, context);
6568 : 21 : needspace = true;
6569 : : }
6570 : : /* framing clause is never inherited, so print unless it's default */
5583 6571 [ + - ]: 21 : if (wc->frameOptions & FRAMEOPTION_NONDEFAULT)
6572 : : {
6573 [ + - ]: 21 : if (needspace)
6574 : 21 : appendStringInfoChar(buf, ' ');
6575 [ + + ]: 21 : if (wc->frameOptions & FRAMEOPTION_RANGE)
6576 : 3 : appendStringInfoString(buf, "RANGE ");
6577 [ + + ]: 18 : else if (wc->frameOptions & FRAMEOPTION_ROWS)
6578 : 15 : appendStringInfoString(buf, "ROWS ");
2258 6579 [ + - ]: 3 : else if (wc->frameOptions & FRAMEOPTION_GROUPS)
6580 : 3 : appendStringInfoString(buf, "GROUPS ");
6581 : : else
5583 tgl@sss.pgh.pa.us 6582 :UBC 0 : Assert(false);
5583 tgl@sss.pgh.pa.us 6583 [ + - ]:CBC 21 : if (wc->frameOptions & FRAMEOPTION_BETWEEN)
6584 : 21 : appendStringInfoString(buf, "BETWEEN ");
6585 [ - + ]: 21 : if (wc->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
5583 tgl@sss.pgh.pa.us 6586 :UBC 0 : appendStringInfoString(buf, "UNBOUNDED PRECEDING ");
5583 tgl@sss.pgh.pa.us 6587 [ - + ]:CBC 21 : else if (wc->frameOptions & FRAMEOPTION_START_CURRENT_ROW)
5583 tgl@sss.pgh.pa.us 6588 :UBC 0 : appendStringInfoString(buf, "CURRENT ROW ");
2258 tgl@sss.pgh.pa.us 6589 [ + - ]:CBC 21 : else if (wc->frameOptions & FRAMEOPTION_START_OFFSET)
6590 : : {
5175 6591 : 21 : get_rule_expr(wc->startOffset, context, false);
2258 6592 [ + - ]: 21 : if (wc->frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
5175 6593 : 21 : appendStringInfoString(buf, " PRECEDING ");
2258 tgl@sss.pgh.pa.us 6594 [ # # ]:UBC 0 : else if (wc->frameOptions & FRAMEOPTION_START_OFFSET_FOLLOWING)
5175 6595 : 0 : appendStringInfoString(buf, " FOLLOWING ");
6596 : : else
6597 : 0 : Assert(false);
6598 : : }
6599 : : else
5583 6600 : 0 : Assert(false);
5583 tgl@sss.pgh.pa.us 6601 [ + - ]:CBC 21 : if (wc->frameOptions & FRAMEOPTION_BETWEEN)
6602 : : {
6603 : 21 : appendStringInfoString(buf, "AND ");
6604 [ - + ]: 21 : if (wc->frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
5583 tgl@sss.pgh.pa.us 6605 :UBC 0 : appendStringInfoString(buf, "UNBOUNDED FOLLOWING ");
5583 tgl@sss.pgh.pa.us 6606 [ - + ]:CBC 21 : else if (wc->frameOptions & FRAMEOPTION_END_CURRENT_ROW)
5583 tgl@sss.pgh.pa.us 6607 :UBC 0 : appendStringInfoString(buf, "CURRENT ROW ");
2258 tgl@sss.pgh.pa.us 6608 [ + - ]:CBC 21 : else if (wc->frameOptions & FRAMEOPTION_END_OFFSET)
6609 : : {
5175 6610 : 21 : get_rule_expr(wc->endOffset, context, false);
2258 6611 [ - + ]: 21 : if (wc->frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
5175 tgl@sss.pgh.pa.us 6612 :UBC 0 : appendStringInfoString(buf, " PRECEDING ");
2258 tgl@sss.pgh.pa.us 6613 [ + - ]:CBC 21 : else if (wc->frameOptions & FRAMEOPTION_END_OFFSET_FOLLOWING)
5175 6614 : 21 : appendStringInfoString(buf, " FOLLOWING ");
6615 : : else
5175 tgl@sss.pgh.pa.us 6616 :UBC 0 : Assert(false);
6617 : : }
6618 : : else
5583 6619 : 0 : Assert(false);
6620 : : }
2258 tgl@sss.pgh.pa.us 6621 [ + + ]:CBC 21 : if (wc->frameOptions & FRAMEOPTION_EXCLUDE_CURRENT_ROW)
6622 : 3 : appendStringInfoString(buf, "EXCLUDE CURRENT ROW ");
6623 [ + + ]: 18 : else if (wc->frameOptions & FRAMEOPTION_EXCLUDE_GROUP)
6624 : 3 : appendStringInfoString(buf, "EXCLUDE GROUP ");
6625 [ + + ]: 15 : else if (wc->frameOptions & FRAMEOPTION_EXCLUDE_TIES)
6626 : 3 : appendStringInfoString(buf, "EXCLUDE TIES ");
6627 : : /* we will now have a trailing space; remove it */
5583 6628 : 21 : buf->len--;
6629 : : }
5586 6630 : 21 : appendStringInfoChar(buf, ')');
6631 : 21 : }
6632 : :
6633 : : /* ----------
6634 : : * get_insert_query_def - Parse back an INSERT parsetree
6635 : : * ----------
6636 : : */
6637 : : static void
694 6638 : 170 : get_insert_query_def(Query *query, deparse_context *context,
6639 : : bool colNamesVisible)
6640 : : {
8960 6641 : 170 : StringInfo buf = context->buf;
8592 6642 : 170 : RangeTblEntry *select_rte = NULL;
6465 mail@joeconway.com 6643 : 170 : RangeTblEntry *values_rte = NULL;
6644 : : RangeTblEntry *rte;
6645 : : char *sep;
6646 : : ListCell *l;
6647 : : List *strippedexprs;
6648 : :
6649 : : /* Insert the WITH clause if given */
4930 tgl@sss.pgh.pa.us 6650 : 170 : get_with_clause(query, context);
6651 : :
6652 : : /*
6653 : : * If it's an INSERT ... SELECT or multi-row VALUES, there will be a
6654 : : * single RTE for the SELECT or VALUES. Plain VALUES has neither.
6655 : : */
9357 bruce@momjian.us 6656 [ + - + + : 661 : foreach(l, query->rtable)
+ + ]
6657 : : {
6658 : 491 : rte = (RangeTblEntry *) lfirst(l);
6659 : :
6465 mail@joeconway.com 6660 [ + + ]: 491 : if (rte->rtekind == RTE_SUBQUERY)
6661 : : {
6662 [ - + ]: 25 : if (select_rte)
6465 mail@joeconway.com 6663 [ # # ]:UBC 0 : elog(ERROR, "too many subquery RTEs in INSERT");
6465 mail@joeconway.com 6664 :CBC 25 : select_rte = rte;
6665 : : }
6666 : :
6667 [ + + ]: 491 : if (rte->rtekind == RTE_VALUES)
6668 : : {
6669 [ - + ]: 22 : if (values_rte)
6465 mail@joeconway.com 6670 [ # # ]:UBC 0 : elog(ERROR, "too many values RTEs in INSERT");
6465 mail@joeconway.com 6671 :CBC 22 : values_rte = rte;
6672 : : }
6673 : : }
6674 [ + + - + ]: 170 : if (select_rte && values_rte)
6465 mail@joeconway.com 6675 [ # # ]:UBC 0 : elog(ERROR, "both subquery and values RTEs in INSERT");
6676 : :
6677 : : /*
6678 : : * Start the query with INSERT INTO relname
6679 : : */
8932 tgl@sss.pgh.pa.us 6680 :CBC 170 : rte = rt_fetch(query->resultRelation, query->rtable);
8017 6681 [ - + ]: 170 : Assert(rte->rtekind == RTE_RELATION);
6682 : :
7564 6683 [ + - ]: 170 : if (PRETTY_INDENT(context))
6684 : : {
7559 bruce@momjian.us 6685 : 170 : context->indentLevel += PRETTYINDENT_STD;
6686 : 170 : appendStringInfoChar(buf, ' ');
6687 : : }
422 tgl@sss.pgh.pa.us 6688 : 170 : appendStringInfo(buf, "INSERT INTO %s",
6689 : : generate_relation_name(rte->relid, NIL));
6690 : :
6691 : : /* Print the relation alias, if needed; INSERT requires explicit AS */
6692 : 170 : get_rte_alias(rte, query->resultRelation, true, context);
6693 : :
6694 : : /* always want a space here */
6695 : 170 : appendStringInfoChar(buf, ' ');
6696 : :
6697 : : /*
6698 : : * Add the insert-column-names list. Any indirection decoration needed on
6699 : : * the column names can be inferred from the top targetlist.
6700 : : */
7249 6701 : 170 : strippedexprs = NIL;
6702 : 170 : sep = "";
4195 6703 [ + - ]: 170 : if (query->targetList)
6704 : 170 : appendStringInfoChar(buf, '(');
9357 bruce@momjian.us 6705 [ + - + + : 621 : foreach(l, query->targetList)
+ + ]
6706 : : {
8615 tgl@sss.pgh.pa.us 6707 : 451 : TargetEntry *tle = (TargetEntry *) lfirst(l);
6708 : :
6948 6709 [ - + ]: 451 : if (tle->resjunk)
8598 tgl@sss.pgh.pa.us 6710 :UBC 0 : continue; /* ignore junk entries */
6711 : :
6924 neilc@samurai.com 6712 :CBC 451 : appendStringInfoString(buf, sep);
9357 bruce@momjian.us 6713 : 451 : sep = ", ";
6714 : :
6715 : : /*
6716 : : * Put out name of target column; look in the catalogs, not at
6717 : : * tle->resname, since resname will fail to track RENAME.
6718 : : */
7379 neilc@samurai.com 6719 : 451 : appendStringInfoString(buf,
2253 alvherre@alvh.no-ip. 6720 : 451 : quote_identifier(get_attname(rte->relid,
6721 : 451 : tle->resno,
6722 : : false)));
6723 : :
6724 : : /*
6725 : : * Print any indirection needed (subfields or subscripts), and strip
6726 : : * off the top-level nodes representing the indirection assignments.
6727 : : * Add the stripped expressions to strippedexprs. (If it's a
6728 : : * single-VALUES statement, the stripped expressions are the VALUES to
6729 : : * print below. Otherwise they're just Vars and not really
6730 : : * interesting.)
6731 : : */
2811 tgl@sss.pgh.pa.us 6732 : 451 : strippedexprs = lappend(strippedexprs,
6733 : 451 : processIndirection((Node *) tle->expr,
6734 : : context));
6735 : : }
4195 6736 [ + - ]: 170 : if (query->targetList)
3818 rhaas@postgresql.org 6737 : 170 : appendStringInfoString(buf, ") ");
6738 : :
2565 peter_e@gmx.net 6739 [ - + ]: 170 : if (query->override)
6740 : : {
2565 peter_e@gmx.net 6741 [ # # ]:UBC 0 : if (query->override == OVERRIDING_SYSTEM_VALUE)
6742 : 0 : appendStringInfoString(buf, "OVERRIDING SYSTEM VALUE ");
6743 [ # # ]: 0 : else if (query->override == OVERRIDING_USER_VALUE)
6744 : 0 : appendStringInfoString(buf, "OVERRIDING USER VALUE ");
6745 : : }
6746 : :
6465 mail@joeconway.com 6747 [ + + ]:CBC 170 : if (select_rte)
6748 : : {
6749 : : /* Add the SELECT */
879 tgl@sss.pgh.pa.us 6750 : 25 : get_query_def(select_rte->subquery, buf, context->namespaces, NULL,
6751 : : false,
6752 : : context->prettyFlags, context->wrapColumn,
6753 : : context->indentLevel);
6754 : : }
6465 mail@joeconway.com 6755 [ + + ]: 145 : else if (values_rte)
6756 : : {
6757 : : /* Add the multi-VALUES expression lists */
6758 : 22 : get_values_def(values_rte->values_lists, context);
6759 : : }
4195 tgl@sss.pgh.pa.us 6760 [ + - ]: 123 : else if (strippedexprs)
6761 : : {
6762 : : /* Add the single-VALUES expression list */
7564 6763 : 123 : appendContextKeyword(context, "VALUES (",
6764 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
822 6765 : 123 : get_rule_list_toplevel(strippedexprs, context, false);
8820 6766 : 123 : appendStringInfoChar(buf, ')');
6767 : : }
6768 : : else
6769 : : {
6770 : : /* No expressions, so it must be DEFAULT VALUES */
3818 rhaas@postgresql.org 6771 :UBC 0 : appendStringInfoString(buf, "DEFAULT VALUES");
6772 : : }
6773 : :
6774 : : /* Add ON CONFLICT if present */
3264 andres@anarazel.de 6775 [ + + ]:CBC 170 : if (query->onConflict)
6776 : : {
6777 : 15 : OnConflictExpr *confl = query->onConflict;
6778 : :
3209 heikki.linnakangas@i 6779 : 15 : appendStringInfoString(buf, " ON CONFLICT");
6780 : :
3253 andres@anarazel.de 6781 [ + + ]: 15 : if (confl->arbiterElems)
6782 : : {
6783 : : /* Add the single-VALUES expression list */
6784 : 12 : appendStringInfoChar(buf, '(');
6785 : 12 : get_rule_expr((Node *) confl->arbiterElems, context, false);
6786 : 12 : appendStringInfoChar(buf, ')');
6787 : :
6788 : : /* Add a WHERE clause (for partial indexes) if given */
6789 [ + + ]: 12 : if (confl->arbiterWhere != NULL)
6790 : : {
6791 : : bool save_varprefix;
6792 : :
6793 : : /*
6794 : : * Force non-prefixing of Vars, since parser assumes that they
6795 : : * belong to target relation. WHERE clause does not use
6796 : : * InferenceElem, so this is separately required.
6797 : : */
2989 tgl@sss.pgh.pa.us 6798 : 6 : save_varprefix = context->varprefix;
6799 : 6 : context->varprefix = false;
6800 : :
3253 andres@anarazel.de 6801 : 6 : appendContextKeyword(context, " WHERE ",
6802 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
6803 : 6 : get_rule_expr(confl->arbiterWhere, context, false);
6804 : :
2989 tgl@sss.pgh.pa.us 6805 : 6 : context->varprefix = save_varprefix;
6806 : : }
6807 : : }
2895 6808 [ - + ]: 3 : else if (OidIsValid(confl->constraint))
6809 : : {
3249 bruce@momjian.us 6810 :UBC 0 : char *constraint = get_constraint_name(confl->constraint);
6811 : :
2895 tgl@sss.pgh.pa.us 6812 [ # # ]: 0 : if (!constraint)
6813 [ # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u",
6814 : : confl->constraint);
3253 andres@anarazel.de 6815 : 0 : appendStringInfo(buf, " ON CONSTRAINT %s",
6816 : : quote_identifier(constraint));
6817 : : }
6818 : :
3264 andres@anarazel.de 6819 [ + + ]:CBC 15 : if (confl->action == ONCONFLICT_NOTHING)
6820 : : {
3253 6821 : 9 : appendStringInfoString(buf, " DO NOTHING");
6822 : : }
6823 : : else
6824 : : {
6825 : 6 : appendStringInfoString(buf, " DO UPDATE SET ");
6826 : : /* Deparse targetlist */
3264 6827 : 6 : get_update_query_targetlist_def(query, confl->onConflictSet,
6828 : : context, rte);
6829 : :
6830 : : /* Add a WHERE clause if given */
6831 [ + - ]: 6 : if (confl->onConflictWhere != NULL)
6832 : : {
6833 : 6 : appendContextKeyword(context, " WHERE ",
6834 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
6835 : 6 : get_rule_expr(confl->onConflictWhere, context, false);
6836 : : }
6837 : : }
6838 : : }
6839 : :
6840 : : /* Add RETURNING if present */
6455 tgl@sss.pgh.pa.us 6841 [ + + ]: 170 : if (query->returningList)
6842 : : {
6843 : 39 : appendContextKeyword(context, " RETURNING",
6844 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
694 6845 : 39 : get_target_list(query->returningList, context, NULL, colNamesVisible);
6846 : : }
9365 bruce@momjian.us 6847 : 170 : }
6848 : :
6849 : :
6850 : : /* ----------
6851 : : * get_update_query_def - Parse back an UPDATE parsetree
6852 : : * ----------
6853 : : */
6854 : : static void
694 tgl@sss.pgh.pa.us 6855 : 65 : get_update_query_def(Query *query, deparse_context *context,
6856 : : bool colNamesVisible)
6857 : : {
7168 bruce@momjian.us 6858 : 65 : StringInfo buf = context->buf;
6859 : : RangeTblEntry *rte;
6860 : :
6861 : : /* Insert the WITH clause if given */
4930 tgl@sss.pgh.pa.us 6862 : 65 : get_with_clause(query, context);
6863 : :
6864 : : /*
6865 : : * Start the query with UPDATE relname SET
6866 : : */
8932 6867 : 65 : rte = rt_fetch(query->resultRelation, query->rtable);
8017 6868 [ - + ]: 65 : Assert(rte->rtekind == RTE_RELATION);
7564 6869 [ + - ]: 65 : if (PRETTY_INDENT(context))
6870 : : {
7559 bruce@momjian.us 6871 : 65 : appendStringInfoChar(buf, ' ');
6872 : 65 : context->indentLevel += PRETTYINDENT_STD;
6873 : : }
5825 tgl@sss.pgh.pa.us 6874 :UBC 0 : appendStringInfo(buf, "UPDATE %s%s",
8709 tgl@sss.pgh.pa.us 6875 [ + - ]:CBC 65 : only_marker(rte),
6876 : : generate_relation_name(rte->relid, NIL));
6877 : :
6878 : : /* Print the relation alias, if needed */
422 6879 : 65 : get_rte_alias(rte, query->resultRelation, false, context);
6880 : :
5825 6881 : 65 : appendStringInfoString(buf, " SET ");
6882 : :
6883 : : /* Deparse targetlist */
3264 andres@anarazel.de 6884 : 65 : get_update_query_targetlist_def(query, query->targetList, context, rte);
6885 : :
6886 : : /* Add the FROM clause if needed */
6887 : 65 : get_from_clause(query, " FROM ", context);
6888 : :
6889 : : /* Add a WHERE clause if given */
6890 [ + + ]: 65 : if (query->jointree->quals != NULL)
6891 : : {
6892 : 57 : appendContextKeyword(context, " WHERE ",
6893 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
6894 : 57 : get_rule_expr(query->jointree->quals, context, false);
6895 : : }
6896 : :
6897 : : /* Add RETURNING if present */
6898 [ + + ]: 65 : if (query->returningList)
6899 : : {
6900 : 11 : appendContextKeyword(context, " RETURNING",
6901 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
694 tgl@sss.pgh.pa.us 6902 : 11 : get_target_list(query->returningList, context, NULL, colNamesVisible);
6903 : : }
3264 andres@anarazel.de 6904 : 65 : }
6905 : :
6906 : :
6907 : : /* ----------
6908 : : * get_update_query_targetlist_def - Parse back an UPDATE targetlist
6909 : : * ----------
6910 : : */
6911 : : static void
6912 : 83 : get_update_query_targetlist_def(Query *query, List *targetList,
6913 : : deparse_context *context, RangeTblEntry *rte)
6914 : : {
6915 : 83 : StringInfo buf = context->buf;
6916 : : ListCell *l;
6917 : : ListCell *next_ma_cell;
6918 : : int remaining_ma_columns;
6919 : : const char *sep;
6920 : : SubLink *cur_ma_sublink;
6921 : : List *ma_sublinks;
6922 : :
6923 : : /*
6924 : : * Prepare to deal with MULTIEXPR assignments: collect the source SubLinks
6925 : : * into a list. We expect them to appear, in ID order, in resjunk tlist
6926 : : * entries.
6927 : : */
3588 tgl@sss.pgh.pa.us 6928 : 83 : ma_sublinks = NIL;
6929 [ + + ]: 83 : if (query->hasSubLinks) /* else there can't be any */
6930 : : {
3264 andres@anarazel.de 6931 [ + - + + : 15 : foreach(l, targetList)
+ + ]
6932 : : {
3588 tgl@sss.pgh.pa.us 6933 : 12 : TargetEntry *tle = (TargetEntry *) lfirst(l);
6934 : :
6935 [ + + + - ]: 12 : if (tle->resjunk && IsA(tle->expr, SubLink))
6936 : : {
6937 : 3 : SubLink *sl = (SubLink *) tle->expr;
6938 : :
6939 [ + - ]: 3 : if (sl->subLinkType == MULTIEXPR_SUBLINK)
6940 : : {
6941 : 3 : ma_sublinks = lappend(ma_sublinks, sl);
6942 [ - + ]: 3 : Assert(sl->subLinkId == list_length(ma_sublinks));
6943 : : }
6944 : : }
6945 : : }
6946 : : }
6947 : 83 : next_ma_cell = list_head(ma_sublinks);
6948 : 83 : cur_ma_sublink = NULL;
6949 : 83 : remaining_ma_columns = 0;
6950 : :
6951 : : /* Add the comma separated list of 'attname = value' */
9357 bruce@momjian.us 6952 : 83 : sep = "";
3264 andres@anarazel.de 6953 [ + - + + : 220 : foreach(l, targetList)
+ + ]
6954 : : {
8598 tgl@sss.pgh.pa.us 6955 : 137 : TargetEntry *tle = (TargetEntry *) lfirst(l);
6956 : : Node *expr;
6957 : :
6948 6958 [ + + ]: 137 : if (tle->resjunk)
8598 6959 : 3 : continue; /* ignore junk entries */
6960 : :
6961 : : /* Emit separator (OK whether we're in multiassignment or not) */
6924 neilc@samurai.com 6962 : 134 : appendStringInfoString(buf, sep);
9357 bruce@momjian.us 6963 : 134 : sep = ", ";
6964 : :
6965 : : /*
6966 : : * Check to see if we're starting a multiassignment group: if so,
6967 : : * output a left paren.
6968 : : */
3588 tgl@sss.pgh.pa.us 6969 [ + + + - ]: 134 : if (next_ma_cell != NULL && cur_ma_sublink == NULL)
6970 : : {
6971 : : /*
6972 : : * We must dig down into the expr to see if it's a PARAM_MULTIEXPR
6973 : : * Param. That could be buried under FieldStores and
6974 : : * SubscriptingRefs and CoerceToDomains (cf processIndirection()),
6975 : : * and underneath those there could be an implicit type coercion.
6976 : : * Because we would ignore implicit type coercions anyway, we
6977 : : * don't need to be as careful as processIndirection() is about
6978 : : * descending past implicit CoerceToDomains.
6979 : : */
6980 : 3 : expr = (Node *) tle->expr;
6981 [ + - ]: 6 : while (expr)
6982 : : {
6983 [ - + ]: 6 : if (IsA(expr, FieldStore))
6984 : : {
3588 tgl@sss.pgh.pa.us 6985 :UBC 0 : FieldStore *fstore = (FieldStore *) expr;
6986 : :
6987 : 0 : expr = (Node *) linitial(fstore->newvals);
6988 : : }
1899 alvherre@alvh.no-ip. 6989 [ + + ]:CBC 6 : else if (IsA(expr, SubscriptingRef))
6990 : : {
6991 : 3 : SubscriptingRef *sbsref = (SubscriptingRef *) expr;
6992 : :
6993 [ - + ]: 3 : if (sbsref->refassgnexpr == NULL)
3588 tgl@sss.pgh.pa.us 6994 :UBC 0 : break;
6995 : :
1899 alvherre@alvh.no-ip. 6996 :CBC 3 : expr = (Node *) sbsref->refassgnexpr;
6997 : : }
2468 tgl@sss.pgh.pa.us 6998 [ - + ]: 3 : else if (IsA(expr, CoerceToDomain))
6999 : : {
2468 tgl@sss.pgh.pa.us 7000 :UBC 0 : CoerceToDomain *cdomain = (CoerceToDomain *) expr;
7001 : :
7002 [ # # ]: 0 : if (cdomain->coercionformat != COERCE_IMPLICIT_CAST)
7003 : 0 : break;
7004 : 0 : expr = (Node *) cdomain->arg;
7005 : : }
7006 : : else
3588 tgl@sss.pgh.pa.us 7007 :CBC 3 : break;
7008 : : }
7009 : 3 : expr = strip_implicit_coercions(expr);
7010 : :
7011 [ + - + - ]: 3 : if (expr && IsA(expr, Param) &&
7012 [ + - ]: 3 : ((Param *) expr)->paramkind == PARAM_MULTIEXPR)
7013 : : {
7014 : 3 : cur_ma_sublink = (SubLink *) lfirst(next_ma_cell);
1735 7015 : 3 : next_ma_cell = lnext(ma_sublinks, next_ma_cell);
1536 alvherre@alvh.no-ip. 7016 : 3 : remaining_ma_columns = count_nonjunk_tlist_entries(((Query *) cur_ma_sublink->subselect)->targetList);
3588 tgl@sss.pgh.pa.us 7017 [ - + ]: 3 : Assert(((Param *) expr)->paramid ==
7018 : : ((cur_ma_sublink->subLinkId << 16) | 1));
7019 : 3 : appendStringInfoChar(buf, '(');
7020 : : }
7021 : : }
7022 : :
7023 : : /*
7024 : : * Put out name of target column; look in the catalogs, not at
7025 : : * tle->resname, since resname will fail to track RENAME.
7026 : : */
7249 7027 : 134 : appendStringInfoString(buf,
2253 alvherre@alvh.no-ip. 7028 : 134 : quote_identifier(get_attname(rte->relid,
7029 : 134 : tle->resno,
7030 : : false)));
7031 : :
7032 : : /*
7033 : : * Print any indirection needed (subfields or subscripts), and strip
7034 : : * off the top-level nodes representing the indirection assignments.
7035 : : */
2811 tgl@sss.pgh.pa.us 7036 : 134 : expr = processIndirection((Node *) tle->expr, context);
7037 : :
7038 : : /*
7039 : : * If we're in a multiassignment, skip printing anything more, unless
7040 : : * this is the last column; in which case, what we print should be the
7041 : : * sublink, not the Param.
7042 : : */
3588 7043 [ + + ]: 134 : if (cur_ma_sublink != NULL)
7044 : : {
7045 [ + + ]: 9 : if (--remaining_ma_columns > 0)
7046 : 6 : continue; /* not the last column of multiassignment */
7047 : 3 : appendStringInfoChar(buf, ')');
7048 : 3 : expr = (Node *) cur_ma_sublink;
7049 : 3 : cur_ma_sublink = NULL;
7050 : : }
7051 : :
3818 rhaas@postgresql.org 7052 : 128 : appendStringInfoString(buf, " = ");
7053 : :
7249 tgl@sss.pgh.pa.us 7054 : 128 : get_rule_expr(expr, context, false);
7055 : : }
9365 bruce@momjian.us 7056 : 83 : }
7057 : :
7058 : :
7059 : : /* ----------
7060 : : * get_delete_query_def - Parse back a DELETE parsetree
7061 : : * ----------
7062 : : */
7063 : : static void
694 tgl@sss.pgh.pa.us 7064 : 38 : get_delete_query_def(Query *query, deparse_context *context,
7065 : : bool colNamesVisible)
7066 : : {
8960 7067 : 38 : StringInfo buf = context->buf;
7068 : : RangeTblEntry *rte;
7069 : :
7070 : : /* Insert the WITH clause if given */
4930 7071 : 38 : get_with_clause(query, context);
7072 : :
7073 : : /*
7074 : : * Start the query with DELETE FROM relname
7075 : : */
8932 7076 : 38 : rte = rt_fetch(query->resultRelation, query->rtable);
8017 7077 [ - + ]: 38 : Assert(rte->rtekind == RTE_RELATION);
7564 7078 [ + - ]: 38 : if (PRETTY_INDENT(context))
7079 : : {
7559 bruce@momjian.us 7080 : 38 : appendStringInfoChar(buf, ' ');
5825 tgl@sss.pgh.pa.us 7081 : 38 : context->indentLevel += PRETTYINDENT_STD;
7082 : : }
8932 tgl@sss.pgh.pa.us 7083 :UBC 0 : appendStringInfo(buf, "DELETE FROM %s%s",
8709 tgl@sss.pgh.pa.us 7084 [ + - ]:CBC 38 : only_marker(rte),
7085 : : generate_relation_name(rte->relid, NIL));
7086 : :
7087 : : /* Print the relation alias, if needed */
422 7088 : 38 : get_rte_alias(rte, query->resultRelation, false, context);
7089 : :
7090 : : /* Add the USING clause if given */
6831 7091 : 38 : get_from_clause(query, " USING ", context);
7092 : :
7093 : : /* Add a WHERE clause if given */
8598 7094 [ + - ]: 38 : if (query->jointree->quals != NULL)
7095 : : {
7564 7096 : 38 : appendContextKeyword(context, " WHERE ",
7097 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
7878 7098 : 38 : get_rule_expr(query->jointree->quals, context, false);
7099 : : }
7100 : :
7101 : : /* Add RETURNING if present */
6455 7102 [ + + ]: 38 : if (query->returningList)
7103 : : {
7104 : 8 : appendContextKeyword(context, " RETURNING",
7105 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
694 7106 : 8 : get_target_list(query->returningList, context, NULL, colNamesVisible);
7107 : : }
9365 bruce@momjian.us 7108 : 38 : }
7109 : :
7110 : :
7111 : : /* ----------
7112 : : * get_merge_query_def - Parse back a MERGE parsetree
7113 : : * ----------
7114 : : */
7115 : : static void
343 tgl@sss.pgh.pa.us 7116 : 6 : get_merge_query_def(Query *query, deparse_context *context,
7117 : : bool colNamesVisible)
7118 : : {
7119 : 6 : StringInfo buf = context->buf;
7120 : : RangeTblEntry *rte;
7121 : : ListCell *lc;
7122 : : bool haveNotMatchedBySource;
7123 : :
7124 : : /* Insert the WITH clause if given */
7125 : 6 : get_with_clause(query, context);
7126 : :
7127 : : /*
7128 : : * Start the query with MERGE INTO relname
7129 : : */
7130 : 6 : rte = rt_fetch(query->resultRelation, query->rtable);
7131 [ - + ]: 6 : Assert(rte->rtekind == RTE_RELATION);
7132 [ + - ]: 6 : if (PRETTY_INDENT(context))
7133 : : {
7134 : 6 : appendStringInfoChar(buf, ' ');
7135 : 6 : context->indentLevel += PRETTYINDENT_STD;
7136 : : }
343 tgl@sss.pgh.pa.us 7137 :UBC 0 : appendStringInfo(buf, "MERGE INTO %s%s",
343 tgl@sss.pgh.pa.us 7138 [ + - ]:CBC 6 : only_marker(rte),
7139 : : generate_relation_name(rte->relid, NIL));
7140 : :
7141 : : /* Print the relation alias, if needed */
7142 : 6 : get_rte_alias(rte, query->resultRelation, false, context);
7143 : :
7144 : : /* Print the source relation and join clause */
7145 : 6 : get_from_clause(query, " USING ", context);
7146 : 6 : appendContextKeyword(context, " ON ",
7147 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
15 dean.a.rasheed@gmail 7148 :GNC 6 : get_rule_expr(query->mergeJoinCondition, context, false);
7149 : :
7150 : : /*
7151 : : * Test for any NOT MATCHED BY SOURCE actions. If there are none, then
7152 : : * any NOT MATCHED BY TARGET actions are output as "WHEN NOT MATCHED", per
7153 : : * SQL standard. Otherwise, we have a non-SQL-standard query, so output
7154 : : * "BY SOURCE" / "BY TARGET" qualifiers for all NOT MATCHED actions, to be
7155 : : * more explicit.
7156 : : */
7157 : 6 : haveNotMatchedBySource = false;
7158 [ + - + + : 42 : foreach(lc, query->mergeActionList)
+ + ]
7159 : : {
7160 : 39 : MergeAction *action = lfirst_node(MergeAction, lc);
7161 : :
7162 [ + + ]: 39 : if (action->matchKind == MERGE_WHEN_NOT_MATCHED_BY_SOURCE)
7163 : : {
7164 : 3 : haveNotMatchedBySource = true;
7165 : 3 : break;
7166 : : }
7167 : : }
7168 : :
7169 : : /* Print each merge action */
343 tgl@sss.pgh.pa.us 7170 [ + - + + :CBC 45 : foreach(lc, query->mergeActionList)
+ + ]
7171 : : {
7172 : 39 : MergeAction *action = lfirst_node(MergeAction, lc);
7173 : :
7174 : 39 : appendContextKeyword(context, " WHEN ",
7175 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
15 dean.a.rasheed@gmail 7176 [ + + + - ]:GNC 39 : switch (action->matchKind)
7177 : : {
7178 : 18 : case MERGE_WHEN_MATCHED:
4 drowley@postgresql.o 7179 : 18 : appendStringInfoString(buf, "MATCHED");
15 dean.a.rasheed@gmail 7180 : 18 : break;
7181 : 3 : case MERGE_WHEN_NOT_MATCHED_BY_SOURCE:
4 drowley@postgresql.o 7182 : 3 : appendStringInfoString(buf, "NOT MATCHED BY SOURCE");
15 dean.a.rasheed@gmail 7183 : 3 : break;
7184 : 18 : case MERGE_WHEN_NOT_MATCHED_BY_TARGET:
7185 [ + + ]: 18 : if (haveNotMatchedBySource)
4 drowley@postgresql.o 7186 : 3 : appendStringInfoString(buf, "NOT MATCHED BY TARGET");
7187 : : else
7188 : 15 : appendStringInfoString(buf, "NOT MATCHED");
15 dean.a.rasheed@gmail 7189 : 18 : break;
15 dean.a.rasheed@gmail 7190 :UNC 0 : default:
7191 [ # # ]: 0 : elog(ERROR, "unrecognized matchKind: %d",
7192 : : (int) action->matchKind);
7193 : : }
7194 : :
343 tgl@sss.pgh.pa.us 7195 [ + + ]:CBC 39 : if (action->qual)
7196 : : {
7197 : 24 : appendContextKeyword(context, " AND ",
7198 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 3);
7199 : 24 : get_rule_expr(action->qual, context, false);
7200 : : }
7201 : 39 : appendContextKeyword(context, " THEN ",
7202 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 3);
7203 : :
7204 [ + + ]: 39 : if (action->commandType == CMD_INSERT)
7205 : : {
7206 : : /* This generally matches get_insert_query_def() */
7207 : 18 : List *strippedexprs = NIL;
7208 : 18 : const char *sep = "";
7209 : : ListCell *lc2;
7210 : :
7211 : 18 : appendStringInfoString(buf, "INSERT");
7212 : :
7213 [ + + ]: 18 : if (action->targetList)
7214 : 15 : appendStringInfoString(buf, " (");
7215 [ + + + + : 51 : foreach(lc2, action->targetList)
+ + ]
7216 : : {
7217 : 33 : TargetEntry *tle = (TargetEntry *) lfirst(lc2);
7218 : :
7219 [ - + ]: 33 : Assert(!tle->resjunk);
7220 : :
7221 : 33 : appendStringInfoString(buf, sep);
7222 : 33 : sep = ", ";
7223 : :
7224 : 33 : appendStringInfoString(buf,
7225 : 33 : quote_identifier(get_attname(rte->relid,
7226 : 33 : tle->resno,
7227 : : false)));
7228 : 33 : strippedexprs = lappend(strippedexprs,
7229 : 33 : processIndirection((Node *) tle->expr,
7230 : : context));
7231 : : }
7232 [ + + ]: 18 : if (action->targetList)
7233 : 15 : appendStringInfoChar(buf, ')');
7234 : :
7235 [ + + ]: 18 : if (action->override)
7236 : : {
7237 [ - + ]: 3 : if (action->override == OVERRIDING_SYSTEM_VALUE)
343 tgl@sss.pgh.pa.us 7238 :UBC 0 : appendStringInfoString(buf, " OVERRIDING SYSTEM VALUE");
343 tgl@sss.pgh.pa.us 7239 [ + - ]:CBC 3 : else if (action->override == OVERRIDING_USER_VALUE)
7240 : 3 : appendStringInfoString(buf, " OVERRIDING USER VALUE");
7241 : : }
7242 : :
7243 [ + + ]: 18 : if (strippedexprs)
7244 : : {
7245 : 15 : appendContextKeyword(context, " VALUES (",
7246 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 4);
7247 : 15 : get_rule_list_toplevel(strippedexprs, context, false);
7248 : 15 : appendStringInfoChar(buf, ')');
7249 : : }
7250 : : else
7251 : 3 : appendStringInfoString(buf, " DEFAULT VALUES");
7252 : : }
7253 [ + + ]: 21 : else if (action->commandType == CMD_UPDATE)
7254 : : {
7255 : 12 : appendStringInfoString(buf, "UPDATE SET ");
7256 : 12 : get_update_query_targetlist_def(query, action->targetList,
7257 : : context, rte);
7258 : : }
7259 [ + + ]: 9 : else if (action->commandType == CMD_DELETE)
7260 : 6 : appendStringInfoString(buf, "DELETE");
7261 [ + - ]: 3 : else if (action->commandType == CMD_NOTHING)
7262 : 3 : appendStringInfoString(buf, "DO NOTHING");
7263 : : }
7264 : :
7265 : : /* Add RETURNING if present */
28 dean.a.rasheed@gmail 7266 [ + + ]:GNC 6 : if (query->returningList)
7267 : : {
7268 : 3 : appendContextKeyword(context, " RETURNING",
7269 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
7270 : 3 : get_target_list(query->returningList, context, NULL, colNamesVisible);
7271 : : }
343 tgl@sss.pgh.pa.us 7272 :CBC 6 : }
7273 : :
7274 : :
7275 : : /* ----------
7276 : : * get_utility_query_def - Parse back a UTILITY parsetree
7277 : : * ----------
7278 : : */
7279 : : static void
8502 7280 : 8 : get_utility_query_def(Query *query, deparse_context *context)
7281 : : {
7282 : 8 : StringInfo buf = context->buf;
7283 : :
7284 [ + - + - ]: 8 : if (query->utilityStmt && IsA(query->utilityStmt, NotifyStmt))
7285 : 8 : {
7286 : 8 : NotifyStmt *stmt = (NotifyStmt *) query->utilityStmt;
7287 : :
7564 7288 : 8 : appendContextKeyword(context, "",
7289 : : 0, PRETTYINDENT_STD, 1);
8059 7290 : 8 : appendStringInfo(buf, "NOTIFY %s",
5704 7291 : 8 : quote_identifier(stmt->conditionname));
5171 7292 [ - + ]: 8 : if (stmt->payload)
7293 : : {
5171 tgl@sss.pgh.pa.us 7294 :UBC 0 : appendStringInfoString(buf, ", ");
7295 : 0 : simple_quote_literal(buf, stmt->payload);
7296 : : }
7297 : : }
7298 : : else
7299 : : {
7300 : : /* Currently only NOTIFY utility commands can appear in rules */
7567 7301 [ # # ]: 0 : elog(ERROR, "unexpected utility statement type");
7302 : : }
8502 tgl@sss.pgh.pa.us 7303 :CBC 8 : }
7304 : :
7305 : : /*
7306 : : * Display a Var appropriately.
7307 : : *
7308 : : * In some cases (currently only when recursing into an unnamed join)
7309 : : * the Var's varlevelsup has to be interpreted with respect to a context
7310 : : * above the current one; levelsup indicates the offset.
7311 : : *
7312 : : * If istoplevel is true, the Var is at the top level of a SELECT's
7313 : : * targetlist, which means we need special treatment of whole-row Vars.
7314 : : * Instead of the normal "tab.*", we'll print "tab.*::typename", which is a
7315 : : * dirty hack to prevent "tab.*" from being expanded into multiple columns.
7316 : : * (The parser will strip the useless coercion, so no inefficiency is added in
7317 : : * dump and reload.) We used to print just "tab" in such cases, but that is
7318 : : * ambiguous and will yield the wrong result if "tab" is also a plain column
7319 : : * name in the query.
7320 : : *
7321 : : * Returns the attname of the Var, or NULL if the Var has no attname (because
7322 : : * it is a whole-row Var or a subplan output reference).
7323 : : */
7324 : : static char *
4370 7325 : 72871 : get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
7326 : : {
6260 7327 : 72871 : StringInfo buf = context->buf;
7328 : : RangeTblEntry *rte;
7329 : : AttrNumber attnum;
7330 : : int netlevelsup;
7331 : : deparse_namespace *dpns;
7332 : : int varno;
7333 : : AttrNumber varattno;
7334 : : deparse_columns *colinfo;
7335 : : char *refname;
7336 : : char *attname;
7337 : :
7338 : : /* Find appropriate nesting depth */
7031 7339 : 72871 : netlevelsup = var->varlevelsup + levelsup;
7340 [ - + ]: 72871 : if (netlevelsup >= list_length(context->namespaces))
7031 tgl@sss.pgh.pa.us 7341 [ # # ]:UBC 0 : elog(ERROR, "bogus varlevelsup: %d offset %d",
7342 : : var->varlevelsup, levelsup);
7263 tgl@sss.pgh.pa.us 7343 :CBC 72871 : dpns = (deparse_namespace *) list_nth(context->namespaces,
7344 : : netlevelsup);
7345 : :
7346 : : /*
7347 : : * If we have a syntactic referent for the Var, and we're working from a
7348 : : * parse tree, prefer to use the syntactic referent. Otherwise, fall back
7349 : : * on the semantic referent. (Forcing use of the semantic referent when
7350 : : * printing plan trees is a design choice that's perhaps more motivated by
7351 : : * backwards compatibility than anything else. But it does have the
7352 : : * advantage of making plans more explicit.)
7353 : : */
1557 7354 [ + + + + ]: 72871 : if (var->varnosyn > 0 && dpns->plan == NULL)
7355 : : {
7356 : 14829 : varno = var->varnosyn;
7357 : 14829 : varattno = var->varattnosyn;
7358 : : }
7359 : : else
7360 : : {
7361 : 58042 : varno = var->varno;
7362 : 58042 : varattno = var->varattno;
7363 : : }
7364 : :
7365 : : /*
7366 : : * Try to find the relevant RTE in this rtable. In a plan tree, it's
7367 : : * likely that varno is OUTER_VAR or INNER_VAR, in which case we must dig
7368 : : * down into the subplans, or INDEX_VAR, which is resolved similarly. Also
7369 : : * find the aliases previously assigned for this RTE.
7370 : : */
7371 [ + + + - ]: 72871 : if (varno >= 1 && varno <= list_length(dpns->rtable))
7372 : : {
7373 : : /*
7374 : : * We might have been asked to map child Vars to some parent relation.
7375 : : */
1586 7376 [ + + + + ]: 54651 : if (context->appendparents && dpns->appendrels)
7377 : : {
942 7378 : 1744 : int pvarno = varno;
1586 7379 : 1744 : AttrNumber pvarattno = varattno;
7380 : 1744 : AppendRelInfo *appinfo = dpns->appendrels[pvarno];
7381 : 1744 : bool found = false;
7382 : :
7383 : : /* Only map up to inheritance parents, not UNION ALL appendrels */
7384 [ + + ]: 3543 : while (appinfo &&
7385 : 1938 : rt_fetch(appinfo->parent_relid,
7386 [ + + ]: 1938 : dpns->rtable)->rtekind == RTE_RELATION)
7387 : : {
7388 : 1799 : found = false;
7389 [ + + ]: 1799 : if (pvarattno > 0) /* system columns stay as-is */
7390 : : {
7391 [ - + ]: 1662 : if (pvarattno > appinfo->num_child_cols)
1586 tgl@sss.pgh.pa.us 7392 :UBC 0 : break; /* safety check */
1586 tgl@sss.pgh.pa.us 7393 :CBC 1662 : pvarattno = appinfo->parent_colnos[pvarattno - 1];
7394 [ - + ]: 1662 : if (pvarattno == 0)
1586 tgl@sss.pgh.pa.us 7395 :UBC 0 : break; /* Var is local to child */
7396 : : }
7397 : :
1586 tgl@sss.pgh.pa.us 7398 :CBC 1799 : pvarno = appinfo->parent_relid;
7399 : 1799 : found = true;
7400 : :
7401 : : /* If the parent is itself a child, continue up. */
7402 [ + - - + ]: 1799 : Assert(pvarno > 0 && pvarno <= list_length(dpns->rtable));
7403 : 1799 : appinfo = dpns->appendrels[pvarno];
7404 : : }
7405 : :
7406 : : /*
7407 : : * If we found an ancestral rel, and that rel is included in
7408 : : * appendparents, print that column not the original one.
7409 : : */
7410 [ + + + + ]: 1744 : if (found && bms_is_member(pvarno, context->appendparents))
7411 : : {
7412 : 1440 : varno = pvarno;
7413 : 1440 : varattno = pvarattno;
7414 : : }
7415 : : }
7416 : :
7417 : 54651 : rte = rt_fetch(varno, dpns->rtable);
7418 : 54651 : refname = (char *) list_nth(dpns->rtable_names, varno - 1);
7419 : 54651 : colinfo = deparse_columns_fetch(varno, dpns);
7420 : 54651 : attnum = varattno;
7421 : : }
7422 : : else
7423 : : {
7424 : 18220 : resolve_special_varno((Node *) var, context,
7425 : : get_special_variable, NULL);
2909 rhaas@postgresql.org 7426 : 18220 : return NULL;
7427 : : }
7428 : :
7429 : : /*
7430 : : * The planner will sometimes emit Vars referencing resjunk elements of a
7431 : : * subquery's target list (this is currently only possible if it chooses
7432 : : * to generate a "physical tlist" for a SubqueryScan or CteScan node).
7433 : : * Although we prefer to print subquery-referencing Vars using the
7434 : : * subquery's alias, that's not possible for resjunk items since they have
7435 : : * no alias. So in that case, drill down to the subplan and print the
7436 : : * contents of the referenced tlist item. This works because in a plan
7437 : : * tree, such Vars can only occur in a SubqueryScan or CteScan node, and
7438 : : * we'll have set dpns->inner_plan to reference the child plan node.
7439 : : */
5028 tgl@sss.pgh.pa.us 7440 [ + + + + : 56492 : if ((rte->rtekind == RTE_SUBQUERY || rte->rtekind == RTE_CTE) &&
+ + ]
7441 : 1841 : attnum > list_length(rte->eref->colnames) &&
1586 7442 [ + - ]: 1 : dpns->inner_plan)
7443 : : {
7444 : : TargetEntry *tle;
7445 : : deparse_namespace save_dpns;
7446 : :
7447 : 1 : tle = get_tle_by_resno(dpns->inner_tlist, attnum);
5028 7448 [ - + ]: 1 : if (!tle)
2844 tgl@sss.pgh.pa.us 7449 [ # # ]:UBC 0 : elog(ERROR, "invalid attnum %d for relation \"%s\"",
7450 : : attnum, rte->eref->aliasname);
7451 : :
5028 tgl@sss.pgh.pa.us 7452 [ - + ]:CBC 1 : Assert(netlevelsup == 0);
1586 7453 : 1 : push_child_plan(dpns, dpns->inner_plan, &save_dpns);
7454 : :
7455 : : /*
7456 : : * Force parentheses because our caller probably assumed a Var is a
7457 : : * simple expression.
7458 : : */
5028 7459 [ - + ]: 1 : if (!IsA(tle->expr, Var))
5028 tgl@sss.pgh.pa.us 7460 :UBC 0 : appendStringInfoChar(buf, '(');
5028 tgl@sss.pgh.pa.us 7461 :CBC 1 : get_rule_expr((Node *) tle->expr, context, true);
7462 [ - + ]: 1 : if (!IsA(tle->expr, Var))
5028 tgl@sss.pgh.pa.us 7463 :UBC 0 : appendStringInfoChar(buf, ')');
7464 : :
5024 tgl@sss.pgh.pa.us 7465 :CBC 1 : pop_child_plan(dpns, &save_dpns);
5028 7466 : 1 : return NULL;
7467 : : }
7468 : :
7469 : : /*
7470 : : * If it's an unnamed join, look at the expansion of the alias variable.
7471 : : * If it's a simple reference to one of the input vars, then recursively
7472 : : * print the name of that var instead. When it's not a simple reference,
7473 : : * we have to just print the unqualified join column name. (This can only
7474 : : * happen with "dangerous" merged columns in a JOIN USING; we took pains
7475 : : * previously to make the unqualified column name unique in such cases.)
7476 : : *
7477 : : * This wouldn't work in decompiling plan trees, because we don't store
7478 : : * joinaliasvars lists after planning; but a plan tree should never
7479 : : * contain a join alias variable.
7480 : : */
4223 7481 [ + + + + ]: 54650 : if (rte->rtekind == RTE_JOIN && rte->alias == NULL)
7482 : : {
7483 [ - + ]: 48 : if (rte->joinaliasvars == NIL)
4223 tgl@sss.pgh.pa.us 7484 [ # # ]:UBC 0 : elog(ERROR, "cannot decompile join alias var in plan tree");
4223 tgl@sss.pgh.pa.us 7485 [ + - ]:CBC 48 : if (attnum > 0)
7486 : : {
7487 : : Var *aliasvar;
7488 : :
7489 : 48 : aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1);
7490 : : /* we intentionally don't strip implicit coercions here */
3918 7491 [ + - - + ]: 48 : if (aliasvar && IsA(aliasvar, Var))
7492 : : {
4223 tgl@sss.pgh.pa.us 7493 :UBC 0 : return get_variable(aliasvar, var->varlevelsup + levelsup,
7494 : : istoplevel, context);
7495 : : }
7496 : : }
7497 : :
7498 : : /*
7499 : : * Unnamed join has no refname. (Note: since it's unnamed, there is
7500 : : * no way the user could have referenced it to create a whole-row Var
7501 : : * for it. So we don't have to cover that case below.)
7502 : : */
4223 tgl@sss.pgh.pa.us 7503 [ - + ]:CBC 48 : Assert(refname == NULL);
7504 : : }
7505 : :
6581 7506 [ + + ]: 54650 : if (attnum == InvalidAttrNumber)
6260 7507 : 408 : attname = NULL;
4122 7508 [ + + ]: 54242 : else if (attnum > 0)
7509 : : {
7510 : : /* Get column name to use from the colinfo struct */
2844 7511 [ - + ]: 53589 : if (attnum > colinfo->num_cols)
2844 tgl@sss.pgh.pa.us 7512 [ # # ]:UBC 0 : elog(ERROR, "invalid attnum %d for relation \"%s\"",
7513 : : attnum, rte->eref->aliasname);
4122 tgl@sss.pgh.pa.us 7514 :CBC 53589 : attname = colinfo->colnames[attnum - 1];
7515 : :
7516 : : /*
7517 : : * If we find a Var referencing a dropped column, it seems better to
7518 : : * print something (anything) than to fail. In general this should
7519 : : * not happen, but it used to be possible for some cases involving
7520 : : * functions returning named composite types, and perhaps there are
7521 : : * still bugs out there.
7522 : : */
633 7523 [ + + ]: 53589 : if (attname == NULL)
7524 : 3 : attname = "?dropped?column?";
7525 : : }
7526 : : else
7527 : : {
7528 : : /* System column - name is fixed, get it from the catalog */
6260 7529 : 653 : attname = get_rte_attribute_name(rte, attnum);
7530 : : }
7531 : :
7532 [ + + + + : 54650 : if (refname && (context->varprefix || attname == NULL))
+ + ]
7533 : : {
5274 7534 : 25907 : appendStringInfoString(buf, quote_identifier(refname));
4370 7535 : 25907 : appendStringInfoChar(buf, '.');
7536 : : }
6260 7537 [ + + ]: 54650 : if (attname)
7538 : 54242 : appendStringInfoString(buf, quote_identifier(attname));
7539 : : else
7540 : : {
7541 : 408 : appendStringInfoChar(buf, '*');
4370 7542 [ + + ]: 408 : if (istoplevel)
7543 : 36 : appendStringInfo(buf, "::%s",
7544 : : format_type_with_typemod(var->vartype,
7545 : : var->vartypmod));
7546 : : }
7547 : :
6260 7548 : 54650 : return attname;
7549 : : }
7550 : :
7551 : : /*
7552 : : * Deparse a Var which references OUTER_VAR, INNER_VAR, or INDEX_VAR. This
7553 : : * routine is actually a callback for resolve_special_varno, which handles
7554 : : * finding the correct TargetEntry. We get the expression contained in that
7555 : : * TargetEntry and just need to deparse it, a job we can throw back on
7556 : : * get_rule_expr.
7557 : : */
7558 : : static void
1586 7559 : 18220 : get_special_variable(Node *node, deparse_context *context, void *callback_arg)
7560 : : {
2909 rhaas@postgresql.org 7561 : 18220 : StringInfo buf = context->buf;
7562 : :
7563 : : /*
7564 : : * For a non-Var referent, force parentheses because our caller probably
7565 : : * assumed a Var is a simple expression.
7566 : : */
7567 [ + + ]: 18220 : if (!IsA(node, Var))
7568 : 1528 : appendStringInfoChar(buf, '(');
7569 : 18220 : get_rule_expr(node, context, true);
7570 [ + + ]: 18220 : if (!IsA(node, Var))
7571 : 1528 : appendStringInfoChar(buf, ')');
7572 : 18220 : }
7573 : :
7574 : : /*
7575 : : * Chase through plan references to special varnos (OUTER_VAR, INNER_VAR,
7576 : : * INDEX_VAR) until we find a real Var or some kind of non-Var node; then,
7577 : : * invoke the callback provided.
7578 : : */
7579 : : static void
1586 tgl@sss.pgh.pa.us 7580 : 49938 : resolve_special_varno(Node *node, deparse_context *context,
7581 : : rsv_callback callback, void *callback_arg)
7582 : : {
7583 : : Var *var;
7584 : : deparse_namespace *dpns;
7585 : :
7586 : : /* This function is recursive, so let's be paranoid. */
7587 : 49938 : check_stack_depth();
7588 : :
7589 : : /* If it's not a Var, invoke the callback. */
2909 rhaas@postgresql.org 7590 [ + + ]: 49938 : if (!IsA(node, Var))
7591 : : {
1586 tgl@sss.pgh.pa.us 7592 : 1658 : (*callback) (node, context, callback_arg);
2909 rhaas@postgresql.org 7593 : 1658 : return;
7594 : : }
7595 : :
7596 : : /* Find appropriate nesting depth */
7597 : 48280 : var = (Var *) node;
7598 : 48280 : dpns = (deparse_namespace *) list_nth(context->namespaces,
7599 : 48280 : var->varlevelsup);
7600 : :
7601 : : /*
7602 : : * If varno is special, recurse. (Don't worry about varnosyn; if we're
7603 : : * here, we already decided not to use that.)
7604 : : */
7605 [ + + + - ]: 48280 : if (var->varno == OUTER_VAR && dpns->outer_tlist)
7606 : : {
7607 : : TargetEntry *tle;
7608 : : deparse_namespace save_dpns;
7609 : : Bitmapset *save_appendparents;
7610 : :
7611 : 23494 : tle = get_tle_by_resno(dpns->outer_tlist, var->varattno);
7612 [ - + ]: 23494 : if (!tle)
2909 rhaas@postgresql.org 7613 [ # # ]:UBC 0 : elog(ERROR, "bogus varattno for OUTER_VAR var: %d", var->varattno);
7614 : :
7615 : : /*
7616 : : * If we're descending to the first child of an Append or MergeAppend,
7617 : : * update appendparents. This will affect deparsing of all Vars
7618 : : * appearing within the eventually-resolved subexpression.
7619 : : */
1586 tgl@sss.pgh.pa.us 7620 :CBC 23494 : save_appendparents = context->appendparents;
7621 : :
7622 [ + + ]: 23494 : if (IsA(dpns->plan, Append))
7623 : 1923 : context->appendparents = bms_union(context->appendparents,
7624 : 1923 : ((Append *) dpns->plan)->apprelids);
7625 [ + + ]: 21571 : else if (IsA(dpns->plan, MergeAppend))
7626 : 271 : context->appendparents = bms_union(context->appendparents,
7627 : 271 : ((MergeAppend *) dpns->plan)->apprelids);
7628 : :
7629 : 23494 : push_child_plan(dpns, dpns->outer_plan, &save_dpns);
7630 : 23494 : resolve_special_varno((Node *) tle->expr, context,
7631 : : callback, callback_arg);
2909 rhaas@postgresql.org 7632 : 23494 : pop_child_plan(dpns, &save_dpns);
1586 tgl@sss.pgh.pa.us 7633 : 23494 : context->appendparents = save_appendparents;
2909 rhaas@postgresql.org 7634 : 23494 : return;
7635 : : }
7636 [ + + + - ]: 24786 : else if (var->varno == INNER_VAR && dpns->inner_tlist)
7637 : : {
7638 : : TargetEntry *tle;
7639 : : deparse_namespace save_dpns;
7640 : :
7641 : 5819 : tle = get_tle_by_resno(dpns->inner_tlist, var->varattno);
7642 [ - + ]: 5819 : if (!tle)
2909 rhaas@postgresql.org 7643 [ # # ]:UBC 0 : elog(ERROR, "bogus varattno for INNER_VAR var: %d", var->varattno);
7644 : :
1586 tgl@sss.pgh.pa.us 7645 :CBC 5819 : push_child_plan(dpns, dpns->inner_plan, &save_dpns);
7646 : 5819 : resolve_special_varno((Node *) tle->expr, context,
7647 : : callback, callback_arg);
2909 rhaas@postgresql.org 7648 : 5819 : pop_child_plan(dpns, &save_dpns);
7649 : 5819 : return;
7650 : : }
7651 [ + + + - ]: 18967 : else if (var->varno == INDEX_VAR && dpns->index_tlist)
7652 : : {
7653 : : TargetEntry *tle;
7654 : :
7655 : 2275 : tle = get_tle_by_resno(dpns->index_tlist, var->varattno);
7656 [ - + ]: 2275 : if (!tle)
2909 rhaas@postgresql.org 7657 [ # # ]:UBC 0 : elog(ERROR, "bogus varattno for INDEX_VAR var: %d", var->varattno);
7658 : :
1586 tgl@sss.pgh.pa.us 7659 :CBC 2275 : resolve_special_varno((Node *) tle->expr, context,
7660 : : callback, callback_arg);
2909 rhaas@postgresql.org 7661 : 2275 : return;
7662 : : }
7663 [ + - - + ]: 16692 : else if (var->varno < 1 || var->varno > list_length(dpns->rtable))
2909 rhaas@postgresql.org 7664 [ # # ]:UBC 0 : elog(ERROR, "bogus varno: %d", var->varno);
7665 : :
7666 : : /* Not special. Just invoke the callback. */
1586 tgl@sss.pgh.pa.us 7667 :CBC 16692 : (*callback) (node, context, callback_arg);
7668 : : }
7669 : :
7670 : : /*
7671 : : * Get the name of a field of an expression of composite type. The
7672 : : * expression is usually a Var, but we handle other cases too.
7673 : : *
7674 : : * levelsup is an extra offset to interpret the Var's varlevelsup correctly.
7675 : : *
7676 : : * This is fairly straightforward when the expression has a named composite
7677 : : * type; we need only look up the type in the catalogs. However, the type
7678 : : * could also be RECORD. Since no actual table or view column is allowed to
7679 : : * have type RECORD, a Var of type RECORD must refer to a JOIN or FUNCTION RTE
7680 : : * or to a subquery output. We drill down to find the ultimate defining
7681 : : * expression and attempt to infer the field name from it. We ereport if we
7682 : : * can't determine the name.
7683 : : *
7684 : : * Similarly, a PARAM of type RECORD has to refer to some expression of
7685 : : * a determinable composite type.
7686 : : */
7687 : : static const char *
6893 7688 : 479 : get_name_for_var_field(Var *var, int fieldno,
7689 : : int levelsup, deparse_context *context)
7690 : : {
7691 : : RangeTblEntry *rte;
7692 : : AttrNumber attnum;
7693 : : int netlevelsup;
7694 : : deparse_namespace *dpns;
7695 : : int varno;
7696 : : AttrNumber varattno;
7697 : : TupleDesc tupleDesc;
7698 : : Node *expr;
7699 : :
7700 : : /*
7701 : : * If it's a RowExpr that was expanded from a whole-row Var, use the
7702 : : * column names attached to it. (We could let get_expr_result_tupdesc()
7703 : : * handle this, but it's much cheaper to just pull out the name we need.)
7704 : : */
5669 7705 [ + + ]: 479 : if (IsA(var, RowExpr))
7706 : : {
5421 bruce@momjian.us 7707 : 18 : RowExpr *r = (RowExpr *) var;
7708 : :
5669 tgl@sss.pgh.pa.us 7709 [ + - + - ]: 18 : if (fieldno > 0 && fieldno <= list_length(r->colnames))
7710 : 18 : return strVal(list_nth(r->colnames, fieldno - 1));
7711 : : }
7712 : :
7713 : : /*
7714 : : * If it's a Param of type RECORD, try to find what the Param refers to.
7715 : : */
4603 7716 [ + + ]: 461 : if (IsA(var, Param))
7717 : : {
7718 : 9 : Param *param = (Param *) var;
7719 : : ListCell *ancestor_cell;
7720 : :
7721 : 9 : expr = find_param_referent(param, context, &dpns, &ancestor_cell);
7722 [ + - ]: 9 : if (expr)
7723 : : {
7724 : : /* Found a match, so recurse to decipher the field name */
7725 : : deparse_namespace save_dpns;
7726 : : const char *result;
7727 : :
7728 : 9 : push_ancestor_plan(dpns, ancestor_cell, &save_dpns);
7729 : 9 : result = get_name_for_var_field((Var *) expr, fieldno,
7730 : : 0, context);
7731 : 9 : pop_ancestor_plan(dpns, &save_dpns);
7732 : 9 : return result;
7733 : : }
7734 : : }
7735 : :
7736 : : /*
7737 : : * If it's a Var of type RECORD, we have to find what the Var refers to;
7738 : : * if not, we can use get_expr_result_tupdesc().
7739 : : */
6260 7740 [ + + ]: 452 : if (!IsA(var, Var) ||
7741 [ + + ]: 421 : var->vartype != RECORDOID)
7742 : : {
2362 7743 : 347 : tupleDesc = get_expr_result_tupdesc((Node *) var, false);
7744 : : /* Got the tupdesc, so we can extract the field name */
6260 7745 [ + - - + ]: 347 : Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
2429 andres@anarazel.de 7746 : 347 : return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname);
7747 : : }
7748 : :
7749 : : /* Find appropriate nesting depth */
6260 tgl@sss.pgh.pa.us 7750 : 105 : netlevelsup = var->varlevelsup + levelsup;
7751 [ - + ]: 105 : if (netlevelsup >= list_length(context->namespaces))
6260 tgl@sss.pgh.pa.us 7752 [ # # ]:UBC 0 : elog(ERROR, "bogus varlevelsup: %d offset %d",
7753 : : var->varlevelsup, levelsup);
6260 tgl@sss.pgh.pa.us 7754 :CBC 105 : dpns = (deparse_namespace *) list_nth(context->namespaces,
7755 : : netlevelsup);
7756 : :
7757 : : /*
7758 : : * If we have a syntactic referent for the Var, and we're working from a
7759 : : * parse tree, prefer to use the syntactic referent. Otherwise, fall back
7760 : : * on the semantic referent. (See comments in get_variable().)
7761 : : */
1557 7762 [ + + + + ]: 105 : if (var->varnosyn > 0 && dpns->plan == NULL)
7763 : : {
7764 : 48 : varno = var->varnosyn;
7765 : 48 : varattno = var->varattnosyn;
7766 : : }
7767 : : else
7768 : : {
7769 : 57 : varno = var->varno;
7770 : 57 : varattno = var->varattno;
7771 : : }
7772 : :
7773 : : /*
7774 : : * Try to find the relevant RTE in this rtable. In a plan tree, it's
7775 : : * likely that varno is OUTER_VAR or INNER_VAR, in which case we must dig
7776 : : * down into the subplans, or INDEX_VAR, which is resolved similarly.
7777 : : *
7778 : : * Note: unlike get_variable and resolve_special_varno, we need not worry
7779 : : * about inheritance mapping: a child Var should have the same datatype as
7780 : : * its parent, and here we're really only interested in the Var's type.
7781 : : */
7782 [ + + + - ]: 105 : if (varno >= 1 && varno <= list_length(dpns->rtable))
7783 : : {
7784 : 66 : rte = rt_fetch(varno, dpns->rtable);
7785 : 66 : attnum = varattno;
7786 : : }
7787 [ + + + - ]: 39 : else if (varno == OUTER_VAR && dpns->outer_tlist)
7788 : : {
7789 : : TargetEntry *tle;
7790 : : deparse_namespace save_dpns;
7791 : : const char *result;
7792 : :
7793 : 30 : tle = get_tle_by_resno(dpns->outer_tlist, varattno);
6260 7794 [ - + ]: 30 : if (!tle)
1557 tgl@sss.pgh.pa.us 7795 [ # # ]:UBC 0 : elog(ERROR, "bogus varattno for OUTER_VAR var: %d", varattno);
7796 : :
6260 tgl@sss.pgh.pa.us 7797 [ - + ]:CBC 30 : Assert(netlevelsup == 0);
1586 7798 : 30 : push_child_plan(dpns, dpns->outer_plan, &save_dpns);
7799 : :
6260 7800 : 30 : result = get_name_for_var_field((Var *) tle->expr, fieldno,
7801 : : levelsup, context);
7802 : :
5024 7803 : 30 : pop_child_plan(dpns, &save_dpns);
6260 7804 : 30 : return result;
7805 : : }
1557 7806 [ + - + - ]: 9 : else if (varno == INNER_VAR && dpns->inner_tlist)
7807 : : {
7808 : : TargetEntry *tle;
7809 : : deparse_namespace save_dpns;
7810 : : const char *result;
7811 : :
7812 : 9 : tle = get_tle_by_resno(dpns->inner_tlist, varattno);
6260 7813 [ - + ]: 9 : if (!tle)
1557 tgl@sss.pgh.pa.us 7814 [ # # ]:UBC 0 : elog(ERROR, "bogus varattno for INNER_VAR var: %d", varattno);
7815 : :
6260 tgl@sss.pgh.pa.us 7816 [ - + ]:CBC 9 : Assert(netlevelsup == 0);
1586 7817 : 9 : push_child_plan(dpns, dpns->inner_plan, &save_dpns);
7818 : :
6260 7819 : 9 : result = get_name_for_var_field((Var *) tle->expr, fieldno,
7820 : : levelsup, context);
7821 : :
5024 7822 : 9 : pop_child_plan(dpns, &save_dpns);
6260 7823 : 9 : return result;
7824 : : }
1557 tgl@sss.pgh.pa.us 7825 [ # # # # ]:UBC 0 : else if (varno == INDEX_VAR && dpns->index_tlist)
7826 : : {
7827 : : TargetEntry *tle;
7828 : : const char *result;
7829 : :
7830 : 0 : tle = get_tle_by_resno(dpns->index_tlist, varattno);
4569 7831 [ # # ]: 0 : if (!tle)
1557 7832 [ # # ]: 0 : elog(ERROR, "bogus varattno for INDEX_VAR var: %d", varattno);
7833 : :
4569 7834 [ # # ]: 0 : Assert(netlevelsup == 0);
7835 : :
7836 : 0 : result = get_name_for_var_field((Var *) tle->expr, fieldno,
7837 : : levelsup, context);
7838 : :
7839 : 0 : return result;
7840 : : }
7841 : : else
7842 : : {
1557 7843 [ # # ]: 0 : elog(ERROR, "bogus varno: %d", varno);
7844 : : return NULL; /* keep compiler quiet */
7845 : : }
7846 : :
6893 tgl@sss.pgh.pa.us 7847 [ + + ]:CBC 66 : if (attnum == InvalidAttrNumber)
7848 : : {
7849 : : /* Var is whole-row reference to RTE, so select the right field */
7850 : 12 : return get_rte_attribute_name(rte, fieldno);
7851 : : }
7852 : :
7853 : : /*
7854 : : * This part has essentially the same logic as the parser's
7855 : : * expandRecordVariable() function, but we are dealing with a different
7856 : : * representation of the input context, and we only need one field name
7857 : : * not a TupleDesc. Also, we need special cases for finding subquery and
7858 : : * CTE subplans when deparsing Plan trees.
7859 : : */
7860 : 54 : expr = (Node *) var; /* default if we can't drill down */
7861 : :
7862 [ - + - - : 54 : switch (rte->rtekind)
+ - ]
7863 : : {
6893 tgl@sss.pgh.pa.us 7864 :UBC 0 : case RTE_RELATION:
7865 : : case RTE_VALUES:
7866 : : case RTE_NAMEDTUPLESTORE:
7867 : : case RTE_RESULT:
7868 : :
7869 : : /*
7870 : : * This case should not occur: a column of a table, values list,
7871 : : * or ENR shouldn't have type RECORD. Fall through and fail (most
7872 : : * likely) at the bottom.
7873 : : */
7874 : 0 : break;
6893 tgl@sss.pgh.pa.us 7875 :CBC 24 : case RTE_SUBQUERY:
7876 : : /* Subselect-in-FROM: examine sub-select's output expr */
7877 : : {
6260 7878 [ + + ]: 24 : if (rte->subquery)
7879 : : {
7880 : 21 : TargetEntry *ste = get_tle_by_resno(rte->subquery->targetList,
7881 : : attnum);
7882 : :
7883 [ + - - + ]: 21 : if (ste == NULL || ste->resjunk)
6260 tgl@sss.pgh.pa.us 7884 [ # # ]:UBC 0 : elog(ERROR, "subquery %s does not have attribute %d",
7885 : : rte->eref->aliasname, attnum);
6260 tgl@sss.pgh.pa.us 7886 :CBC 21 : expr = (Node *) ste->expr;
7887 [ + + ]: 21 : if (IsA(expr, Var))
7888 : : {
7889 : : /*
7890 : : * Recurse into the sub-select to see what its Var
7891 : : * refers to. We have to build an additional level of
7892 : : * namespace to keep in step with varlevelsup in the
7893 : : * subselect; furthermore, the subquery RTE might be
7894 : : * from an outer query level, in which case the
7895 : : * namespace for the subselect must have that outer
7896 : : * level as parent namespace.
7897 : : */
212 7898 : 9 : List *save_nslist = context->namespaces;
7899 : : List *parent_namespaces;
7900 : : deparse_namespace mydpns;
7901 : : const char *result;
7902 : :
7903 : 9 : parent_namespaces = list_copy_tail(context->namespaces,
7904 : : netlevelsup);
7905 : :
4122 7906 : 9 : set_deparse_for_query(&mydpns, rte->subquery,
7907 : : parent_namespaces);
7908 : :
212 7909 : 9 : context->namespaces = lcons(&mydpns, parent_namespaces);
7910 : :
6260 7911 : 9 : result = get_name_for_var_field((Var *) expr, fieldno,
7912 : : 0, context);
7913 : :
212 7914 : 9 : context->namespaces = save_nslist;
7915 : :
6260 7916 : 9 : return result;
7917 : : }
7918 : : /* else fall through to inspect the expression */
7919 : : }
7920 : : else
7921 : : {
7922 : : /*
7923 : : * We're deparsing a Plan tree so we don't have complete
7924 : : * RTE entries (in particular, rte->subquery is NULL). But
7925 : : * the only place we'd see a Var directly referencing a
7926 : : * SUBQUERY RTE is in a SubqueryScan plan node, and we can
7927 : : * look into the child plan's tlist instead.
7928 : : */
7929 : : TargetEntry *tle;
7930 : : deparse_namespace save_dpns;
7931 : : const char *result;
7932 : :
1586 7933 [ - + ]: 3 : if (!dpns->inner_plan)
6260 tgl@sss.pgh.pa.us 7934 [ # # ]:UBC 0 : elog(ERROR, "failed to find plan for subquery %s",
7935 : : rte->eref->aliasname);
4569 tgl@sss.pgh.pa.us 7936 :CBC 3 : tle = get_tle_by_resno(dpns->inner_tlist, attnum);
6260 7937 [ - + ]: 3 : if (!tle)
6260 tgl@sss.pgh.pa.us 7938 [ # # ]:UBC 0 : elog(ERROR, "bogus varattno for subquery var: %d",
7939 : : attnum);
6260 tgl@sss.pgh.pa.us 7940 [ - + ]:CBC 3 : Assert(netlevelsup == 0);
1586 7941 : 3 : push_child_plan(dpns, dpns->inner_plan, &save_dpns);
7942 : :
6260 7943 : 3 : result = get_name_for_var_field((Var *) tle->expr, fieldno,
7944 : : levelsup, context);
7945 : :
5024 7946 : 3 : pop_child_plan(dpns, &save_dpns);
6893 7947 : 3 : return result;
7948 : : }
7949 : : }
6893 tgl@sss.pgh.pa.us 7950 :GBC 12 : break;
6893 tgl@sss.pgh.pa.us 7951 :UBC 0 : case RTE_JOIN:
7952 : : /* Join RTE --- recursively inspect the alias variable */
6170 7953 [ # # ]: 0 : if (rte->joinaliasvars == NIL)
7954 [ # # ]: 0 : elog(ERROR, "cannot decompile join alias var in plan tree");
6893 7955 [ # # # # ]: 0 : Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars));
7956 : 0 : expr = (Node *) list_nth(rte->joinaliasvars, attnum - 1);
3918 7957 [ # # ]: 0 : Assert(expr != NULL);
7958 : : /* we intentionally don't strip implicit coercions here */
6893 7959 [ # # ]: 0 : if (IsA(expr, Var))
7960 : 0 : return get_name_for_var_field((Var *) expr, fieldno,
7961 : 0 : var->varlevelsup + levelsup,
7962 : : context);
7963 : : /* else fall through to inspect the expression */
7964 : 0 : break;
7965 : 0 : case RTE_FUNCTION:
7966 : : case RTE_TABLEFUNC:
7967 : :
7968 : : /*
7969 : : * We couldn't get here unless a function is declared with one of
7970 : : * its result columns as RECORD, which is not allowed.
7971 : : */
7972 : 0 : break;
5671 tgl@sss.pgh.pa.us 7973 :CBC 30 : case RTE_CTE:
7974 : : /* CTE reference: examine subquery's output expr */
7975 : : {
5669 7976 : 30 : CommonTableExpr *cte = NULL;
7977 : : Index ctelevelsup;
7978 : : ListCell *lc;
7979 : :
7980 : : /*
7981 : : * Try to find the referenced CTE using the namespace stack.
7982 : : */
7983 : 30 : ctelevelsup = rte->ctelevelsup + netlevelsup;
7984 [ + + ]: 30 : if (ctelevelsup >= list_length(context->namespaces))
7985 : 6 : lc = NULL;
7986 : : else
7987 : : {
7988 : : deparse_namespace *ctedpns;
7989 : :
7990 : : ctedpns = (deparse_namespace *)
7991 : 24 : list_nth(context->namespaces, ctelevelsup);
7992 [ + + + - : 27 : foreach(lc, ctedpns->ctes)
+ + ]
7993 : : {
7994 : 18 : cte = (CommonTableExpr *) lfirst(lc);
7995 [ + + ]: 18 : if (strcmp(cte->ctename, rte->ctename) == 0)
7996 : 15 : break;
7997 : : }
7998 : : }
7999 [ + + ]: 30 : if (lc != NULL)
8000 : : {
8001 : 15 : Query *ctequery = (Query *) cte->ctequery;
4753 bruce@momjian.us 8002 [ - + + - ]: 15 : TargetEntry *ste = get_tle_by_resno(GetCTETargetList(cte),
8003 : : attnum);
8004 : :
5669 tgl@sss.pgh.pa.us 8005 [ + - - + ]: 15 : if (ste == NULL || ste->resjunk)
212 tgl@sss.pgh.pa.us 8006 [ # # ]:UBC 0 : elog(ERROR, "CTE %s does not have attribute %d",
8007 : : rte->eref->aliasname, attnum);
5669 tgl@sss.pgh.pa.us 8008 :CBC 15 : expr = (Node *) ste->expr;
8009 [ + + ]: 15 : if (IsA(expr, Var))
8010 : : {
8011 : : /*
8012 : : * Recurse into the CTE to see what its Var refers to.
8013 : : * We have to build an additional level of namespace
8014 : : * to keep in step with varlevelsup in the CTE;
8015 : : * furthermore it could be an outer CTE (compare
8016 : : * SUBQUERY case above).
8017 : : */
8018 : 9 : List *save_nslist = context->namespaces;
8019 : : List *parent_namespaces;
8020 : : deparse_namespace mydpns;
8021 : : const char *result;
8022 : :
212 8023 : 9 : parent_namespaces = list_copy_tail(context->namespaces,
8024 : : ctelevelsup);
8025 : :
4122 8026 : 9 : set_deparse_for_query(&mydpns, ctequery,
8027 : : parent_namespaces);
8028 : :
212 8029 : 9 : context->namespaces = lcons(&mydpns, parent_namespaces);
8030 : :
5669 8031 : 9 : result = get_name_for_var_field((Var *) expr, fieldno,
8032 : : 0, context);
8033 : :
8034 : 9 : context->namespaces = save_nslist;
8035 : :
8036 : 9 : return result;
8037 : : }
8038 : : /* else fall through to inspect the expression */
8039 : : }
8040 : : else
8041 : : {
8042 : : /*
8043 : : * We're deparsing a Plan tree so we don't have a CTE
8044 : : * list. But the only places we'd see a Var directly
8045 : : * referencing a CTE RTE are in CteScan or WorkTableScan
8046 : : * plan nodes. For those cases, set_deparse_plan arranged
8047 : : * for dpns->inner_plan to be the plan node that emits the
8048 : : * CTE or RecursiveUnion result, and we can look at its
8049 : : * tlist instead.
8050 : : */
8051 : : TargetEntry *tle;
8052 : : deparse_namespace save_dpns;
8053 : : const char *result;
8054 : :
1586 8055 [ - + ]: 15 : if (!dpns->inner_plan)
5669 tgl@sss.pgh.pa.us 8056 [ # # ]:UBC 0 : elog(ERROR, "failed to find plan for CTE %s",
8057 : : rte->eref->aliasname);
4569 tgl@sss.pgh.pa.us 8058 :CBC 15 : tle = get_tle_by_resno(dpns->inner_tlist, attnum);
5669 8059 [ - + ]: 15 : if (!tle)
5669 tgl@sss.pgh.pa.us 8060 [ # # ]:UBC 0 : elog(ERROR, "bogus varattno for subquery var: %d",
8061 : : attnum);
5669 tgl@sss.pgh.pa.us 8062 [ - + ]:CBC 15 : Assert(netlevelsup == 0);
1586 8063 : 15 : push_child_plan(dpns, dpns->inner_plan, &save_dpns);
8064 : :
5669 8065 : 15 : result = get_name_for_var_field((Var *) tle->expr, fieldno,
8066 : : levelsup, context);
8067 : :
5024 8068 : 15 : pop_child_plan(dpns, &save_dpns);
5669 8069 : 15 : return result;
8070 : : }
8071 : : }
5671 8072 : 6 : break;
8073 : : }
8074 : :
8075 : : /*
8076 : : * We now have an expression we can't expand any more, so see if
8077 : : * get_expr_result_tupdesc() can do anything with it.
8078 : : */
2362 8079 : 18 : tupleDesc = get_expr_result_tupdesc(expr, false);
8080 : : /* Got the tupdesc, so we can extract the field name */
6893 8081 [ + - - + ]: 18 : Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
2429 andres@anarazel.de 8082 : 18 : return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname);
8083 : : }
8084 : :
8085 : : /*
8086 : : * Try to find the referenced expression for a PARAM_EXEC Param that might
8087 : : * reference a parameter supplied by an upper NestLoop or SubPlan plan node.
8088 : : *
8089 : : * If successful, return the expression and set *dpns_p and *ancestor_cell_p
8090 : : * appropriately for calling push_ancestor_plan(). If no referent can be
8091 : : * found, return NULL.
8092 : : */
8093 : : static Node *
4603 tgl@sss.pgh.pa.us 8094 : 3102 : find_param_referent(Param *param, deparse_context *context,
8095 : : deparse_namespace **dpns_p, ListCell **ancestor_cell_p)
8096 : : {
8097 : : /* Initialize output parameters to prevent compiler warnings */
8098 : 3102 : *dpns_p = NULL;
8099 : 3102 : *ancestor_cell_p = NULL;
8100 : :
8101 : : /*
8102 : : * If it's a PARAM_EXEC parameter, look for a matching NestLoopParam or
8103 : : * SubPlan argument. This will necessarily be in some ancestor of the
8104 : : * current expression's Plan node.
8105 : : */
5024 8106 [ + + ]: 3102 : if (param->paramkind == PARAM_EXEC)
8107 : : {
8108 : : deparse_namespace *dpns;
8109 : : Plan *child_plan;
8110 : : ListCell *lc;
8111 : :
8112 : 2672 : dpns = (deparse_namespace *) linitial(context->namespaces);
1586 8113 : 2672 : child_plan = dpns->plan;
8114 : :
5024 8115 [ + + + + : 4842 : foreach(lc, dpns->ancestors)
+ + ]
8116 : : {
1586 8117 : 4060 : Node *ancestor = (Node *) lfirst(lc);
8118 : : ListCell *lc2;
8119 : :
8120 : : /*
8121 : : * NestLoops transmit params to their inner child only.
8122 : : */
8123 [ + + ]: 4060 : if (IsA(ancestor, NestLoop) &&
515 8124 [ + + ]: 1737 : child_plan == innerPlan(ancestor))
8125 : : {
1586 8126 : 1691 : NestLoop *nl = (NestLoop *) ancestor;
8127 : :
5024 8128 [ + + + + : 2136 : foreach(lc2, nl->nestParams)
+ + ]
8129 : : {
4753 bruce@momjian.us 8130 : 2070 : NestLoopParam *nlp = (NestLoopParam *) lfirst(lc2);
8131 : :
5024 tgl@sss.pgh.pa.us 8132 [ + + ]: 2070 : if (nlp->paramno == param->paramid)
8133 : : {
8134 : : /* Found a match, so return it */
4603 8135 : 1625 : *dpns_p = dpns;
8136 : 1625 : *ancestor_cell_p = lc;
8137 : 1625 : return (Node *) nlp->paramval;
8138 : : }
8139 : : }
8140 : : }
8141 : :
8142 : : /*
8143 : : * If ancestor is a SubPlan, check the arguments it provides.
8144 : : */
1586 8145 [ + + ]: 2435 : if (IsA(ancestor, SubPlan))
5024 8146 : 165 : {
1586 8147 : 430 : SubPlan *subplan = (SubPlan *) ancestor;
8148 : : ListCell *lc3;
8149 : : ListCell *lc4;
8150 : :
5024 8151 [ + + + + : 577 : forboth(lc3, subplan->parParam, lc4, subplan->args)
+ + + + +
+ + - +
+ ]
8152 : : {
4753 bruce@momjian.us 8153 : 412 : int paramid = lfirst_int(lc3);
8154 : 412 : Node *arg = (Node *) lfirst(lc4);
8155 : :
5024 tgl@sss.pgh.pa.us 8156 [ + + ]: 412 : if (paramid == param->paramid)
8157 : : {
8158 : : /*
8159 : : * Found a match, so return it. But, since Vars in
8160 : : * the arg are to be evaluated in the surrounding
8161 : : * context, we have to point to the next ancestor item
8162 : : * that is *not* a SubPlan.
8163 : : */
8164 : : ListCell *rest;
8165 : :
1586 8166 [ + - + - : 265 : for_each_cell(rest, dpns->ancestors,
+ - ]
8167 : : lnext(dpns->ancestors, lc))
8168 : : {
8169 : 265 : Node *ancestor2 = (Node *) lfirst(rest);
8170 : :
8171 [ + - ]: 265 : if (!IsA(ancestor2, SubPlan))
8172 : : {
8173 : 265 : *dpns_p = dpns;
8174 : 265 : *ancestor_cell_p = rest;
8175 : 265 : return arg;
8176 : : }
8177 : : }
1586 tgl@sss.pgh.pa.us 8178 [ # # ]:UBC 0 : elog(ERROR, "SubPlan cannot be outermost ancestor");
8179 : : }
8180 : : }
8181 : :
8182 : : /* SubPlan isn't a kind of Plan, so skip the rest */
1586 tgl@sss.pgh.pa.us 8183 :CBC 165 : continue;
8184 : : }
8185 : :
8186 : : /*
8187 : : * We need not consider the ancestor's initPlan list, since
8188 : : * initplans never have any parParams.
8189 : : */
8190 : :
8191 : : /* No luck, crawl up to next ancestor */
8192 : 2005 : child_plan = (Plan *) ancestor;
8193 : : }
8194 : : }
8195 : :
8196 : : /* No referent found */
4603 8197 : 1212 : return NULL;
8198 : : }
8199 : :
8200 : : /*
8201 : : * Try to find a subplan/initplan that emits the value for a PARAM_EXEC Param.
8202 : : *
8203 : : * If successful, return the generating subplan/initplan and set *column_p
8204 : : * to the subplan's 0-based output column number.
8205 : : * Otherwise, return NULL.
8206 : : */
8207 : : static SubPlan *
26 tgl@sss.pgh.pa.us 8208 :GNC 1212 : find_param_generator(Param *param, deparse_context *context, int *column_p)
8209 : : {
8210 : : /* Initialize output parameter to prevent compiler warnings */
8211 : 1212 : *column_p = 0;
8212 : :
8213 : : /*
8214 : : * If it's a PARAM_EXEC parameter, search the current plan node as well as
8215 : : * ancestor nodes looking for a subplan or initplan that emits the value
8216 : : * for the Param. It could appear in the setParams of an initplan or
8217 : : * MULTIEXPR_SUBLINK subplan, or in the paramIds of an ancestral SubPlan.
8218 : : */
8219 [ + + ]: 1212 : if (param->paramkind == PARAM_EXEC)
8220 : : {
8221 : : SubPlan *result;
8222 : : deparse_namespace *dpns;
8223 : : ListCell *lc;
8224 : :
8225 : 782 : dpns = (deparse_namespace *) linitial(context->namespaces);
8226 : :
8227 : : /* First check the innermost plan node's initplans */
8228 : 782 : result = find_param_generator_initplan(param, dpns->plan, column_p);
8229 [ + + ]: 782 : if (result)
8230 : 225 : return result;
8231 : :
8232 : : /*
8233 : : * The plan's targetlist might contain MULTIEXPR_SUBLINK SubPlans,
8234 : : * which can be referenced by Params elsewhere in the targetlist.
8235 : : * (Such Params should always be in the same targetlist, so there's no
8236 : : * need to do this work at upper plan nodes.)
8237 : : */
8238 [ + + + + : 2845 : foreach_node(TargetEntry, tle, dpns->plan->targetlist)
+ + ]
8239 : : {
8240 [ + - + + ]: 1783 : if (tle->expr && IsA(tle->expr, SubPlan))
8241 : : {
8242 : 50 : SubPlan *subplan = (SubPlan *) tle->expr;
8243 : :
8244 [ + + ]: 50 : if (subplan->subLinkType == MULTIEXPR_SUBLINK)
8245 : : {
8246 [ + - + - : 39 : foreach_int(paramid, subplan->setParam)
+ - ]
8247 : : {
8248 [ + + ]: 39 : if (paramid == param->paramid)
8249 : : {
8250 : : /* Found a match, so return it. */
8251 : 26 : *column_p = foreach_current_index(paramid);
8252 : 26 : return subplan;
8253 : : }
8254 : : }
8255 : : }
8256 : : }
8257 : : }
8258 : :
8259 : : /* No luck, so check the ancestor nodes */
8260 [ + - + - : 696 : foreach(lc, dpns->ancestors)
+ - ]
8261 : : {
8262 : 696 : Node *ancestor = (Node *) lfirst(lc);
8263 : :
8264 : : /*
8265 : : * If ancestor is a SubPlan, check the paramIds it provides.
8266 : : */
8267 [ + + ]: 696 : if (IsA(ancestor, SubPlan))
26 tgl@sss.pgh.pa.us 8268 :UNC 0 : {
26 tgl@sss.pgh.pa.us 8269 :GNC 102 : SubPlan *subplan = (SubPlan *) ancestor;
8270 : :
8271 [ + - + - : 115 : foreach_int(paramid, subplan->paramIds)
+ - ]
8272 : : {
8273 [ + + ]: 115 : if (paramid == param->paramid)
8274 : : {
8275 : : /* Found a match, so return it. */
8276 : 102 : *column_p = foreach_current_index(paramid);
8277 : 102 : return subplan;
8278 : : }
8279 : : }
8280 : :
8281 : : /* SubPlan isn't a kind of Plan, so skip the rest */
26 tgl@sss.pgh.pa.us 8282 :UNC 0 : continue;
8283 : : }
8284 : :
8285 : : /*
8286 : : * Otherwise, it's some kind of Plan node, so check its initplans.
8287 : : */
26 tgl@sss.pgh.pa.us 8288 :GNC 594 : result = find_param_generator_initplan(param, (Plan *) ancestor,
8289 : : column_p);
8290 [ + + ]: 594 : if (result)
8291 : 429 : return result;
8292 : :
8293 : : /* No luck, crawl up to next ancestor */
8294 : : }
8295 : : }
8296 : :
8297 : : /* No generator found */
8298 : 430 : return NULL;
8299 : : }
8300 : :
8301 : : /*
8302 : : * Subroutine for find_param_generator: search one Plan node's initplans
8303 : : */
8304 : : static SubPlan *
8305 : 1376 : find_param_generator_initplan(Param *param, Plan *plan, int *column_p)
8306 : : {
8307 [ + + + - : 2168 : foreach_node(SubPlan, subplan, plan->initPlan)
+ + ]
8308 : : {
8309 [ + - + + : 867 : foreach_int(paramid, subplan->setParam)
+ + ]
8310 : : {
8311 [ + + ]: 727 : if (paramid == param->paramid)
8312 : : {
8313 : : /* Found a match, so return it. */
8314 : 654 : *column_p = foreach_current_index(paramid);
8315 : 654 : return subplan;
8316 : : }
8317 : : }
8318 : : }
8319 : 722 : return NULL;
8320 : : }
8321 : :
8322 : : /*
8323 : : * Display a Param appropriately.
8324 : : */
8325 : : static void
4603 tgl@sss.pgh.pa.us 8326 :CBC 3093 : get_parameter(Param *param, deparse_context *context)
8327 : : {
8328 : : Node *expr;
8329 : : deparse_namespace *dpns;
8330 : : ListCell *ancestor_cell;
8331 : : SubPlan *subplan;
8332 : : int column;
8333 : :
8334 : : /*
8335 : : * If it's a PARAM_EXEC parameter, try to locate the expression from which
8336 : : * the parameter was computed. This stanza handles only cases in which
8337 : : * the Param represents an input to the subplan we are currently in.
8338 : : */
8339 : 3093 : expr = find_param_referent(param, context, &dpns, &ancestor_cell);
8340 [ + + ]: 3093 : if (expr)
8341 : : {
8342 : : /* Found a match, so print it */
8343 : : deparse_namespace save_dpns;
8344 : : bool save_varprefix;
8345 : : bool need_paren;
8346 : :
8347 : : /* Switch attention to the ancestor plan node */
8348 : 1881 : push_ancestor_plan(dpns, ancestor_cell, &save_dpns);
8349 : :
8350 : : /*
8351 : : * Force prefixing of Vars, since they won't belong to the relation
8352 : : * being scanned in the original plan node.
8353 : : */
8354 : 1881 : save_varprefix = context->varprefix;
8355 : 1881 : context->varprefix = true;
8356 : :
8357 : : /*
8358 : : * A Param's expansion is typically a Var, Aggref, GroupingFunc, or
8359 : : * upper-level Param, which wouldn't need extra parentheses.
8360 : : * Otherwise, insert parens to ensure the expression looks atomic.
8361 : : */
8362 [ + + ]: 1884 : need_paren = !(IsA(expr, Var) ||
8363 [ + - ]: 3 : IsA(expr, Aggref) ||
755 8364 [ - + ]: 3 : IsA(expr, GroupingFunc) ||
4603 tgl@sss.pgh.pa.us 8365 [ # # ]:UBC 0 : IsA(expr, Param));
4603 tgl@sss.pgh.pa.us 8366 [ - + ]:CBC 1881 : if (need_paren)
4603 tgl@sss.pgh.pa.us 8367 :UBC 0 : appendStringInfoChar(context->buf, '(');
8368 : :
4603 tgl@sss.pgh.pa.us 8369 :CBC 1881 : get_rule_expr(expr, context, false);
8370 : :
8371 [ - + ]: 1881 : if (need_paren)
4603 tgl@sss.pgh.pa.us 8372 :UBC 0 : appendStringInfoChar(context->buf, ')');
8373 : :
4603 tgl@sss.pgh.pa.us 8374 :CBC 1881 : context->varprefix = save_varprefix;
8375 : :
8376 : 1881 : pop_ancestor_plan(dpns, &save_dpns);
8377 : :
8378 : 1881 : return;
8379 : : }
8380 : :
8381 : : /*
8382 : : * Alternatively, maybe it's a subplan output, which we print as a
8383 : : * reference to the subplan. (We could drill down into the subplan and
8384 : : * print the relevant targetlist expression, but that has been deemed too
8385 : : * confusing since it would violate normal SQL scope rules. Also, we're
8386 : : * relying on this reference to show that the testexpr containing the
8387 : : * Param has anything to do with that subplan at all.)
8388 : : */
26 tgl@sss.pgh.pa.us 8389 :GNC 1212 : subplan = find_param_generator(param, context, &column);
8390 [ + + ]: 1212 : if (subplan)
8391 : : {
8392 : 782 : appendStringInfo(context->buf, "(%s%s).col%d",
8393 [ + + ]: 782 : subplan->useHashTable ? "hashed " : "",
8394 : : subplan->plan_name, column + 1);
8395 : :
8396 : 782 : return;
8397 : : }
8398 : :
8399 : : /*
8400 : : * If it's an external parameter, see if the outermost namespace provides
8401 : : * function argument names.
8402 : : */
879 tgl@sss.pgh.pa.us 8403 [ + - + - ]:CBC 430 : if (param->paramkind == PARAM_EXTERN && context->namespaces != NIL)
8404 : : {
8405 : 430 : dpns = llast(context->namespaces);
8406 [ + + ]: 430 : if (dpns->argnames &&
8407 [ + - ]: 34 : param->paramid > 0 &&
8408 [ + - ]: 34 : param->paramid <= dpns->numargs)
8409 : : {
1103 peter@eisentraut.org 8410 : 34 : char *argname = dpns->argnames[param->paramid - 1];
8411 : :
8412 [ + - ]: 34 : if (argname)
8413 : : {
8414 : 34 : bool should_qualify = false;
8415 : : ListCell *lc;
8416 : :
8417 : : /*
8418 : : * Qualify the parameter name if there are any other deparse
8419 : : * namespaces with range tables. This avoids qualifying in
8420 : : * trivial cases like "RETURN a + b", but makes it safe in all
8421 : : * other cases.
8422 : : */
8423 [ + - + + : 78 : foreach(lc, context->namespaces)
+ + ]
8424 : : {
557 drowley@postgresql.o 8425 : 59 : deparse_namespace *depns = lfirst(lc);
8426 : :
8427 [ + + ]: 59 : if (depns->rtable_names != NIL)
8428 : : {
1103 peter@eisentraut.org 8429 : 15 : should_qualify = true;
8430 : 15 : break;
8431 : : }
8432 : : }
8433 [ + + ]: 34 : if (should_qualify)
8434 : : {
8435 : 15 : appendStringInfoString(context->buf, quote_identifier(dpns->funcname));
8436 : 15 : appendStringInfoChar(context->buf, '.');
8437 : : }
8438 : :
8439 : 34 : appendStringInfoString(context->buf, quote_identifier(argname));
8440 : 34 : return;
8441 : : }
8442 : : }
8443 : : }
8444 : :
8445 : : /*
8446 : : * Not PARAM_EXEC, or couldn't find referent: just print $N.
8447 : : *
8448 : : * It's a bug if we get here for anything except PARAM_EXTERN Params, but
8449 : : * in production builds printing $N seems more useful than failing.
8450 : : */
26 tgl@sss.pgh.pa.us 8451 [ - + ]:GNC 396 : Assert(param->paramkind == PARAM_EXTERN);
8452 : :
4603 tgl@sss.pgh.pa.us 8453 :CBC 396 : appendStringInfo(context->buf, "$%d", param->paramid);
8454 : : }
8455 : :
8456 : : /*
8457 : : * get_simple_binary_op_name
8458 : : *
8459 : : * helper function for isSimpleNode
8460 : : * will return single char binary operator name, or NULL if it's not
8461 : : */
8462 : : static const char *
7555 bruce@momjian.us 8463 : 57 : get_simple_binary_op_name(OpExpr *expr)
8464 : : {
7564 tgl@sss.pgh.pa.us 8465 : 57 : List *args = expr->args;
8466 : :
7259 neilc@samurai.com 8467 [ + - ]: 57 : if (list_length(args) == 2)
8468 : : {
8469 : : /* binary operator */
7263 8470 : 57 : Node *arg1 = (Node *) linitial(args);
7564 tgl@sss.pgh.pa.us 8471 : 57 : Node *arg2 = (Node *) lsecond(args);
8472 : : const char *op;
8473 : :
8474 : 57 : op = generate_operator_name(expr->opno, exprType(arg1), exprType(arg2));
8475 [ + - ]: 57 : if (strlen(op) == 1)
7559 bruce@momjian.us 8476 : 57 : return op;
8477 : : }
7564 tgl@sss.pgh.pa.us 8478 :UBC 0 : return NULL;
8479 : : }
8480 : :
8481 : :
8482 : : /*
8483 : : * isSimpleNode - check if given node is simple (doesn't need parenthesizing)
8484 : : *
8485 : : * true : simple in the context of parent node's type
8486 : : * false : not simple
8487 : : */
8488 : : static bool
7564 tgl@sss.pgh.pa.us 8489 :CBC 2410 : isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
8490 : : {
7559 bruce@momjian.us 8491 [ - + ]: 2410 : if (!node)
7564 tgl@sss.pgh.pa.us 8492 :UBC 0 : return false;
8493 : :
7559 bruce@momjian.us 8494 [ + + - + :CBC 2410 : switch (nodeTag(node))
- - + - -
- + + + -
+ ]
8495 : : {
7564 tgl@sss.pgh.pa.us 8496 : 2026 : case T_Var:
8497 : : case T_Const:
8498 : : case T_Param:
8499 : : case T_CoerceToDomainValue:
8500 : : case T_SetToDefault:
8501 : : case T_CurrentOfExpr:
8502 : : /* single words: always simple */
8503 : 2026 : return true;
8504 : :
1899 alvherre@alvh.no-ip. 8505 : 201 : case T_SubscriptingRef:
8506 : : case T_ArrayExpr:
8507 : : case T_RowExpr:
8508 : : case T_CoalesceExpr:
8509 : : case T_MinMaxExpr:
8510 : : case T_SQLValueFunction:
8511 : : case T_XmlExpr:
8512 : : case T_NextValueExpr:
8513 : : case T_NullIfExpr:
8514 : : case T_Aggref:
8515 : : case T_GroupingFunc:
8516 : : case T_WindowFunc:
8517 : : case T_MergeSupportFunc:
8518 : : case T_FuncExpr:
8519 : : case T_JsonConstructorExpr:
8520 : : case T_JsonExpr:
8521 : : /* function-like: name(..) or name[..] */
7564 tgl@sss.pgh.pa.us 8522 : 201 : return true;
8523 : :
8524 : : /* CASE keywords act as parentheses */
7564 tgl@sss.pgh.pa.us 8525 :UBC 0 : case T_CaseExpr:
8526 : 0 : return true;
8527 : :
7564 tgl@sss.pgh.pa.us 8528 :CBC 27 : case T_FieldSelect:
8529 : :
8530 : : /*
8531 : : * appears simple since . has top precedence, unless parent is
8532 : : * T_FieldSelect itself!
8533 : : */
949 michael@paquier.xyz 8534 : 27 : return !IsA(parentNode, FieldSelect);
8535 : :
7249 tgl@sss.pgh.pa.us 8536 :UBC 0 : case T_FieldStore:
8537 : :
8538 : : /*
8539 : : * treat like FieldSelect (probably doesn't matter)
8540 : : */
949 michael@paquier.xyz 8541 : 0 : return !IsA(parentNode, FieldStore);
8542 : :
7564 tgl@sss.pgh.pa.us 8543 : 0 : case T_CoerceToDomain:
8544 : : /* maybe simple, check args */
7559 bruce@momjian.us 8545 : 0 : return isSimpleNode((Node *) ((CoerceToDomain *) node)->arg,
8546 : : node, prettyFlags);
7564 tgl@sss.pgh.pa.us 8547 :CBC 3 : case T_RelabelType:
7559 bruce@momjian.us 8548 : 3 : return isSimpleNode((Node *) ((RelabelType *) node)->arg,
8549 : : node, prettyFlags);
6158 tgl@sss.pgh.pa.us 8550 :UBC 0 : case T_CoerceViaIO:
8551 : 0 : return isSimpleNode((Node *) ((CoerceViaIO *) node)->arg,
8552 : : node, prettyFlags);
6228 8553 : 0 : case T_ArrayCoerceExpr:
8554 : 0 : return isSimpleNode((Node *) ((ArrayCoerceExpr *) node)->arg,
8555 : : node, prettyFlags);
7064 8556 : 0 : case T_ConvertRowtypeExpr:
8557 : 0 : return isSimpleNode((Node *) ((ConvertRowtypeExpr *) node)->arg,
8558 : : node, prettyFlags);
8559 : :
7564 tgl@sss.pgh.pa.us 8560 :CBC 126 : case T_OpExpr:
8561 : : {
8562 : : /* depends on parent node type; needs further checking */
7559 bruce@momjian.us 8563 [ + - + + ]: 126 : if (prettyFlags & PRETTYFLAG_PAREN && IsA(parentNode, OpExpr))
8564 : : {
8565 : : const char *op;
8566 : : const char *parentOp;
8567 : : bool is_lopriop;
8568 : : bool is_hipriop;
8569 : : bool is_lopriparent;
8570 : : bool is_hipriparent;
8571 : :
8572 : 30 : op = get_simple_binary_op_name((OpExpr *) node);
8573 [ - + ]: 30 : if (!op)
7559 bruce@momjian.us 8574 :UBC 0 : return false;
8575 : :
8576 : : /* We know only the basic operators + - and * / % */
7559 bruce@momjian.us 8577 :CBC 30 : is_lopriop = (strchr("+-", *op) != NULL);
8578 : 30 : is_hipriop = (strchr("*/%", *op) != NULL);
8579 [ + + + + ]: 30 : if (!(is_lopriop || is_hipriop))
8580 : 3 : return false;
8581 : :
8582 : 27 : parentOp = get_simple_binary_op_name((OpExpr *) parentNode);
8583 [ - + ]: 27 : if (!parentOp)
7559 bruce@momjian.us 8584 :UBC 0 : return false;
8585 : :
7559 bruce@momjian.us 8586 :CBC 27 : is_lopriparent = (strchr("+-", *parentOp) != NULL);
8587 : 27 : is_hipriparent = (strchr("*/%", *parentOp) != NULL);
8588 [ + + - + ]: 27 : if (!(is_lopriparent || is_hipriparent))
7559 bruce@momjian.us 8589 :UBC 0 : return false;
8590 : :
7559 bruce@momjian.us 8591 [ + + + - ]:CBC 27 : if (is_hipriop && is_lopriparent)
8592 : 6 : return true; /* op binds tighter than parent */
8593 : :
8594 [ + - + + ]: 21 : if (is_lopriop && is_hipriparent)
8595 : 15 : return false;
8596 : :
8597 : : /*
8598 : : * Operators are same priority --- can skip parens only if
8599 : : * we have (a - b) - c, not a - (b - c).
8600 : : */
7263 neilc@samurai.com 8601 [ + + ]: 6 : if (node == (Node *) linitial(((OpExpr *) parentNode)->args))
7559 bruce@momjian.us 8602 : 3 : return true;
8603 : :
8604 : 3 : return false;
8605 : : }
8606 : : /* else do the same stuff as for T_SubLink et al. */
8607 : : }
8608 : : /* FALLTHROUGH */
8609 : :
8610 : : case T_SubLink:
8611 : : case T_NullTest:
8612 : : case T_BooleanTest:
8613 : : case T_DistinctExpr:
8614 : : case T_JsonIsPredicate:
7564 tgl@sss.pgh.pa.us 8615 [ + + + ]: 111 : switch (nodeTag(parentNode))
8616 : : {
8617 : 15 : case T_FuncExpr:
8618 : : {
8619 : : /* special handling for casts and COERCE_SQL_SYNTAX */
7559 bruce@momjian.us 8620 : 15 : CoercionForm type = ((FuncExpr *) parentNode)->funcformat;
8621 : :
8622 [ + + + + ]: 15 : if (type == COERCE_EXPLICIT_CAST ||
500 tgl@sss.pgh.pa.us 8623 [ + - ]: 3 : type == COERCE_IMPLICIT_CAST ||
8624 : : type == COERCE_SQL_SYNTAX)
7559 bruce@momjian.us 8625 : 15 : return false;
7559 bruce@momjian.us 8626 :UBC 0 : return true; /* own parentheses */
8627 : : }
2489 tgl@sss.pgh.pa.us 8628 :CBC 81 : case T_BoolExpr: /* lower precedence */
8629 : : case T_SubscriptingRef: /* other separators */
8630 : : case T_ArrayExpr: /* other separators */
8631 : : case T_RowExpr: /* other separators */
8632 : : case T_CoalesceExpr: /* own parentheses */
8633 : : case T_MinMaxExpr: /* own parentheses */
8634 : : case T_XmlExpr: /* own parentheses */
8635 : : case T_NullIfExpr: /* other separators */
8636 : : case T_Aggref: /* own parentheses */
8637 : : case T_GroupingFunc: /* own parentheses */
8638 : : case T_WindowFunc: /* own parentheses */
8639 : : case T_CaseExpr: /* other separators */
7564 8640 : 81 : return true;
8641 : 15 : default:
8642 : 15 : return false;
8643 : : }
8644 : :
8645 : 9 : case T_BoolExpr:
8646 [ + - - - ]: 9 : switch (nodeTag(parentNode))
8647 : : {
8648 : 9 : case T_BoolExpr:
8649 [ + - ]: 9 : if (prettyFlags & PRETTYFLAG_PAREN)
8650 : : {
8651 : : BoolExprType type;
8652 : : BoolExprType parentType;
8653 : :
7559 bruce@momjian.us 8654 : 9 : type = ((BoolExpr *) node)->boolop;
8655 [ + + - ]: 9 : parentType = ((BoolExpr *) parentNode)->boolop;
8656 : : switch (type)
8657 : : {
7564 tgl@sss.pgh.pa.us 8658 : 6 : case NOT_EXPR:
8659 : : case AND_EXPR:
8660 [ + + + - ]: 6 : if (parentType == AND_EXPR || parentType == OR_EXPR)
8661 : 6 : return true;
7564 tgl@sss.pgh.pa.us 8662 :UBC 0 : break;
7564 tgl@sss.pgh.pa.us 8663 :CBC 3 : case OR_EXPR:
8664 [ - + ]: 3 : if (parentType == OR_EXPR)
7564 tgl@sss.pgh.pa.us 8665 :UBC 0 : return true;
7564 tgl@sss.pgh.pa.us 8666 :CBC 3 : break;
8667 : : }
8668 : : }
8669 : 3 : return false;
7564 tgl@sss.pgh.pa.us 8670 :UBC 0 : case T_FuncExpr:
8671 : : {
8672 : : /* special handling for casts and COERCE_SQL_SYNTAX */
7559 bruce@momjian.us 8673 : 0 : CoercionForm type = ((FuncExpr *) parentNode)->funcformat;
8674 : :
8675 [ # # # # ]: 0 : if (type == COERCE_EXPLICIT_CAST ||
500 tgl@sss.pgh.pa.us 8676 [ # # ]: 0 : type == COERCE_IMPLICIT_CAST ||
8677 : : type == COERCE_SQL_SYNTAX)
7559 bruce@momjian.us 8678 : 0 : return false;
8679 : 0 : return true; /* own parentheses */
8680 : : }
1899 alvherre@alvh.no-ip. 8681 : 0 : case T_SubscriptingRef: /* other separators */
8682 : : case T_ArrayExpr: /* other separators */
8683 : : case T_RowExpr: /* other separators */
8684 : : case T_CoalesceExpr: /* own parentheses */
8685 : : case T_MinMaxExpr: /* own parentheses */
8686 : : case T_XmlExpr: /* own parentheses */
8687 : : case T_NullIfExpr: /* other separators */
8688 : : case T_Aggref: /* own parentheses */
8689 : : case T_GroupingFunc: /* own parentheses */
8690 : : case T_WindowFunc: /* own parentheses */
8691 : : case T_CaseExpr: /* other separators */
8692 : : case T_JsonExpr: /* own parentheses */
7564 tgl@sss.pgh.pa.us 8693 : 0 : return true;
8694 : 0 : default:
8695 : 0 : return false;
8696 : : }
8697 : :
382 alvherre@alvh.no-ip. 8698 : 0 : case T_JsonValueExpr:
8699 : : /* maybe simple, check args */
8700 : 0 : return isSimpleNode((Node *) ((JsonValueExpr *) node)->raw_expr,
8701 : : node, prettyFlags);
8702 : :
7564 tgl@sss.pgh.pa.us 8703 :GBC 3 : default:
8704 : 3 : break;
8705 : : }
8706 : : /* those we don't know: in dubio complexo */
7559 bruce@momjian.us 8707 : 3 : return false;
8708 : : }
8709 : :
8710 : :
8711 : : /*
8712 : : * appendContextKeyword - append a keyword to buffer
8713 : : *
8714 : : * If prettyPrint is enabled, perform a line break, and adjust indentation.
8715 : : * Otherwise, just append the keyword.
8716 : : */
8717 : : static void
7564 tgl@sss.pgh.pa.us 8718 :CBC 11372 : appendContextKeyword(deparse_context *context, const char *str,
8719 : : int indentBefore, int indentAfter, int indentPlus)
8720 : : {
3807 8721 : 11372 : StringInfo buf = context->buf;
8722 : :
7559 bruce@momjian.us 8723 [ + + ]: 11372 : if (PRETTY_INDENT(context))
8724 : : {
8725 : : int indentAmount;
8726 : :
7564 tgl@sss.pgh.pa.us 8727 : 10986 : context->indentLevel += indentBefore;
8728 : :
8729 : : /* remove any trailing spaces currently in the buffer ... */
3807 8730 : 10986 : removeStringInfoSpaces(buf);
8731 : : /* ... then add a newline and some spaces */
8732 : 10986 : appendStringInfoChar(buf, '\n');
8733 : :
3637 8734 [ + - ]: 10986 : if (context->indentLevel < PRETTYINDENT_LIMIT)
8735 : 10986 : indentAmount = Max(context->indentLevel, 0) + indentPlus;
8736 : : else
8737 : : {
8738 : : /*
8739 : : * If we're indented more than PRETTYINDENT_LIMIT characters, try
8740 : : * to conserve horizontal space by reducing the per-level
8741 : : * indentation. For best results the scale factor here should
8742 : : * divide all the indent amounts that get added to indentLevel
8743 : : * (PRETTYINDENT_STD, etc). It's important that the indentation
8744 : : * not grow unboundedly, else deeply-nested trees use O(N^2)
8745 : : * whitespace; so we also wrap modulo PRETTYINDENT_LIMIT.
8746 : : */
3637 tgl@sss.pgh.pa.us 8747 :UBC 0 : indentAmount = PRETTYINDENT_LIMIT +
8748 : 0 : (context->indentLevel - PRETTYINDENT_LIMIT) /
8749 : : (PRETTYINDENT_STD / 2);
8750 : 0 : indentAmount %= PRETTYINDENT_LIMIT;
8751 : : /* scale/wrap logic affects indentLevel, but not indentPlus */
8752 : 0 : indentAmount += indentPlus;
8753 : : }
3637 tgl@sss.pgh.pa.us 8754 :CBC 10986 : appendStringInfoSpaces(buf, indentAmount);
8755 : :
3807 8756 : 10986 : appendStringInfoString(buf, str);
8757 : :
7564 8758 : 10986 : context->indentLevel += indentAfter;
8759 [ - + ]: 10986 : if (context->indentLevel < 0)
7564 tgl@sss.pgh.pa.us 8760 :UBC 0 : context->indentLevel = 0;
8761 : : }
8762 : : else
3807 tgl@sss.pgh.pa.us 8763 :CBC 386 : appendStringInfoString(buf, str);
7564 8764 : 11372 : }
8765 : :
8766 : : /*
8767 : : * removeStringInfoSpaces - delete trailing spaces from a buffer.
8768 : : *
8769 : : * Possibly this should move to stringinfo.c at some point.
8770 : : */
8771 : : static void
3807 8772 : 11145 : removeStringInfoSpaces(StringInfo str)
8773 : : {
8774 [ + + + + ]: 17196 : while (str->len > 0 && str->data[str->len - 1] == ' ')
8775 : 6051 : str->data[--(str->len)] = '\0';
8776 : 11145 : }
8777 : :
8778 : :
8779 : : /*
8780 : : * get_rule_expr_paren - deparse expr using get_rule_expr,
8781 : : * embracing the string with parentheses if necessary for prettyPrint.
8782 : : *
8783 : : * Never embrace if prettyFlags=0, because it's done in the calling node.
8784 : : *
8785 : : * Any node that does *not* embrace its argument node by sql syntax (with
8786 : : * parentheses, non-operator keywords like CASE/WHEN/ON, or comma etc) should
8787 : : * use get_rule_expr_paren instead of get_rule_expr so parentheses can be
8788 : : * added.
8789 : : */
8790 : : static void
7559 bruce@momjian.us 8791 : 72693 : get_rule_expr_paren(Node *node, deparse_context *context,
8792 : : bool showimplicit, Node *parentNode)
8793 : : {
8794 : : bool need_paren;
8795 : :
7564 tgl@sss.pgh.pa.us 8796 [ + + ]: 75100 : need_paren = PRETTY_PAREN(context) &&
8797 [ + + ]: 2407 : !isSimpleNode(node, parentNode, context->prettyFlags);
8798 : :
8799 [ + + ]: 72693 : if (need_paren)
7559 bruce@momjian.us 8800 : 57 : appendStringInfoChar(context->buf, '(');
8801 : :
7564 tgl@sss.pgh.pa.us 8802 : 72693 : get_rule_expr(node, context, showimplicit);
8803 : :
8804 [ + + ]: 72693 : if (need_paren)
7559 bruce@momjian.us 8805 : 57 : appendStringInfoChar(context->buf, ')');
7564 tgl@sss.pgh.pa.us 8806 : 72693 : }
8807 : :
8808 : : static void
24 amitlan@postgresql.o 8809 :GNC 39 : get_json_behavior(JsonBehavior *behavior, deparse_context *context,
8810 : : const char *on)
8811 : : {
8812 : : /*
8813 : : * The order of array elements must correspond to the order of
8814 : : * JsonBehaviorType members.
8815 : : */
8816 : 39 : const char *behavior_names[] =
8817 : : {
8818 : : " NULL",
8819 : : " ERROR",
8820 : : " EMPTY",
8821 : : " TRUE",
8822 : : " FALSE",
8823 : : " UNKNOWN",
8824 : : " EMPTY ARRAY",
8825 : : " EMPTY OBJECT",
8826 : : " DEFAULT "
8827 : : };
8828 : :
8829 [ + - - + ]: 39 : if ((int) behavior->btype < 0 || behavior->btype >= lengthof(behavior_names))
24 amitlan@postgresql.o 8830 [ # # ]:UNC 0 : elog(ERROR, "invalid json behavior type: %d", behavior->btype);
8831 : :
24 amitlan@postgresql.o 8832 :GNC 39 : appendStringInfoString(context->buf, behavior_names[behavior->btype]);
8833 : :
8834 [ + + ]: 39 : if (behavior->btype == JSON_BEHAVIOR_DEFAULT)
8835 : 9 : get_rule_expr(behavior->expr, context, false);
8836 : :
8837 : 39 : appendStringInfo(context->buf, " ON %s", on);
8838 : 39 : }
8839 : :
8840 : : /*
8841 : : * get_json_expr_options
8842 : : *
8843 : : * Parse back common options for JSON_QUERY, JSON_VALUE, JSON_EXISTS and
8844 : : * JSON_TABLE columns.
8845 : : */
8846 : : static void
8847 : 216 : get_json_expr_options(JsonExpr *jsexpr, deparse_context *context,
8848 : : JsonBehaviorType default_behavior)
8849 : : {
8850 [ + + ]: 216 : if (jsexpr->op == JSON_QUERY_OP)
8851 : : {
8852 [ + + ]: 105 : if (jsexpr->wrapper == JSW_CONDITIONAL)
4 drowley@postgresql.o 8853 : 6 : appendStringInfoString(context->buf, " WITH CONDITIONAL WRAPPER");
24 amitlan@postgresql.o 8854 [ + + ]: 99 : else if (jsexpr->wrapper == JSW_UNCONDITIONAL)
4 drowley@postgresql.o 8855 : 15 : appendStringInfoString(context->buf, " WITH UNCONDITIONAL WRAPPER");
8856 : : /* The default */
6 amitlan@postgresql.o 8857 [ + + + - ]: 84 : else if (jsexpr->wrapper == JSW_NONE || jsexpr->wrapper == JSW_UNSPEC)
4 drowley@postgresql.o 8858 : 84 : appendStringInfoString(context->buf, " WITHOUT WRAPPER");
8859 : :
24 amitlan@postgresql.o 8860 [ + + ]: 105 : if (jsexpr->omit_quotes)
4 drowley@postgresql.o 8861 : 21 : appendStringInfoString(context->buf, " OMIT QUOTES");
8862 : : /* The default */
8863 : : else
8864 : 84 : appendStringInfoString(context->buf, " KEEP QUOTES");
8865 : : }
8866 : :
24 amitlan@postgresql.o 8867 [ + + + - ]: 216 : if (jsexpr->on_empty && jsexpr->on_empty->btype != default_behavior)
8868 : 15 : get_json_behavior(jsexpr->on_empty, context, "EMPTY");
8869 : :
8870 [ + - + + ]: 216 : if (jsexpr->on_error && jsexpr->on_error->btype != default_behavior)
8871 : 24 : get_json_behavior(jsexpr->on_error, context, "ERROR");
8872 : 216 : }
8873 : :
8874 : : /* ----------
8875 : : * get_rule_expr - Parse back an expression
8876 : : *
8877 : : * Note: showimplicit determines whether we display any implicit cast that
8878 : : * is present at the top of the expression tree. It is a passed argument,
8879 : : * not a field of the context struct, because we change the value as we
8880 : : * recurse down into the expression. In general we suppress implicit casts
8881 : : * when the result type is known with certainty (eg, the arguments of an
8882 : : * OR must be boolean). We display implicit casts for arguments of functions
8883 : : * and operators, since this is needed to be certain that the same function
8884 : : * or operator will be chosen when the expression is re-parsed.
8885 : : * ----------
8886 : : */
8887 : : static void
7878 tgl@sss.pgh.pa.us 8888 :CBC 145842 : get_rule_expr(Node *node, deparse_context *context,
8889 : : bool showimplicit)
8890 : : {
8960 8891 : 145842 : StringInfo buf = context->buf;
8892 : :
9357 bruce@momjian.us 8893 [ + + ]: 145842 : if (node == NULL)
8961 tgl@sss.pgh.pa.us 8894 : 48 : return;
8895 : :
8896 : : /* Guard against excessively long or deeply-nested queries */
3637 8897 [ - + ]: 145794 : CHECK_FOR_INTERRUPTS();
8898 : 145794 : check_stack_depth();
8899 : :
8900 : : /*
8901 : : * Each level of get_rule_expr must emit an indivisible term
8902 : : * (parenthesized if necessary) to ensure result is reparsed into the same
8903 : : * expression tree. The only exception is that when the input is a List,
8904 : : * we emit the component items comma-separated with no surrounding
8905 : : * decoration; this is convenient for most callers.
8906 : : */
9357 bruce@momjian.us 8907 [ + + + + : 145794 : switch (nodeTag(node))
+ + + + +
+ + + + +
+ + + - +
+ + + + +
+ + - + +
+ + + + +
+ + + + +
+ - + + +
+ + + + +
- ]
8908 : : {
8996 tgl@sss.pgh.pa.us 8909 : 66519 : case T_Var:
4370 8910 : 66519 : (void) get_variable((Var *) node, 0, false, context);
9357 bruce@momjian.us 8911 : 66519 : break;
8912 : :
7794 tgl@sss.pgh.pa.us 8913 : 27494 : case T_Const:
5943 8914 : 27494 : get_const_expr((Const *) node, context, 0);
7794 8915 : 27494 : break;
8916 : :
8917 : 3093 : case T_Param:
5024 8918 : 3093 : get_parameter((Param *) node, context);
9357 bruce@momjian.us 8919 : 3093 : break;
8920 : :
8996 tgl@sss.pgh.pa.us 8921 : 792 : case T_Aggref:
2909 rhaas@postgresql.org 8922 : 792 : get_agg_expr((Aggref *) node, context, (Aggref *) node);
8996 tgl@sss.pgh.pa.us 8923 : 792 : break;
8924 : :
3256 andres@anarazel.de 8925 : 26 : case T_GroupingFunc:
8926 : : {
8927 : 26 : GroupingFunc *gexpr = (GroupingFunc *) node;
8928 : :
8929 : 26 : appendStringInfoString(buf, "GROUPING(");
8930 : 26 : get_rule_expr((Node *) gexpr->args, context, true);
8931 : 26 : appendStringInfoChar(buf, ')');
8932 : : }
8933 : 26 : break;
8934 : :
5586 tgl@sss.pgh.pa.us 8935 : 120 : case T_WindowFunc:
8936 : 120 : get_windowfunc_expr((WindowFunc *) node, context);
8937 : 120 : break;
8938 : :
28 dean.a.rasheed@gmail 8939 :GNC 3 : case T_MergeSupportFunc:
8940 : 3 : appendStringInfoString(buf, "MERGE_ACTION()");
8941 : 3 : break;
8942 : :
1899 alvherre@alvh.no-ip. 8943 :CBC 134 : case T_SubscriptingRef:
8944 : : {
8945 : 134 : SubscriptingRef *sbsref = (SubscriptingRef *) node;
8946 : : bool need_parens;
8947 : :
8948 : : /*
8949 : : * If the argument is a CaseTestExpr, we must be inside a
8950 : : * FieldStore, ie, we are assigning to an element of an array
8951 : : * within a composite column. Since we already punted on
8952 : : * displaying the FieldStore's target information, just punt
8953 : : * here too, and display only the assignment source
8954 : : * expression.
8955 : : */
8956 [ - + ]: 134 : if (IsA(sbsref->refexpr, CaseTestExpr))
8957 : : {
1899 alvherre@alvh.no-ip. 8958 [ # # ]:UBC 0 : Assert(sbsref->refassgnexpr);
8959 : 0 : get_rule_expr((Node *) sbsref->refassgnexpr,
8960 : : context, showimplicit);
5169 tgl@sss.pgh.pa.us 8961 : 0 : break;
8962 : : }
8963 : :
8964 : : /*
8965 : : * Parenthesize the argument unless it's a simple Var or a
8966 : : * FieldSelect. (In particular, if it's another
8967 : : * SubscriptingRef, we *must* parenthesize to avoid
8968 : : * confusion.)
8969 : : */
1899 alvherre@alvh.no-ip. 8970 [ + + ]:CBC 201 : need_parens = !IsA(sbsref->refexpr, Var) &&
8971 [ + + ]: 67 : !IsA(sbsref->refexpr, FieldSelect);
7677 tgl@sss.pgh.pa.us 8972 [ + + ]: 134 : if (need_parens)
8973 : 47 : appendStringInfoChar(buf, '(');
1899 alvherre@alvh.no-ip. 8974 : 134 : get_rule_expr((Node *) sbsref->refexpr, context, showimplicit);
7677 tgl@sss.pgh.pa.us 8975 [ + + ]: 134 : if (need_parens)
8976 : 47 : appendStringInfoChar(buf, ')');
8977 : :
8978 : : /*
8979 : : * If there's a refassgnexpr, we want to print the node in the
8980 : : * format "container[subscripts] := refassgnexpr". This is
8981 : : * not legal SQL, so decompilation of INSERT or UPDATE
8982 : : * statements should always use processIndirection as part of
8983 : : * the statement-level syntax. We should only see this when
8984 : : * EXPLAIN tries to print the targetlist of a plan resulting
8985 : : * from such a statement.
8986 : : */
1899 alvherre@alvh.no-ip. 8987 [ + + ]: 134 : if (sbsref->refassgnexpr)
8988 : : {
8989 : : Node *refassgnexpr;
8990 : :
8991 : : /*
8992 : : * Use processIndirection to print this node's subscripts
8993 : : * as well as any additional field selections or
8994 : : * subscripting in immediate descendants. It returns the
8995 : : * RHS expr that is actually being "assigned".
8996 : : */
2811 tgl@sss.pgh.pa.us 8997 : 6 : refassgnexpr = processIndirection(node, context);
5169 8998 : 6 : appendStringInfoString(buf, " := ");
8999 : 6 : get_rule_expr(refassgnexpr, context, showimplicit);
9000 : : }
9001 : : else
9002 : : {
9003 : : /* Just an ordinary container fetch, so print subscripts */
1899 alvherre@alvh.no-ip. 9004 : 128 : printSubscripts(sbsref, context);
9005 : : }
9006 : : }
7794 tgl@sss.pgh.pa.us 9007 : 134 : break;
9008 : :
9009 : 5343 : case T_FuncExpr:
9010 : 5343 : get_func_expr((FuncExpr *) node, context, showimplicit);
9011 : 5343 : break;
9012 : :
5302 9013 : 9 : case T_NamedArgExpr:
9014 : : {
9015 : 9 : NamedArgExpr *na = (NamedArgExpr *) node;
9016 : :
3271 rhaas@postgresql.org 9017 : 9 : appendStringInfo(buf, "%s => ", quote_identifier(na->name));
5302 tgl@sss.pgh.pa.us 9018 : 9 : get_rule_expr((Node *) na->arg, context, showimplicit);
9019 : : }
9020 : 9 : break;
9021 : :
7794 9022 : 27098 : case T_OpExpr:
9023 : 27098 : get_oper_expr((OpExpr *) node, context);
9024 : 27098 : break;
9025 : :
9026 : 9 : case T_DistinctExpr:
9027 : : {
9028 : 9 : DistinctExpr *expr = (DistinctExpr *) node;
9029 : 9 : List *args = expr->args;
7263 neilc@samurai.com 9030 : 9 : Node *arg1 = (Node *) linitial(args);
7595 tgl@sss.pgh.pa.us 9031 : 9 : Node *arg2 = (Node *) lsecond(args);
9032 : :
7564 9033 [ + + ]: 9 : if (!PRETTY_PAREN(context))
7559 bruce@momjian.us 9034 : 6 : appendStringInfoChar(buf, '(');
7564 tgl@sss.pgh.pa.us 9035 : 9 : get_rule_expr_paren(arg1, context, true, node);
3818 rhaas@postgresql.org 9036 : 9 : appendStringInfoString(buf, " IS DISTINCT FROM ");
7564 tgl@sss.pgh.pa.us 9037 : 9 : get_rule_expr_paren(arg2, context, true, node);
9038 [ + + ]: 9 : if (!PRETTY_PAREN(context))
7559 bruce@momjian.us 9039 : 6 : appendStringInfoChar(buf, ')');
9040 : : }
7595 tgl@sss.pgh.pa.us 9041 : 9 : break;
9042 : :
4775 9043 : 10 : case T_NullIfExpr:
9044 : : {
9045 : 10 : NullIfExpr *nullifexpr = (NullIfExpr *) node;
9046 : :
3818 rhaas@postgresql.org 9047 : 10 : appendStringInfoString(buf, "NULLIF(");
4775 tgl@sss.pgh.pa.us 9048 : 10 : get_rule_expr((Node *) nullifexpr->args, context, true);
9049 : 10 : appendStringInfoChar(buf, ')');
9050 : : }
9051 : 10 : break;
9052 : :
7595 9053 : 1304 : case T_ScalarArrayOpExpr:
9054 : : {
9055 : 1304 : ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
9056 : 1304 : List *args = expr->args;
7263 neilc@samurai.com 9057 : 1304 : Node *arg1 = (Node *) linitial(args);
7595 tgl@sss.pgh.pa.us 9058 : 1304 : Node *arg2 = (Node *) lsecond(args);
9059 : :
7564 9060 [ + + ]: 1304 : if (!PRETTY_PAREN(context))
7559 bruce@momjian.us 9061 : 1298 : appendStringInfoChar(buf, '(');
7564 tgl@sss.pgh.pa.us 9062 : 1304 : get_rule_expr_paren(arg1, context, true, node);
7595 9063 : 1304 : appendStringInfo(buf, " %s %s (",
9064 : : generate_operator_name(expr->opno,
9065 : : exprType(arg1),
9066 : : get_base_element_type(exprType(arg2))),
9067 [ + + ]: 1304 : expr->useOr ? "ANY" : "ALL");
7564 9068 : 1304 : get_rule_expr_paren(arg2, context, true, node);
9069 : :
9070 : : /*
9071 : : * There's inherent ambiguity in "x op ANY/ALL (y)" when y is
9072 : : * a bare sub-SELECT. Since we're here, the sub-SELECT must
9073 : : * be meant as a scalar sub-SELECT yielding an array value to
9074 : : * be used in ScalarArrayOpExpr; but the grammar will
9075 : : * preferentially interpret such a construct as an ANY/ALL
9076 : : * SubLink. To prevent misparsing the output that way, insert
9077 : : * a dummy coercion (which will be stripped by parse analysis,
9078 : : * so no inefficiency is added in dump and reload). This is
9079 : : * indeed most likely what the user wrote to get the construct
9080 : : * accepted in the first place.
9081 : : */
2915 9082 [ + + ]: 1304 : if (IsA(arg2, SubLink) &&
9083 [ + - ]: 3 : ((SubLink *) arg2)->subLinkType == EXPR_SUBLINK)
9084 : 3 : appendStringInfo(buf, "::%s",
9085 : : format_type_with_typemod(exprType(arg2),
9086 : : exprTypmod(arg2)));
7564 9087 : 1304 : appendStringInfoChar(buf, ')');
9088 [ + + ]: 1304 : if (!PRETTY_PAREN(context))
7559 bruce@momjian.us 9089 : 1298 : appendStringInfoChar(buf, ')');
9090 : : }
9357 9091 : 1304 : break;
9092 : :
7794 tgl@sss.pgh.pa.us 9093 : 5115 : case T_BoolExpr:
9094 : : {
9095 : 5115 : BoolExpr *expr = (BoolExpr *) node;
7263 neilc@samurai.com 9096 : 5115 : Node *first_arg = linitial(expr->args);
9097 : : ListCell *arg;
9098 : :
7794 tgl@sss.pgh.pa.us 9099 [ + + + - ]: 5115 : switch (expr->boolop)
9100 : : {
9101 : 4178 : case AND_EXPR:
7564 9102 [ + + ]: 4178 : if (!PRETTY_PAREN(context))
7559 bruce@momjian.us 9103 : 4145 : appendStringInfoChar(buf, '(');
7263 neilc@samurai.com 9104 : 4178 : get_rule_expr_paren(first_arg, context,
9105 : : false, node);
1294 tgl@sss.pgh.pa.us 9106 [ + - + + : 9562 : for_each_from(arg, expr->args, 1)
+ + ]
9107 : : {
3818 rhaas@postgresql.org 9108 : 5384 : appendStringInfoString(buf, " AND ");
7263 neilc@samurai.com 9109 : 5384 : get_rule_expr_paren((Node *) lfirst(arg), context,
9110 : : false, node);
9111 : : }
7564 tgl@sss.pgh.pa.us 9112 [ + + ]: 4178 : if (!PRETTY_PAREN(context))
7559 bruce@momjian.us 9113 : 4145 : appendStringInfoChar(buf, ')');
7794 tgl@sss.pgh.pa.us 9114 : 4178 : break;
9115 : :
9116 : 777 : case OR_EXPR:
7564 9117 [ + + ]: 777 : if (!PRETTY_PAREN(context))
7559 bruce@momjian.us 9118 : 771 : appendStringInfoChar(buf, '(');
7263 neilc@samurai.com 9119 : 777 : get_rule_expr_paren(first_arg, context,
9120 : : false, node);
1294 tgl@sss.pgh.pa.us 9121 [ + - + + : 1840 : for_each_from(arg, expr->args, 1)
+ + ]
9122 : : {
3818 rhaas@postgresql.org 9123 : 1063 : appendStringInfoString(buf, " OR ");
7263 neilc@samurai.com 9124 : 1063 : get_rule_expr_paren((Node *) lfirst(arg), context,
9125 : : false, node);
9126 : : }
7564 tgl@sss.pgh.pa.us 9127 [ + + ]: 777 : if (!PRETTY_PAREN(context))
7559 bruce@momjian.us 9128 : 771 : appendStringInfoChar(buf, ')');
7794 tgl@sss.pgh.pa.us 9129 : 777 : break;
9130 : :
9131 : 160 : case NOT_EXPR:
7564 9132 [ + + ]: 160 : if (!PRETTY_PAREN(context))
7559 bruce@momjian.us 9133 : 154 : appendStringInfoChar(buf, '(');
3818 rhaas@postgresql.org 9134 : 160 : appendStringInfoString(buf, "NOT ");
7263 neilc@samurai.com 9135 : 160 : get_rule_expr_paren(first_arg, context,
9136 : : false, node);
7564 tgl@sss.pgh.pa.us 9137 [ + + ]: 160 : if (!PRETTY_PAREN(context))
7559 bruce@momjian.us 9138 : 154 : appendStringInfoChar(buf, ')');
7794 tgl@sss.pgh.pa.us 9139 : 160 : break;
9140 : :
7794 tgl@sss.pgh.pa.us 9141 :UBC 0 : default:
7567 9142 [ # # ]: 0 : elog(ERROR, "unrecognized boolop: %d",
9143 : : (int) expr->boolop);
9144 : : }
9145 : : }
7794 tgl@sss.pgh.pa.us 9146 :CBC 5115 : break;
9147 : :
9148 : 203 : case T_SubLink:
9149 : 203 : get_sublink_expr((SubLink *) node, context);
9150 : 203 : break;
9151 : :
7792 9152 : 248 : case T_SubPlan:
9153 : : {
5421 bruce@momjian.us 9154 : 248 : SubPlan *subplan = (SubPlan *) node;
9155 : :
9156 : : /*
9157 : : * We cannot see an already-planned subplan in rule deparsing,
9158 : : * only while EXPLAINing a query plan. We don't try to
9159 : : * reconstruct the original SQL, just reference the subplan
9160 : : * that appears elsewhere in EXPLAIN's result. It does seem
9161 : : * useful to show the subLinkType and testexpr (if any), and
9162 : : * we also note whether the subplan will be hashed.
9163 : : */
26 tgl@sss.pgh.pa.us 9164 [ + + + + :GNC 248 : switch (subplan->subLinkType)
+ + + -
- ]
9165 : : {
9166 : 39 : case EXISTS_SUBLINK:
9167 : 39 : appendStringInfoString(buf, "EXISTS(");
9168 [ - + ]: 39 : Assert(subplan->testexpr == NULL);
9169 : 39 : break;
9170 : 3 : case ALL_SUBLINK:
9171 : 3 : appendStringInfoString(buf, "(ALL ");
9172 [ - + ]: 3 : Assert(subplan->testexpr != NULL);
9173 : 3 : break;
9174 : 80 : case ANY_SUBLINK:
9175 : 80 : appendStringInfoString(buf, "(ANY ");
9176 [ - + ]: 80 : Assert(subplan->testexpr != NULL);
9177 : 80 : break;
9178 : 3 : case ROWCOMPARE_SUBLINK:
9179 : : /* Parenthesizing the testexpr seems sufficient */
9180 : 3 : appendStringInfoChar(buf, '(');
9181 [ - + ]: 3 : Assert(subplan->testexpr != NULL);
9182 : 3 : break;
9183 : 104 : case EXPR_SUBLINK:
9184 : : /* No need to decorate these subplan references */
9185 : 104 : appendStringInfoChar(buf, '(');
9186 [ - + ]: 104 : Assert(subplan->testexpr == NULL);
9187 : 104 : break;
9188 : 13 : case MULTIEXPR_SUBLINK:
9189 : : /* MULTIEXPR isn't executed in the normal way */
9190 : 13 : appendStringInfoString(buf, "(rescan ");
9191 [ - + ]: 13 : Assert(subplan->testexpr == NULL);
9192 : 13 : break;
9193 : 6 : case ARRAY_SUBLINK:
9194 : 6 : appendStringInfoString(buf, "ARRAY(");
9195 [ - + ]: 6 : Assert(subplan->testexpr == NULL);
9196 : 6 : break;
26 tgl@sss.pgh.pa.us 9197 :UNC 0 : case CTE_SUBLINK:
9198 : : /* This case is unreachable within expressions */
9199 : 0 : appendStringInfoString(buf, "CTE(");
9200 [ # # ]: 0 : Assert(subplan->testexpr == NULL);
9201 : 0 : break;
9202 : : }
9203 : :
26 tgl@sss.pgh.pa.us 9204 [ + + ]:GNC 248 : if (subplan->testexpr != NULL)
9205 : : {
9206 : : deparse_namespace *dpns;
9207 : :
9208 : : /*
9209 : : * Push SubPlan into ancestors list while deparsing
9210 : : * testexpr, so that we can handle PARAM_EXEC references
9211 : : * to the SubPlan's paramIds. (This makes it look like
9212 : : * the SubPlan is an "ancestor" of the current plan node,
9213 : : * which is a little weird, but it does no harm.) In this
9214 : : * path, we don't need to mention the SubPlan explicitly,
9215 : : * because the referencing Params will show its existence.
9216 : : */
9217 : 86 : dpns = (deparse_namespace *) linitial(context->namespaces);
9218 : 86 : dpns->ancestors = lcons(subplan, dpns->ancestors);
9219 : :
9220 : 86 : get_rule_expr(subplan->testexpr, context, showimplicit);
9221 : 86 : appendStringInfoChar(buf, ')');
9222 : :
9223 : 86 : dpns->ancestors = list_delete_first(dpns->ancestors);
9224 : : }
9225 : : else
9226 : : {
9227 : : /* No referencing Params, so show the SubPlan's name */
9228 [ - + ]: 162 : if (subplan->useHashTable)
26 tgl@sss.pgh.pa.us 9229 :UNC 0 : appendStringInfo(buf, "hashed %s)", subplan->plan_name);
9230 : : else
26 tgl@sss.pgh.pa.us 9231 :GNC 162 : appendStringInfo(buf, "%s)", subplan->plan_name);
9232 : : }
9233 : : }
7794 tgl@sss.pgh.pa.us 9234 :CBC 248 : break;
9235 : :
5714 tgl@sss.pgh.pa.us 9236 :UBC 0 : case T_AlternativeSubPlan:
9237 : : {
5488 9238 : 0 : AlternativeSubPlan *asplan = (AlternativeSubPlan *) node;
9239 : : ListCell *lc;
9240 : :
9241 : : /*
9242 : : * This case cannot be reached in normal usage, since no
9243 : : * AlternativeSubPlan can appear either in parsetrees or
9244 : : * finished plan trees. We keep it just in case somebody
9245 : : * wants to use this code to print planner data structures.
9246 : : */
3818 rhaas@postgresql.org 9247 : 0 : appendStringInfoString(buf, "(alternatives: ");
5488 tgl@sss.pgh.pa.us 9248 [ # # # # : 0 : foreach(lc, asplan->subplans)
# # ]
9249 : : {
2561 9250 : 0 : SubPlan *splan = lfirst_node(SubPlan, lc);
9251 : :
5488 9252 [ # # ]: 0 : if (splan->useHashTable)
9253 : 0 : appendStringInfo(buf, "hashed %s", splan->plan_name);
9254 : : else
3818 rhaas@postgresql.org 9255 : 0 : appendStringInfoString(buf, splan->plan_name);
1735 tgl@sss.pgh.pa.us 9256 [ # # ]: 0 : if (lnext(asplan->subplans, lc))
3818 rhaas@postgresql.org 9257 : 0 : appendStringInfoString(buf, " or ");
9258 : : }
9259 : 0 : appendStringInfoChar(buf, ')');
9260 : : }
5714 tgl@sss.pgh.pa.us 9261 : 0 : break;
9262 : :
8650 tgl@sss.pgh.pa.us 9263 :CBC 395 : case T_FieldSelect:
9264 : : {
9265 : 395 : FieldSelect *fselect = (FieldSelect *) node;
6893 9266 : 395 : Node *arg = (Node *) fselect->arg;
9267 : 395 : int fno = fselect->fieldnum;
9268 : : const char *fieldname;
9269 : : bool need_parens;
9270 : :
9271 : : /*
9272 : : * Parenthesize the argument unless it's an SubscriptingRef or
9273 : : * another FieldSelect. Note in particular that it would be
9274 : : * WRONG to not parenthesize a Var argument; simplicity is not
9275 : : * the issue here, having the right number of names is.
9276 : : */
1899 alvherre@alvh.no-ip. 9277 [ + + ]: 772 : need_parens = !IsA(arg, SubscriptingRef) &&
9278 [ + - ]: 377 : !IsA(arg, FieldSelect);
7249 tgl@sss.pgh.pa.us 9279 [ + + ]: 395 : if (need_parens)
9280 : 377 : appendStringInfoChar(buf, '(');
6893 9281 : 395 : get_rule_expr(arg, context, true);
7249 9282 [ + + ]: 395 : if (need_parens)
9283 : 377 : appendStringInfoChar(buf, ')');
9284 : :
9285 : : /*
9286 : : * Get and print the field name.
9287 : : */
6260 9288 : 395 : fieldname = get_name_for_var_field((Var *) arg, fno,
9289 : : 0, context);
7564 9290 : 395 : appendStringInfo(buf, ".%s", quote_identifier(fieldname));
9291 : : }
8650 9292 : 395 : break;
9293 : :
7249 9294 : 3 : case T_FieldStore:
9295 : : {
5169 9296 : 3 : FieldStore *fstore = (FieldStore *) node;
9297 : : bool need_parens;
9298 : :
9299 : : /*
9300 : : * There is no good way to represent a FieldStore as real SQL,
9301 : : * so decompilation of INSERT or UPDATE statements should
9302 : : * always use processIndirection as part of the
9303 : : * statement-level syntax. We should only get here when
9304 : : * EXPLAIN tries to print the targetlist of a plan resulting
9305 : : * from such a statement. The plan case is even harder than
9306 : : * ordinary rules would be, because the planner tries to
9307 : : * collapse multiple assignments to the same field or subfield
9308 : : * into one FieldStore; so we can see a list of target fields
9309 : : * not just one, and the arguments could be FieldStores
9310 : : * themselves. We don't bother to try to print the target
9311 : : * field names; we just print the source arguments, with a
9312 : : * ROW() around them if there's more than one. This isn't
9313 : : * terribly complete, but it's probably good enough for
9314 : : * EXPLAIN's purposes; especially since anything more would be
9315 : : * either hopelessly confusing or an even poorer
9316 : : * representation of what the plan is actually doing.
9317 : : */
9318 : 3 : need_parens = (list_length(fstore->newvals) != 1);
9319 [ + - ]: 3 : if (need_parens)
9320 : 3 : appendStringInfoString(buf, "ROW(");
9321 : 3 : get_rule_expr((Node *) fstore->newvals, context, showimplicit);
9322 [ + - ]: 3 : if (need_parens)
9323 : 3 : appendStringInfoChar(buf, ')');
9324 : : }
7249 9325 : 3 : break;
9326 : :
8820 9327 : 1146 : case T_RelabelType:
9328 : : {
9329 : 1146 : RelabelType *relabel = (RelabelType *) node;
7559 bruce@momjian.us 9330 : 1146 : Node *arg = (Node *) relabel->arg;
9331 : :
7878 tgl@sss.pgh.pa.us 9332 [ + + ]: 1146 : if (relabel->relabelformat == COERCE_IMPLICIT_CAST &&
9333 [ + + ]: 1075 : !showimplicit)
9334 : : {
9335 : : /* don't show the implicit cast */
7242 9336 : 32 : get_rule_expr_paren(arg, context, false, node);
9337 : : }
9338 : : else
9339 : : {
6238 9340 : 1114 : get_coercion_expr(arg, context,
9341 : : relabel->resulttype,
9342 : : relabel->resulttypmod,
9343 : : node);
9344 : : }
9345 : : }
8820 9346 : 1146 : break;
9347 : :
6158 9348 : 320 : case T_CoerceViaIO:
9349 : : {
9350 : 320 : CoerceViaIO *iocoerce = (CoerceViaIO *) node;
9351 : 320 : Node *arg = (Node *) iocoerce->arg;
9352 : :
9353 [ + + ]: 320 : if (iocoerce->coerceformat == COERCE_IMPLICIT_CAST &&
9354 [ + - ]: 12 : !showimplicit)
9355 : : {
9356 : : /* don't show the implicit cast */
9357 : 12 : get_rule_expr_paren(arg, context, false, node);
9358 : : }
9359 : : else
9360 : : {
9361 : 308 : get_coercion_expr(arg, context,
9362 : : iocoerce->resulttype,
9363 : : -1,
9364 : : node);
9365 : : }
9366 : : }
9367 : 320 : break;
9368 : :
6228 9369 : 20 : case T_ArrayCoerceExpr:
9370 : : {
9371 : 20 : ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
9372 : 20 : Node *arg = (Node *) acoerce->arg;
9373 : :
9374 [ + - ]: 20 : if (acoerce->coerceformat == COERCE_IMPLICIT_CAST &&
9375 [ - + ]: 20 : !showimplicit)
9376 : : {
9377 : : /* don't show the implicit cast */
6228 tgl@sss.pgh.pa.us 9378 :UBC 0 : get_rule_expr_paren(arg, context, false, node);
9379 : : }
9380 : : else
9381 : : {
6228 tgl@sss.pgh.pa.us 9382 :CBC 20 : get_coercion_expr(arg, context,
9383 : : acoerce->resulttype,
9384 : : acoerce->resulttypmod,
9385 : : node);
9386 : : }
9387 : : }
9388 : 20 : break;
9389 : :
7064 9390 : 44 : case T_ConvertRowtypeExpr:
9391 : : {
9392 : 44 : ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node;
9393 : 44 : Node *arg = (Node *) convert->arg;
9394 : :
9395 [ + + ]: 44 : if (convert->convertformat == COERCE_IMPLICIT_CAST &&
9396 [ + + ]: 41 : !showimplicit)
9397 : : {
9398 : : /* don't show the implicit cast */
9399 : 12 : get_rule_expr_paren(arg, context, false, node);
9400 : : }
9401 : : else
9402 : : {
6238 9403 : 32 : get_coercion_expr(arg, context,
9404 : : convert->resulttype, -1,
9405 : : node);
9406 : : }
9407 : : }
7064 9408 : 44 : break;
9409 : :
4783 9410 : 45 : case T_CollateExpr:
9411 : : {
9412 : 45 : CollateExpr *collate = (CollateExpr *) node;
9413 : 45 : Node *arg = (Node *) collate->arg;
9414 : :
9415 [ + + ]: 45 : if (!PRETTY_PAREN(context))
9416 : 42 : appendStringInfoChar(buf, '(');
9417 : 45 : get_rule_expr_paren(arg, context, showimplicit, node);
9418 : 45 : appendStringInfo(buf, " COLLATE %s",
9419 : : generate_collation_name(collate->collOid));
9420 [ + + ]: 45 : if (!PRETTY_PAREN(context))
9421 : 42 : appendStringInfoChar(buf, ')');
9422 : : }
9423 : 45 : break;
9424 : :
8996 9425 : 203 : case T_CaseExpr:
9426 : : {
9427 : 203 : CaseExpr *caseexpr = (CaseExpr *) node;
9428 : : ListCell *temp;
9429 : :
7564 9430 : 203 : appendContextKeyword(context, "CASE",
9431 : : 0, PRETTYINDENT_VAR, 0);
7333 9432 [ + + ]: 203 : if (caseexpr->arg)
9433 : : {
9434 : 63 : appendStringInfoChar(buf, ' ');
9435 : 63 : get_rule_expr((Node *) caseexpr->arg, context, true);
9436 : : }
8996 9437 [ + - + + : 887 : foreach(temp, caseexpr->args)
+ + ]
9438 : : {
9439 : 684 : CaseWhen *when = (CaseWhen *) lfirst(temp);
6700 9440 : 684 : Node *w = (Node *) when->expr;
9441 : :
7333 9442 [ + + ]: 684 : if (caseexpr->arg)
9443 : : {
9444 : : /*
9445 : : * The parser should have produced WHEN clauses of the
9446 : : * form "CaseTestExpr = RHS", possibly with an
9447 : : * implicit coercion inserted above the CaseTestExpr.
9448 : : * For accurate decompilation of rules it's essential
9449 : : * that we show just the RHS. However in an
9450 : : * expression that's been through the optimizer, the
9451 : : * WHEN clause could be almost anything (since the
9452 : : * equality operator could have been expanded into an
9453 : : * inline function). If we don't recognize the form
9454 : : * of the WHEN clause, just punt and display it as-is.
9455 : : */
6700 9456 [ + - ]: 256 : if (IsA(w, OpExpr))
9457 : : {
5527 9458 : 256 : List *args = ((OpExpr *) w)->args;
9459 : :
4707 9460 [ + - ]: 256 : if (list_length(args) == 2 &&
9461 [ + - ]: 256 : IsA(strip_implicit_coercions(linitial(args)),
9462 : : CaseTestExpr))
9463 : 256 : w = (Node *) lsecond(args);
9464 : : }
9465 : : }
9466 : :
9467 [ + + ]: 684 : if (!PRETTY_INDENT(context))
9468 : 41 : appendStringInfoChar(buf, ' ');
9469 : 684 : appendContextKeyword(context, "WHEN ",
9470 : : 0, 0, 0);
9471 : 684 : get_rule_expr(w, context, false);
3818 rhaas@postgresql.org 9472 : 684 : appendStringInfoString(buf, " THEN ");
7794 tgl@sss.pgh.pa.us 9473 : 684 : get_rule_expr((Node *) when->result, context, true);
9474 : : }
7564 9475 [ + + ]: 203 : if (!PRETTY_INDENT(context))
7559 bruce@momjian.us 9476 : 36 : appendStringInfoChar(buf, ' ');
7564 tgl@sss.pgh.pa.us 9477 : 203 : appendContextKeyword(context, "ELSE ",
9478 : : 0, 0, 0);
7794 9479 : 203 : get_rule_expr((Node *) caseexpr->defresult, context, true);
7564 9480 [ + + ]: 203 : if (!PRETTY_INDENT(context))
7559 bruce@momjian.us 9481 : 36 : appendStringInfoChar(buf, ' ');
7564 tgl@sss.pgh.pa.us 9482 : 203 : appendContextKeyword(context, "END",
9483 : : -PRETTYINDENT_VAR, 0, 0);
9484 : : }
9357 bruce@momjian.us 9485 : 203 : break;
9486 : :
4707 tgl@sss.pgh.pa.us 9487 :UBC 0 : case T_CaseTestExpr:
9488 : : {
9489 : : /*
9490 : : * Normally we should never get here, since for expressions
9491 : : * that can contain this node type we attempt to avoid
9492 : : * recursing to it. But in an optimized expression we might
9493 : : * be unable to avoid that (see comments for CaseExpr). If we
9494 : : * do see one, print it as CASE_TEST_EXPR.
9495 : : */
3818 rhaas@postgresql.org 9496 : 0 : appendStringInfoString(buf, "CASE_TEST_EXPR");
9497 : : }
4707 tgl@sss.pgh.pa.us 9498 : 0 : break;
9499 : :
7677 tgl@sss.pgh.pa.us 9500 :CBC 220 : case T_ArrayExpr:
9501 : : {
7559 bruce@momjian.us 9502 : 220 : ArrayExpr *arrayexpr = (ArrayExpr *) node;
9503 : :
3818 rhaas@postgresql.org 9504 : 220 : appendStringInfoString(buf, "ARRAY[");
7129 tgl@sss.pgh.pa.us 9505 : 220 : get_rule_expr((Node *) arrayexpr->elements, context, true);
9506 : 220 : appendStringInfoChar(buf, ']');
9507 : :
9508 : : /*
9509 : : * If the array isn't empty, we assume its elements are
9510 : : * coerced to the desired type. If it's empty, though, we
9511 : : * need an explicit coercion to the array type.
9512 : : */
5595 9513 [ + + ]: 220 : if (arrayexpr->elements == NIL)
9514 : 3 : appendStringInfo(buf, "::%s",
9515 : : format_type_with_typemod(arrayexpr->array_typeid, -1));
9516 : : }
7677 9517 : 220 : break;
9518 : :
7279 9519 : 93 : case T_RowExpr:
9520 : : {
7168 bruce@momjian.us 9521 : 93 : RowExpr *rowexpr = (RowExpr *) node;
7180 tgl@sss.pgh.pa.us 9522 : 93 : TupleDesc tupdesc = NULL;
9523 : : ListCell *arg;
9524 : : int i;
9525 : : char *sep;
9526 : :
9527 : : /*
9528 : : * If it's a named type and not RECORD, we may have to skip
9529 : : * dropped columns and/or claim there are NULLs for added
9530 : : * columns.
9531 : : */
9532 [ + + ]: 93 : if (rowexpr->row_typeid != RECORDOID)
9533 : : {
9534 : 24 : tupdesc = lookup_rowtype_tupdesc(rowexpr->row_typeid, -1);
9535 [ - + ]: 24 : Assert(list_length(rowexpr->args) <= tupdesc->natts);
9536 : : }
9537 : :
9538 : : /*
9539 : : * SQL99 allows "ROW" to be omitted when there is more than
9540 : : * one column, but for simplicity we always print it.
9541 : : */
3818 rhaas@postgresql.org 9542 : 93 : appendStringInfoString(buf, "ROW(");
7279 tgl@sss.pgh.pa.us 9543 : 93 : sep = "";
7180 9544 : 93 : i = 0;
7279 9545 [ + - + + : 267 : foreach(arg, rowexpr->args)
+ + ]
9546 : : {
9547 : 174 : Node *e = (Node *) lfirst(arg);
9548 : :
7180 9549 [ + + ]: 174 : if (tupdesc == NULL ||
2429 andres@anarazel.de 9550 [ + - ]: 45 : !TupleDescAttr(tupdesc, i)->attisdropped)
9551 : : {
6924 neilc@samurai.com 9552 : 174 : appendStringInfoString(buf, sep);
9553 : : /* Whole-row Vars need special treatment here */
3073 tgl@sss.pgh.pa.us 9554 : 174 : get_rule_expr_toplevel(e, context, true);
7180 9555 : 174 : sep = ", ";
9556 : : }
9557 : 174 : i++;
9558 : : }
9559 [ + + ]: 93 : if (tupdesc != NULL)
9560 : : {
9561 [ - + ]: 24 : while (i < tupdesc->natts)
9562 : : {
2429 andres@anarazel.de 9563 [ # # ]:UBC 0 : if (!TupleDescAttr(tupdesc, i)->attisdropped)
9564 : : {
6924 neilc@samurai.com 9565 : 0 : appendStringInfoString(buf, sep);
3818 rhaas@postgresql.org 9566 : 0 : appendStringInfoString(buf, "NULL");
7180 tgl@sss.pgh.pa.us 9567 : 0 : sep = ", ";
9568 : : }
9569 : 0 : i++;
9570 : : }
9571 : :
6512 tgl@sss.pgh.pa.us 9572 [ + - ]:CBC 24 : ReleaseTupleDesc(tupdesc);
9573 : : }
3818 rhaas@postgresql.org 9574 : 93 : appendStringInfoChar(buf, ')');
7279 tgl@sss.pgh.pa.us 9575 [ + + ]: 93 : if (rowexpr->row_format == COERCE_EXPLICIT_CAST)
9576 : 18 : appendStringInfo(buf, "::%s",
9577 : : format_type_with_typemod(rowexpr->row_typeid, -1));
9578 : : }
9579 : 93 : break;
9580 : :
6682 9581 : 33 : case T_RowCompareExpr:
9582 : : {
9583 : 33 : RowCompareExpr *rcexpr = (RowCompareExpr *) node;
9584 : :
9585 : : /*
9586 : : * SQL99 allows "ROW" to be omitted when there is more than
9587 : : * one column, but for simplicity we always print it. Within
9588 : : * a ROW expression, whole-row Vars need special treatment, so
9589 : : * use get_rule_list_toplevel.
9590 : : */
3818 rhaas@postgresql.org 9591 : 33 : appendStringInfoString(buf, "(ROW(");
822 tgl@sss.pgh.pa.us 9592 : 33 : get_rule_list_toplevel(rcexpr->largs, context, true);
9593 : :
9594 : : /*
9595 : : * We assume that the name of the first-column operator will
9596 : : * do for all the rest too. This is definitely open to
9597 : : * failure, eg if some but not all operators were renamed
9598 : : * since the construct was parsed, but there seems no way to
9599 : : * be perfect.
9600 : : */
6682 9601 : 33 : appendStringInfo(buf, ") %s ROW(",
2489 9602 : 33 : generate_operator_name(linitial_oid(rcexpr->opnos),
9603 : 33 : exprType(linitial(rcexpr->largs)),
9604 : 33 : exprType(linitial(rcexpr->rargs))));
822 9605 : 33 : get_rule_list_toplevel(rcexpr->rargs, context, true);
3818 rhaas@postgresql.org 9606 : 33 : appendStringInfoString(buf, "))");
9607 : : }
6682 tgl@sss.pgh.pa.us 9608 : 33 : break;
9609 : :
7728 9610 : 499 : case T_CoalesceExpr:
9611 : : {
9612 : 499 : CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
9613 : :
3818 rhaas@postgresql.org 9614 : 499 : appendStringInfoString(buf, "COALESCE(");
7129 tgl@sss.pgh.pa.us 9615 : 499 : get_rule_expr((Node *) coalesceexpr->args, context, true);
9616 : 499 : appendStringInfoChar(buf, ')');
9617 : : }
7728 9618 : 499 : break;
9619 : :
6867 9620 : 18 : case T_MinMaxExpr:
9621 : : {
9622 : 18 : MinMaxExpr *minmaxexpr = (MinMaxExpr *) node;
9623 : :
9624 [ + + - ]: 18 : switch (minmaxexpr->op)
9625 : : {
9626 : 3 : case IS_GREATEST:
3818 rhaas@postgresql.org 9627 : 3 : appendStringInfoString(buf, "GREATEST(");
6867 tgl@sss.pgh.pa.us 9628 : 3 : break;
9629 : 15 : case IS_LEAST:
3818 rhaas@postgresql.org 9630 : 15 : appendStringInfoString(buf, "LEAST(");
6867 tgl@sss.pgh.pa.us 9631 : 15 : break;
9632 : : }
9633 : 18 : get_rule_expr((Node *) minmaxexpr->args, context, true);
9634 : 18 : appendStringInfoChar(buf, ')');
9635 : : }
9636 : 18 : break;
9637 : :
333 michael@paquier.xyz 9638 : 343 : case T_SQLValueFunction:
9639 : : {
9640 : 343 : SQLValueFunction *svf = (SQLValueFunction *) node;
9641 : :
9642 : : /*
9643 : : * Note: this code knows that typmod for time, timestamp, and
9644 : : * timestamptz just prints as integer.
9645 : : */
9646 [ + + + + : 343 : switch (svf->op)
+ + + + +
+ + + + +
+ - ]
9647 : : {
9648 : 52 : case SVFOP_CURRENT_DATE:
9649 : 52 : appendStringInfoString(buf, "CURRENT_DATE");
9650 : 52 : break;
9651 : 6 : case SVFOP_CURRENT_TIME:
9652 : 6 : appendStringInfoString(buf, "CURRENT_TIME");
9653 : 6 : break;
9654 : 6 : case SVFOP_CURRENT_TIME_N:
9655 : 6 : appendStringInfo(buf, "CURRENT_TIME(%d)", svf->typmod);
9656 : 6 : break;
9657 : 6 : case SVFOP_CURRENT_TIMESTAMP:
9658 : 6 : appendStringInfoString(buf, "CURRENT_TIMESTAMP");
9659 : 6 : break;
9660 : 52 : case SVFOP_CURRENT_TIMESTAMP_N:
9661 : 52 : appendStringInfo(buf, "CURRENT_TIMESTAMP(%d)",
9662 : : svf->typmod);
9663 : 52 : break;
9664 : 6 : case SVFOP_LOCALTIME:
9665 : 6 : appendStringInfoString(buf, "LOCALTIME");
9666 : 6 : break;
9667 : 6 : case SVFOP_LOCALTIME_N:
9668 : 6 : appendStringInfo(buf, "LOCALTIME(%d)", svf->typmod);
9669 : 6 : break;
9670 : 15 : case SVFOP_LOCALTIMESTAMP:
9671 : 15 : appendStringInfoString(buf, "LOCALTIMESTAMP");
9672 : 15 : break;
9673 : 9 : case SVFOP_LOCALTIMESTAMP_N:
9674 : 9 : appendStringInfo(buf, "LOCALTIMESTAMP(%d)",
9675 : : svf->typmod);
9676 : 9 : break;
9677 : 6 : case SVFOP_CURRENT_ROLE:
9678 : 6 : appendStringInfoString(buf, "CURRENT_ROLE");
9679 : 6 : break;
9680 : 144 : case SVFOP_CURRENT_USER:
9681 : 144 : appendStringInfoString(buf, "CURRENT_USER");
9682 : 144 : break;
9683 : 6 : case SVFOP_USER:
9684 : 6 : appendStringInfoString(buf, "USER");
9685 : 6 : break;
9686 : 17 : case SVFOP_SESSION_USER:
9687 : 17 : appendStringInfoString(buf, "SESSION_USER");
9688 : 17 : break;
9689 : 6 : case SVFOP_CURRENT_CATALOG:
9690 : 6 : appendStringInfoString(buf, "CURRENT_CATALOG");
9691 : 6 : break;
9692 : 6 : case SVFOP_CURRENT_SCHEMA:
9693 : 6 : appendStringInfoString(buf, "CURRENT_SCHEMA");
9694 : 6 : break;
9695 : : }
9696 : : }
9697 : 343 : break;
9698 : :
6321 tgl@sss.pgh.pa.us 9699 : 72 : case T_XmlExpr:
9700 : : {
5995 bruce@momjian.us 9701 : 72 : XmlExpr *xexpr = (XmlExpr *) node;
9702 : 72 : bool needcomma = false;
9703 : : ListCell *arg;
9704 : : ListCell *narg;
9705 : : Const *con;
9706 : :
6321 tgl@sss.pgh.pa.us 9707 [ + + + + : 72 : switch (xexpr->op)
+ + + -
- ]
9708 : : {
9709 : 8 : case IS_XMLCONCAT:
9710 : 8 : appendStringInfoString(buf, "XMLCONCAT(");
9711 : 8 : break;
9712 : 16 : case IS_XMLELEMENT:
9713 : 16 : appendStringInfoString(buf, "XMLELEMENT(");
9714 : 16 : break;
9715 : 8 : case IS_XMLFOREST:
9716 : 8 : appendStringInfoString(buf, "XMLFOREST(");
9717 : 8 : break;
9718 : 8 : case IS_XMLPARSE:
9719 : 8 : appendStringInfoString(buf, "XMLPARSE(");
9720 : 8 : break;
9721 : 8 : case IS_XMLPI:
9722 : 8 : appendStringInfoString(buf, "XMLPI(");
9723 : 8 : break;
9724 : 8 : case IS_XMLROOT:
9725 : 8 : appendStringInfoString(buf, "XMLROOT(");
9726 : 8 : break;
6280 peter_e@gmx.net 9727 : 16 : case IS_XMLSERIALIZE:
9728 : 16 : appendStringInfoString(buf, "XMLSERIALIZE(");
9729 : 16 : break;
6300 peter_e@gmx.net 9730 :UBC 0 : case IS_DOCUMENT:
9731 : 0 : break;
9732 : : }
6280 peter_e@gmx.net 9733 [ + + + + ]:CBC 72 : if (xexpr->op == IS_XMLPARSE || xexpr->op == IS_XMLSERIALIZE)
9734 : : {
9735 [ - + ]: 24 : if (xexpr->xmloption == XMLOPTION_DOCUMENT)
6280 peter_e@gmx.net 9736 :UBC 0 : appendStringInfoString(buf, "DOCUMENT ");
9737 : : else
6280 peter_e@gmx.net 9738 :CBC 24 : appendStringInfoString(buf, "CONTENT ");
9739 : : }
6321 tgl@sss.pgh.pa.us 9740 [ + + ]: 72 : if (xexpr->name)
9741 : : {
9742 : 24 : appendStringInfo(buf, "NAME %s",
6316 peter_e@gmx.net 9743 : 24 : quote_identifier(map_xml_name_to_sql_identifier(xexpr->name)));
6321 tgl@sss.pgh.pa.us 9744 : 24 : needcomma = true;
9745 : : }
9746 [ + + ]: 72 : if (xexpr->named_args)
9747 : : {
9748 [ + + ]: 16 : if (xexpr->op != IS_XMLFOREST)
9749 : : {
9750 [ + - ]: 8 : if (needcomma)
9751 : 8 : appendStringInfoString(buf, ", ");
9752 : 8 : appendStringInfoString(buf, "XMLATTRIBUTES(");
9753 : 8 : needcomma = false;
9754 : : }
9755 [ + - + + : 56 : forboth(arg, xexpr->named_args, narg, xexpr->arg_names)
+ - + + +
+ + - +
+ ]
9756 : : {
5995 bruce@momjian.us 9757 : 40 : Node *e = (Node *) lfirst(arg);
9758 : 40 : char *argname = strVal(lfirst(narg));
9759 : :
6321 tgl@sss.pgh.pa.us 9760 [ + + ]: 40 : if (needcomma)
9761 : 24 : appendStringInfoString(buf, ", ");
9762 : 40 : get_rule_expr((Node *) e, context, true);
9763 : 40 : appendStringInfo(buf, " AS %s",
6316 peter_e@gmx.net 9764 : 40 : quote_identifier(map_xml_name_to_sql_identifier(argname)));
6321 tgl@sss.pgh.pa.us 9765 : 40 : needcomma = true;
9766 : : }
9767 [ + + ]: 16 : if (xexpr->op != IS_XMLFOREST)
9768 : 8 : appendStringInfoChar(buf, ')');
9769 : : }
9770 [ + + ]: 72 : if (xexpr->args)
9771 : : {
9772 [ + + ]: 64 : if (needcomma)
9773 : 24 : appendStringInfoString(buf, ", ");
9774 [ + + + - : 64 : switch (xexpr->op)
- ]
9775 : : {
9776 : 48 : case IS_XMLCONCAT:
9777 : : case IS_XMLELEMENT:
9778 : : case IS_XMLFOREST:
9779 : : case IS_XMLPI:
9780 : : case IS_XMLSERIALIZE:
9781 : : /* no extra decoration needed */
9782 : 48 : get_rule_expr((Node *) xexpr->args, context, true);
9783 : 48 : break;
9784 : 8 : case IS_XMLPARSE:
6280 peter_e@gmx.net 9785 [ - + ]: 8 : Assert(list_length(xexpr->args) == 2);
9786 : :
6321 tgl@sss.pgh.pa.us 9787 : 8 : get_rule_expr((Node *) linitial(xexpr->args),
9788 : : context, true);
9789 : :
2561 9790 : 8 : con = lsecond_node(Const, xexpr->args);
6321 9791 [ - + ]: 8 : Assert(!con->constisnull);
9792 [ - + ]: 8 : if (DatumGetBool(con->constvalue))
6321 tgl@sss.pgh.pa.us 9793 :UBC 0 : appendStringInfoString(buf,
9794 : : " PRESERVE WHITESPACE");
9795 : : else
6321 tgl@sss.pgh.pa.us 9796 :CBC 8 : appendStringInfoString(buf,
9797 : : " STRIP WHITESPACE");
9798 : 8 : break;
9799 : 8 : case IS_XMLROOT:
9800 [ - + ]: 8 : Assert(list_length(xexpr->args) == 3);
9801 : :
9802 : 8 : get_rule_expr((Node *) linitial(xexpr->args),
9803 : : context, true);
9804 : :
9805 : 8 : appendStringInfoString(buf, ", VERSION ");
9806 : 8 : con = (Const *) lsecond(xexpr->args);
9807 [ + - ]: 8 : if (IsA(con, Const) &&
9808 [ + - ]: 8 : con->constisnull)
9809 : 8 : appendStringInfoString(buf, "NO VALUE");
9810 : : else
6321 tgl@sss.pgh.pa.us 9811 :UBC 0 : get_rule_expr((Node *) con, context, false);
9812 : :
2561 tgl@sss.pgh.pa.us 9813 :CBC 8 : con = lthird_node(Const, xexpr->args);
6321 9814 [ + - ]: 8 : if (con->constisnull)
9815 : : /* suppress STANDALONE NO VALUE */ ;
9816 : : else
9817 : : {
6280 peter_e@gmx.net 9818 : 8 : switch (DatumGetInt32(con->constvalue))
9819 : : {
9820 : 8 : case XML_STANDALONE_YES:
9821 : 8 : appendStringInfoString(buf,
9822 : : ", STANDALONE YES");
9823 : 8 : break;
6280 peter_e@gmx.net 9824 :UBC 0 : case XML_STANDALONE_NO:
9825 : 0 : appendStringInfoString(buf,
9826 : : ", STANDALONE NO");
9827 : 0 : break;
9828 : 0 : case XML_STANDALONE_NO_VALUE:
9829 : 0 : appendStringInfoString(buf,
9830 : : ", STANDALONE NO VALUE");
9831 : 0 : break;
9832 : 0 : default:
9833 : 0 : break;
9834 : : }
9835 : : }
6321 tgl@sss.pgh.pa.us 9836 :CBC 8 : break;
6300 peter_e@gmx.net 9837 :UBC 0 : case IS_DOCUMENT:
9838 : 0 : get_rule_expr_paren((Node *) xexpr->args, context, false, node);
9839 : 0 : break;
9840 : : }
9841 : : }
6280 peter_e@gmx.net 9842 [ + + ]:CBC 72 : if (xexpr->op == IS_XMLSERIALIZE)
4775 tgl@sss.pgh.pa.us 9843 : 16 : appendStringInfo(buf, " AS %s",
9844 : : format_type_with_typemod(xexpr->type,
9845 : : xexpr->typmod));
6300 peter_e@gmx.net 9846 [ - + ]: 72 : if (xexpr->op == IS_DOCUMENT)
6300 peter_e@gmx.net 9847 :UBC 0 : appendStringInfoString(buf, " IS DOCUMENT");
9848 : : else
6300 peter_e@gmx.net 9849 :CBC 72 : appendStringInfoChar(buf, ')');
9850 : : }
6321 tgl@sss.pgh.pa.us 9851 : 72 : break;
9852 : :
8335 9853 : 1200 : case T_NullTest:
9854 : : {
8207 bruce@momjian.us 9855 : 1200 : NullTest *ntest = (NullTest *) node;
9856 : :
7564 tgl@sss.pgh.pa.us 9857 [ + + ]: 1200 : if (!PRETTY_PAREN(context))
7559 bruce@momjian.us 9858 : 1173 : appendStringInfoChar(buf, '(');
7564 tgl@sss.pgh.pa.us 9859 : 1200 : get_rule_expr_paren((Node *) ntest->arg, context, true, node);
9860 : :
9861 : : /*
9862 : : * For scalar inputs, we prefer to print as IS [NOT] NULL,
9863 : : * which is shorter and traditional. If it's a rowtype input
9864 : : * but we're applying a scalar test, must print IS [NOT]
9865 : : * DISTINCT FROM NULL to be semantically correct.
9866 : : */
2817 9867 [ + + ]: 1200 : if (ntest->argisrow ||
9868 [ + + ]: 1185 : !type_is_rowtype(exprType((Node *) ntest->arg)))
9869 : : {
9870 [ + + - ]: 2382 : switch (ntest->nulltesttype)
9871 : : {
9872 : 396 : case IS_NULL:
9873 : 396 : appendStringInfoString(buf, " IS NULL");
9874 : 396 : break;
9875 : 795 : case IS_NOT_NULL:
9876 : 795 : appendStringInfoString(buf, " IS NOT NULL");
9877 : 795 : break;
2817 tgl@sss.pgh.pa.us 9878 :UBC 0 : default:
9879 [ # # ]: 0 : elog(ERROR, "unrecognized nulltesttype: %d",
9880 : : (int) ntest->nulltesttype);
9881 : : }
9882 : : }
9883 : : else
9884 : : {
2817 tgl@sss.pgh.pa.us 9885 [ + + - ]:CBC 9 : switch (ntest->nulltesttype)
9886 : : {
9887 : 3 : case IS_NULL:
9888 : 3 : appendStringInfoString(buf, " IS NOT DISTINCT FROM NULL");
9889 : 3 : break;
9890 : 6 : case IS_NOT_NULL:
9891 : 6 : appendStringInfoString(buf, " IS DISTINCT FROM NULL");
9892 : 6 : break;
2817 tgl@sss.pgh.pa.us 9893 :UBC 0 : default:
9894 [ # # ]: 0 : elog(ERROR, "unrecognized nulltesttype: %d",
9895 : : (int) ntest->nulltesttype);
9896 : : }
9897 : : }
7564 tgl@sss.pgh.pa.us 9898 [ + + ]:CBC 1200 : if (!PRETTY_PAREN(context))
7559 bruce@momjian.us 9899 : 1173 : appendStringInfoChar(buf, ')');
9900 : : }
8335 tgl@sss.pgh.pa.us 9901 : 1200 : break;
9902 : :
9903 : 153 : case T_BooleanTest:
9904 : : {
8207 bruce@momjian.us 9905 : 153 : BooleanTest *btest = (BooleanTest *) node;
9906 : :
7564 tgl@sss.pgh.pa.us 9907 [ + - ]: 153 : if (!PRETTY_PAREN(context))
7559 bruce@momjian.us 9908 : 153 : appendStringInfoChar(buf, '(');
7564 tgl@sss.pgh.pa.us 9909 : 153 : get_rule_expr_paren((Node *) btest->arg, context, false, node);
8207 bruce@momjian.us 9910 [ + + - + : 153 : switch (btest->booltesttype)
+ + - ]
9911 : : {
9912 : 18 : case IS_TRUE:
3818 rhaas@postgresql.org 9913 : 18 : appendStringInfoString(buf, " IS TRUE");
8335 tgl@sss.pgh.pa.us 9914 : 18 : break;
8207 bruce@momjian.us 9915 : 69 : case IS_NOT_TRUE:
3818 rhaas@postgresql.org 9916 : 69 : appendStringInfoString(buf, " IS NOT TRUE");
8335 tgl@sss.pgh.pa.us 9917 : 69 : break;
8207 bruce@momjian.us 9918 :UBC 0 : case IS_FALSE:
3818 rhaas@postgresql.org 9919 : 0 : appendStringInfoString(buf, " IS FALSE");
8335 tgl@sss.pgh.pa.us 9920 : 0 : break;
8207 bruce@momjian.us 9921 :CBC 27 : case IS_NOT_FALSE:
3818 rhaas@postgresql.org 9922 : 27 : appendStringInfoString(buf, " IS NOT FALSE");
8335 tgl@sss.pgh.pa.us 9923 : 27 : break;
8207 bruce@momjian.us 9924 : 12 : case IS_UNKNOWN:
3818 rhaas@postgresql.org 9925 : 12 : appendStringInfoString(buf, " IS UNKNOWN");
8335 tgl@sss.pgh.pa.us 9926 : 12 : break;
8207 bruce@momjian.us 9927 : 27 : case IS_NOT_UNKNOWN:
3818 rhaas@postgresql.org 9928 : 27 : appendStringInfoString(buf, " IS NOT UNKNOWN");
8335 tgl@sss.pgh.pa.us 9929 : 27 : break;
8207 bruce@momjian.us 9930 :UBC 0 : default:
7567 tgl@sss.pgh.pa.us 9931 [ # # ]: 0 : elog(ERROR, "unrecognized booltesttype: %d",
9932 : : (int) btest->booltesttype);
9933 : : }
7564 tgl@sss.pgh.pa.us 9934 [ + - ]:CBC 153 : if (!PRETTY_PAREN(context))
7559 bruce@momjian.us 9935 : 153 : appendStringInfoChar(buf, ')');
9936 : : }
8335 tgl@sss.pgh.pa.us 9937 : 153 : break;
9938 : :
7741 9939 : 16 : case T_CoerceToDomain:
9940 : : {
9941 : 16 : CoerceToDomain *ctest = (CoerceToDomain *) node;
7559 bruce@momjian.us 9942 : 16 : Node *arg = (Node *) ctest->arg;
9943 : :
7741 tgl@sss.pgh.pa.us 9944 [ + + ]: 16 : if (ctest->coercionformat == COERCE_IMPLICIT_CAST &&
9945 [ + - ]: 11 : !showimplicit)
9946 : : {
9947 : : /* don't show the implicit cast */
9948 : 11 : get_rule_expr(arg, context, false);
9949 : : }
9950 : : else
9951 : : {
6238 9952 : 5 : get_coercion_expr(arg, context,
9953 : : ctest->resulttype,
9954 : : ctest->resulttypmod,
9955 : : node);
9956 : : }
9957 : : }
7897 9958 : 16 : break;
9959 : :
7741 9960 : 175 : case T_CoerceToDomainValue:
3818 rhaas@postgresql.org 9961 : 175 : appendStringInfoString(buf, "VALUE");
8069 tgl@sss.pgh.pa.us 9962 : 175 : break;
9963 : :
7591 9964 : 38 : case T_SetToDefault:
3818 rhaas@postgresql.org 9965 : 38 : appendStringInfoString(buf, "DEFAULT");
7591 tgl@sss.pgh.pa.us 9966 : 38 : break;
9967 : :
6152 9968 : 9 : case T_CurrentOfExpr:
9969 : : {
9970 : 9 : CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
9971 : :
9972 [ + - ]: 9 : if (cexpr->cursor_name)
9973 : 9 : appendStringInfo(buf, "CURRENT OF %s",
9974 : 9 : quote_identifier(cexpr->cursor_name));
9975 : : else
6152 tgl@sss.pgh.pa.us 9976 :UBC 0 : appendStringInfo(buf, "CURRENT OF $%d",
9977 : : cexpr->cursor_param);
9978 : : }
6152 tgl@sss.pgh.pa.us 9979 :CBC 9 : break;
9980 : :
2466 tgl@sss.pgh.pa.us 9981 :UBC 0 : case T_NextValueExpr:
9982 : : {
9983 : 0 : NextValueExpr *nvexpr = (NextValueExpr *) node;
9984 : :
9985 : : /*
9986 : : * This isn't exactly nextval(), but that seems close enough
9987 : : * for EXPLAIN's purposes.
9988 : : */
9989 : 0 : appendStringInfoString(buf, "nextval(");
9990 : 0 : simple_quote_literal(buf,
9991 : 0 : generate_relation_name(nvexpr->seqid,
9992 : : NIL));
9993 : 0 : appendStringInfoChar(buf, ')');
9994 : : }
9995 : 0 : break;
9996 : :
3253 andres@anarazel.de 9997 :CBC 12 : case T_InferenceElem:
9998 : : {
3249 bruce@momjian.us 9999 : 12 : InferenceElem *iexpr = (InferenceElem *) node;
10000 : : bool save_varprefix;
10001 : : bool need_parens;
10002 : :
10003 : : /*
10004 : : * InferenceElem can only refer to target relation, so a
10005 : : * prefix is not useful, and indeed would cause parse errors.
10006 : : */
2989 tgl@sss.pgh.pa.us 10007 : 12 : save_varprefix = context->varprefix;
3253 andres@anarazel.de 10008 : 12 : context->varprefix = false;
10009 : :
10010 : : /*
10011 : : * Parenthesize the element unless it's a simple Var or a bare
10012 : : * function call. Follows pg_get_indexdef_worker().
10013 : : */
10014 : 12 : need_parens = !IsA(iexpr->expr, Var);
10015 [ - + ]: 12 : if (IsA(iexpr->expr, FuncExpr) &&
3253 andres@anarazel.de 10016 [ # # ]:UBC 0 : ((FuncExpr *) iexpr->expr)->funcformat ==
10017 : : COERCE_EXPLICIT_CALL)
10018 : 0 : need_parens = false;
10019 : :
3253 andres@anarazel.de 10020 [ - + ]:CBC 12 : if (need_parens)
3253 andres@anarazel.de 10021 :UBC 0 : appendStringInfoChar(buf, '(');
3253 andres@anarazel.de 10022 :CBC 12 : get_rule_expr((Node *) iexpr->expr,
10023 : : context, false);
10024 [ - + ]: 12 : if (need_parens)
3253 andres@anarazel.de 10025 :UBC 0 : appendStringInfoChar(buf, ')');
10026 : :
2989 tgl@sss.pgh.pa.us 10027 :CBC 12 : context->varprefix = save_varprefix;
10028 : :
3253 andres@anarazel.de 10029 [ + + ]: 12 : if (iexpr->infercollid)
10030 : 6 : appendStringInfo(buf, " COLLATE %s",
10031 : : generate_collation_name(iexpr->infercollid));
10032 : :
10033 : : /* Add the operator class name, if not default */
10034 [ + + ]: 12 : if (iexpr->inferopclass)
10035 : : {
3249 bruce@momjian.us 10036 : 6 : Oid inferopclass = iexpr->inferopclass;
10037 : 6 : Oid inferopcinputtype = get_opclass_input_type(iexpr->inferopclass);
10038 : :
3253 andres@anarazel.de 10039 : 6 : get_opclass_name(inferopclass, inferopcinputtype, buf);
10040 : : }
10041 : : }
10042 : 12 : break;
10043 : :
2685 rhaas@postgresql.org 10044 : 1994 : case T_PartitionBoundSpec:
10045 : : {
10046 : 1994 : PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
10047 : : ListCell *cell;
10048 : : char *sep;
10049 : :
2410 10050 [ + + ]: 1994 : if (spec->is_default)
10051 : : {
10052 : 112 : appendStringInfoString(buf, "DEFAULT");
10053 : 112 : break;
10054 : : }
10055 : :
2685 10056 [ + + + - ]: 1882 : switch (spec->strategy)
10057 : : {
2348 10058 : 91 : case PARTITION_STRATEGY_HASH:
10059 [ + - - + ]: 91 : Assert(spec->modulus > 0 && spec->remainder >= 0);
10060 [ - + ]: 91 : Assert(spec->modulus > spec->remainder);
10061 : :
10062 : 91 : appendStringInfoString(buf, "FOR VALUES");
10063 : 91 : appendStringInfo(buf, " WITH (modulus %d, remainder %d)",
10064 : : spec->modulus, spec->remainder);
10065 : 91 : break;
10066 : :
2685 10067 : 645 : case PARTITION_STRATEGY_LIST:
10068 [ - + ]: 645 : Assert(spec->listdatums != NIL);
10069 : :
2513 tgl@sss.pgh.pa.us 10070 : 645 : appendStringInfoString(buf, "FOR VALUES IN (");
2685 rhaas@postgresql.org 10071 : 645 : sep = "";
2637 10072 [ + - + + : 1807 : foreach(cell, spec->listdatums)
+ + ]
10073 : : {
1000 peter@eisentraut.org 10074 : 1162 : Const *val = lfirst_node(Const, cell);
10075 : :
2685 rhaas@postgresql.org 10076 : 1162 : appendStringInfoString(buf, sep);
10077 : 1162 : get_const_expr(val, context, -1);
10078 : 1162 : sep = ", ";
10079 : : }
10080 : :
2434 peter_e@gmx.net 10081 : 645 : appendStringInfoChar(buf, ')');
2685 rhaas@postgresql.org 10082 : 645 : break;
10083 : :
10084 : 1146 : case PARTITION_STRATEGY_RANGE:
10085 [ + - + - : 1146 : Assert(spec->lowerdatums != NIL &&
- + ]
10086 : : spec->upperdatums != NIL &&
10087 : : list_length(spec->lowerdatums) ==
10088 : : list_length(spec->upperdatums));
10089 : :
2439 10090 : 1146 : appendStringInfo(buf, "FOR VALUES FROM %s TO %s",
10091 : : get_range_partbound_string(spec->lowerdatums),
10092 : : get_range_partbound_string(spec->upperdatums));
2685 10093 : 1146 : break;
10094 : :
2685 rhaas@postgresql.org 10095 :UBC 0 : default:
10096 [ # # ]: 0 : elog(ERROR, "unrecognized partition strategy: %d",
10097 : : (int) spec->strategy);
10098 : : break;
10099 : : }
10100 : : }
2685 rhaas@postgresql.org 10101 :CBC 1882 : break;
10102 : :
382 alvherre@alvh.no-ip. 10103 : 54 : case T_JsonValueExpr:
10104 : : {
10105 : 54 : JsonValueExpr *jve = (JsonValueExpr *) node;
10106 : :
10107 : 54 : get_rule_expr((Node *) jve->raw_expr, context, false);
10108 : 54 : get_json_format(jve->format, context->buf);
10109 : : }
10110 : 54 : break;
10111 : :
10112 : 69 : case T_JsonConstructorExpr:
10113 : 69 : get_json_constructor((JsonConstructorExpr *) node, context, false);
10114 : 69 : break;
10115 : :
380 10116 : 30 : case T_JsonIsPredicate:
10117 : : {
10118 : 30 : JsonIsPredicate *pred = (JsonIsPredicate *) node;
10119 : :
10120 [ + + ]: 30 : if (!PRETTY_PAREN(context))
10121 : 15 : appendStringInfoChar(context->buf, '(');
10122 : :
10123 : 30 : get_rule_expr_paren(pred->expr, context, true, node);
10124 : :
10125 : 30 : appendStringInfoString(context->buf, " IS JSON");
10126 : :
10127 : : /* TODO: handle FORMAT clause */
10128 : :
10129 [ + + + + ]: 30 : switch (pred->item_type)
10130 : : {
10131 : 6 : case JS_TYPE_SCALAR:
10132 : 6 : appendStringInfoString(context->buf, " SCALAR");
10133 : 6 : break;
10134 : 6 : case JS_TYPE_ARRAY:
10135 : 6 : appendStringInfoString(context->buf, " ARRAY");
10136 : 6 : break;
10137 : 6 : case JS_TYPE_OBJECT:
10138 : 6 : appendStringInfoString(context->buf, " OBJECT");
10139 : 6 : break;
10140 : 12 : default:
10141 : 12 : break;
10142 : : }
10143 : :
10144 [ + + ]: 30 : if (pred->unique_keys)
10145 : 6 : appendStringInfoString(context->buf, " WITH UNIQUE KEYS");
10146 : :
10147 [ + + ]: 30 : if (!PRETTY_PAREN(context))
10148 : 15 : appendStringInfoChar(context->buf, ')');
10149 : : }
10150 : 30 : break;
10151 : :
24 amitlan@postgresql.o 10152 :GNC 30 : case T_JsonExpr:
10153 : : {
10154 : 30 : JsonExpr *jexpr = (JsonExpr *) node;
10155 : :
10156 [ + + + - ]: 30 : switch (jexpr->op)
10157 : : {
10158 : 6 : case JSON_EXISTS_OP:
10159 : 6 : appendStringInfoString(buf, "JSON_EXISTS(");
10160 : 6 : break;
10161 : 18 : case JSON_QUERY_OP:
10162 : 18 : appendStringInfoString(buf, "JSON_QUERY(");
10163 : 18 : break;
10164 : 6 : case JSON_VALUE_OP:
10165 : 6 : appendStringInfoString(buf, "JSON_VALUE(");
10166 : 6 : break;
24 amitlan@postgresql.o 10167 :UNC 0 : default:
10168 [ # # ]: 0 : elog(ERROR, "unrecognized JsonExpr op: %d",
10169 : : (int) jexpr->op);
10170 : : }
10171 : :
24 amitlan@postgresql.o 10172 :GNC 30 : get_rule_expr(jexpr->formatted_expr, context, showimplicit);
10173 : :
10174 : 30 : appendStringInfoString(buf, ", ");
10175 : :
10176 : 30 : get_json_path_spec(jexpr->path_spec, context, showimplicit);
10177 : :
10178 [ + + ]: 30 : if (jexpr->passing_values)
10179 : : {
10180 : : ListCell *lc1,
10181 : : *lc2;
10182 : 6 : bool needcomma = false;
10183 : :
10184 : 6 : appendStringInfoString(buf, " PASSING ");
10185 : :
10186 [ + - + + : 24 : forboth(lc1, jexpr->passing_names,
+ - + + +
+ + - +
+ ]
10187 : : lc2, jexpr->passing_values)
10188 : : {
10189 [ + + ]: 18 : if (needcomma)
10190 : 12 : appendStringInfoString(buf, ", ");
10191 : 18 : needcomma = true;
10192 : :
10193 : 18 : get_rule_expr((Node *) lfirst(lc2), context, showimplicit);
10194 : 18 : appendStringInfo(buf, " AS %s",
10195 : 18 : ((String *) lfirst_node(String, lc1))->sval);
10196 : : }
10197 : : }
10198 : :
10199 [ + + ]: 30 : if (jexpr->op != JSON_EXISTS_OP ||
10200 [ - + ]: 6 : jexpr->returning->typid != BOOLOID)
10201 : 24 : get_json_returning(jexpr->returning, context->buf,
10202 : 24 : jexpr->op == JSON_QUERY_OP);
10203 : :
10204 : 30 : get_json_expr_options(jexpr, context,
10205 [ + + ]: 30 : jexpr->op != JSON_EXISTS_OP ?
10206 : : JSON_BEHAVIOR_NULL :
10207 : : JSON_BEHAVIOR_FALSE);
10208 : :
4 drowley@postgresql.o 10209 : 30 : appendStringInfoChar(buf, ')');
10210 : : }
24 amitlan@postgresql.o 10211 : 30 : break;
10212 : :
7129 tgl@sss.pgh.pa.us 10213 :CBC 1004 : case T_List:
10214 : : {
10215 : : char *sep;
10216 : : ListCell *l;
10217 : :
10218 : 1004 : sep = "";
10219 [ + - + + : 2856 : foreach(l, (List *) node)
+ + ]
10220 : : {
6924 neilc@samurai.com 10221 : 1852 : appendStringInfoString(buf, sep);
7129 tgl@sss.pgh.pa.us 10222 : 1852 : get_rule_expr((Node *) lfirst(l), context, showimplicit);
10223 : 1852 : sep = ", ";
10224 : : }
10225 : : }
10226 : 1004 : break;
10227 : :
2594 alvherre@alvh.no-ip. 10228 : 36 : case T_TableFunc:
10229 : 36 : get_tablefunc((TableFunc *) node, context, showimplicit);
10230 : 36 : break;
10231 : :
9357 bruce@momjian.us 10232 :UBC 0 : default:
7567 tgl@sss.pgh.pa.us 10233 [ # # ]: 0 : elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
10234 : : break;
10235 : : }
10236 : : }
10237 : :
10238 : : /*
10239 : : * get_rule_expr_toplevel - Parse back a toplevel expression
10240 : : *
10241 : : * Same as get_rule_expr(), except that if the expr is just a Var, we pass
10242 : : * istoplevel = true not false to get_variable(). This causes whole-row Vars
10243 : : * to get printed with decoration that will prevent expansion of "*".
10244 : : * We need to use this in contexts such as ROW() and VALUES(), where the
10245 : : * parser would expand "foo.*" appearing at top level. (In principle we'd
10246 : : * use this in get_target_list() too, but that has additional worries about
10247 : : * whether to print AS, so it needs to invoke get_variable() directly anyway.)
10248 : : */
10249 : : static void
3073 tgl@sss.pgh.pa.us 10250 :CBC 1411 : get_rule_expr_toplevel(Node *node, deparse_context *context,
10251 : : bool showimplicit)
10252 : : {
10253 [ + - + + ]: 1411 : if (node && IsA(node, Var))
10254 : 544 : (void) get_variable((Var *) node, 0, true, context);
10255 : : else
10256 : 867 : get_rule_expr(node, context, showimplicit);
10257 : 1411 : }
10258 : :
10259 : : /*
10260 : : * get_rule_list_toplevel - Parse back a list of toplevel expressions
10261 : : *
10262 : : * Apply get_rule_expr_toplevel() to each element of a List.
10263 : : *
10264 : : * This adds commas between the expressions, but caller is responsible
10265 : : * for printing surrounding decoration.
10266 : : */
10267 : : static void
822 10268 : 204 : get_rule_list_toplevel(List *lst, deparse_context *context,
10269 : : bool showimplicit)
10270 : : {
10271 : : const char *sep;
10272 : : ListCell *lc;
10273 : :
10274 : 204 : sep = "";
10275 [ + - + + : 715 : foreach(lc, lst)
+ + ]
10276 : : {
10277 : 511 : Node *e = (Node *) lfirst(lc);
10278 : :
10279 : 511 : appendStringInfoString(context->buf, sep);
10280 : 511 : get_rule_expr_toplevel(e, context, showimplicit);
10281 : 511 : sep = ", ";
10282 : : }
10283 : 204 : }
10284 : :
10285 : : /*
10286 : : * get_rule_expr_funccall - Parse back a function-call expression
10287 : : *
10288 : : * Same as get_rule_expr(), except that we guarantee that the output will
10289 : : * look like a function call, or like one of the things the grammar treats as
10290 : : * equivalent to a function call (see the func_expr_windowless production).
10291 : : * This is needed in places where the grammar uses func_expr_windowless and
10292 : : * you can't substitute a parenthesized a_expr. If what we have isn't going
10293 : : * to look like a function call, wrap it in a dummy CAST() expression, which
10294 : : * will satisfy the grammar --- and, indeed, is likely what the user wrote to
10295 : : * produce such a thing.
10296 : : */
10297 : : static void
2467 10298 : 324 : get_rule_expr_funccall(Node *node, deparse_context *context,
10299 : : bool showimplicit)
10300 : : {
10301 [ + + ]: 324 : if (looks_like_function(node))
10302 : 318 : get_rule_expr(node, context, showimplicit);
10303 : : else
10304 : : {
10305 : 6 : StringInfo buf = context->buf;
10306 : :
10307 : 6 : appendStringInfoString(buf, "CAST(");
10308 : : /* no point in showing any top-level implicit cast */
10309 : 6 : get_rule_expr(node, context, false);
10310 : 6 : appendStringInfo(buf, " AS %s)",
10311 : : format_type_with_typemod(exprType(node),
10312 : : exprTypmod(node)));
10313 : : }
10314 : 324 : }
10315 : :
10316 : : /*
10317 : : * Helper function to identify node types that satisfy func_expr_windowless.
10318 : : * If in doubt, "false" is always a safe answer.
10319 : : */
10320 : : static bool
10321 : 871 : looks_like_function(Node *node)
10322 : : {
10323 [ - + ]: 871 : if (node == NULL)
2467 tgl@sss.pgh.pa.us 10324 :UBC 0 : return false; /* probably shouldn't happen */
2467 tgl@sss.pgh.pa.us 10325 [ + + + ]:CBC 871 : switch (nodeTag(node))
10326 : : {
10327 : 338 : case T_FuncExpr:
10328 : : /* OK, unless it's going to deparse as a cast */
1257 10329 [ + + ]: 347 : return (((FuncExpr *) node)->funcformat == COERCE_EXPLICIT_CALL ||
10330 [ + + ]: 9 : ((FuncExpr *) node)->funcformat == COERCE_SQL_SYNTAX);
2467 10331 : 54 : case T_NullIfExpr:
10332 : : case T_CoalesceExpr:
10333 : : case T_MinMaxExpr:
10334 : : case T_SQLValueFunction:
10335 : : case T_XmlExpr:
10336 : : case T_JsonExpr:
10337 : : /* these are all accepted by func_expr_common_subexpr */
10338 : 54 : return true;
10339 : 479 : default:
10340 : 479 : break;
10341 : : }
10342 : 479 : return false;
10343 : : }
10344 : :
10345 : :
10346 : : /*
10347 : : * get_oper_expr - Parse back an OpExpr node
10348 : : */
10349 : : static void
7555 bruce@momjian.us 10350 : 27098 : get_oper_expr(OpExpr *expr, deparse_context *context)
10351 : : {
8017 tgl@sss.pgh.pa.us 10352 : 27098 : StringInfo buf = context->buf;
7794 10353 : 27098 : Oid opno = expr->opno;
8017 10354 : 27098 : List *args = expr->args;
10355 : :
7564 10356 [ + + ]: 27098 : if (!PRETTY_PAREN(context))
7559 bruce@momjian.us 10357 : 26124 : appendStringInfoChar(buf, '(');
7259 neilc@samurai.com 10358 [ + + ]: 27098 : if (list_length(args) == 2)
10359 : : {
10360 : : /* binary operator */
7263 10361 : 27083 : Node *arg1 = (Node *) linitial(args);
7893 bruce@momjian.us 10362 : 27083 : Node *arg2 = (Node *) lsecond(args);
10363 : :
7559 10364 : 27083 : get_rule_expr_paren(arg1, context, true, (Node *) expr);
8017 tgl@sss.pgh.pa.us 10365 : 27083 : appendStringInfo(buf, " %s ",
10366 : : generate_operator_name(opno,
10367 : : exprType(arg1),
10368 : : exprType(arg2)));
7559 bruce@momjian.us 10369 : 27083 : get_rule_expr_paren(arg2, context, true, (Node *) expr);
10370 : : }
10371 : : else
10372 : : {
10373 : : /* prefix operator */
7263 neilc@samurai.com 10374 : 15 : Node *arg = (Node *) linitial(args);
10375 : :
1305 tgl@sss.pgh.pa.us 10376 : 15 : appendStringInfo(buf, "%s ",
10377 : : generate_operator_name(opno,
10378 : : InvalidOid,
10379 : : exprType(arg)));
10380 : 15 : get_rule_expr_paren(arg, context, true, (Node *) expr);
10381 : : }
7564 10382 [ + + ]: 27098 : if (!PRETTY_PAREN(context))
7559 bruce@momjian.us 10383 : 26124 : appendStringInfoChar(buf, ')');
8017 tgl@sss.pgh.pa.us 10384 : 27098 : }
10385 : :
10386 : : /*
10387 : : * get_func_expr - Parse back a FuncExpr node
10388 : : */
10389 : : static void
7555 bruce@momjian.us 10390 : 5343 : get_func_expr(FuncExpr *expr, deparse_context *context,
10391 : : bool showimplicit)
10392 : : {
8960 tgl@sss.pgh.pa.us 10393 : 5343 : StringInfo buf = context->buf;
7794 10394 : 5343 : Oid funcoid = expr->funcid;
10395 : : Oid argtypes[FUNC_MAX_ARGS];
10396 : : int nargs;
10397 : : List *argnames;
10398 : : bool use_variadic;
10399 : : ListCell *l;
10400 : :
10401 : : /*
10402 : : * If the function call came from an implicit coercion, then just show the
10403 : : * first argument --- unless caller wants to see implicit coercions.
10404 : : */
10405 [ + + + + ]: 5343 : if (expr->funcformat == COERCE_IMPLICIT_CAST && !showimplicit)
10406 : : {
7263 neilc@samurai.com 10407 : 624 : get_rule_expr_paren((Node *) linitial(expr->args), context,
10408 : : false, (Node *) expr);
7879 tgl@sss.pgh.pa.us 10409 : 1421 : return;
10410 : : }
10411 : :
10412 : : /*
10413 : : * If the function call came from a cast, then show the first argument
10414 : : * plus an explicit cast operation.
10415 : : */
7794 10416 [ + + ]: 4719 : if (expr->funcformat == COERCE_EXPLICIT_CAST ||
10417 [ + + ]: 4436 : expr->funcformat == COERCE_IMPLICIT_CAST)
10418 : : {
7263 neilc@samurai.com 10419 : 710 : Node *arg = linitial(expr->args);
7794 tgl@sss.pgh.pa.us 10420 : 710 : Oid rettype = expr->funcresulttype;
10421 : : int32 coercedTypmod;
10422 : :
10423 : : /* Get the typmod if this is a length-coercion function */
7879 10424 : 710 : (void) exprIsLengthCoercion((Node *) expr, &coercedTypmod);
10425 : :
6238 10426 : 710 : get_coercion_expr(arg, context,
10427 : : rettype, coercedTypmod,
10428 : : (Node *) expr);
10429 : :
8814 10430 : 710 : return;
10431 : : }
10432 : :
10433 : : /*
10434 : : * If the function was called using one of the SQL spec's random special
10435 : : * syntaxes, try to reproduce that. If we don't recognize the function,
10436 : : * fall through.
10437 : : */
1257 10438 [ + + ]: 4009 : if (expr->funcformat == COERCE_SQL_SYNTAX)
10439 : : {
10440 [ + + ]: 90 : if (get_func_sql_syntax(expr, context))
10441 : 87 : return;
10442 : : }
10443 : :
10444 : : /*
10445 : : * Normal function: display as proname(args). First we need to extract
10446 : : * the argument datatypes.
10447 : : */
5586 10448 [ - + ]: 3922 : if (list_length(expr->args) > FUNC_MAX_ARGS)
5586 tgl@sss.pgh.pa.us 10449 [ # # ]:UBC 0 : ereport(ERROR,
10450 : : (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
10451 : : errmsg("too many arguments")));
8017 tgl@sss.pgh.pa.us 10452 :CBC 3922 : nargs = 0;
5302 10453 : 3922 : argnames = NIL;
8017 10454 [ + + + + : 8282 : foreach(l, expr->args)
+ + ]
10455 : : {
5161 bruce@momjian.us 10456 : 4360 : Node *arg = (Node *) lfirst(l);
10457 : :
5302 tgl@sss.pgh.pa.us 10458 [ + + ]: 4360 : if (IsA(arg, NamedArgExpr))
10459 : 9 : argnames = lappend(argnames, ((NamedArgExpr *) arg)->name);
10460 : 4360 : argtypes[nargs] = exprType(arg);
8017 10461 : 4360 : nargs++;
10462 : : }
10463 : :
10464 : 3922 : appendStringInfo(buf, "%s(",
10465 : : generate_function_name(funcoid, nargs,
10466 : : argnames, argtypes,
4101 10467 : 3922 : expr->funcvariadic,
10468 : : &use_variadic,
10469 : : context->special_exprkind));
5751 10470 : 3922 : nargs = 0;
10471 [ + + + + : 8282 : foreach(l, expr->args)
+ + ]
10472 : : {
10473 [ + + ]: 4360 : if (nargs++ > 0)
10474 : 843 : appendStringInfoString(buf, ", ");
1735 10475 [ + + + - ]: 4360 : if (use_variadic && lnext(expr->args, l) == NULL)
5751 10476 : 4 : appendStringInfoString(buf, "VARIADIC ");
10477 : 4360 : get_rule_expr((Node *) lfirst(l), context, true);
10478 : : }
8820 10479 : 3922 : appendStringInfoChar(buf, ')');
10480 : : }
10481 : :
10482 : : /*
10483 : : * get_agg_expr - Parse back an Aggref node
10484 : : */
10485 : : static void
591 andrew@dunslane.net 10486 : 922 : get_agg_expr(Aggref *aggref, deparse_context *context,
10487 : : Aggref *original_aggref)
10488 : : {
382 alvherre@alvh.no-ip. 10489 : 922 : get_agg_expr_helper(aggref, context, original_aggref, NULL, NULL,
10490 : : false);
10491 : 922 : }
10492 : :
10493 : : /*
10494 : : * get_agg_expr_helper - subroutine for get_agg_expr and
10495 : : * get_json_agg_constructor
10496 : : */
10497 : : static void
10498 : 940 : get_agg_expr_helper(Aggref *aggref, deparse_context *context,
10499 : : Aggref *original_aggref, const char *funcname,
10500 : : const char *options, bool is_json_objectagg)
10501 : : {
8039 tgl@sss.pgh.pa.us 10502 : 940 : StringInfo buf = context->buf;
10503 : : Oid argtypes[FUNC_MAX_ARGS];
10504 : : int nargs;
382 alvherre@alvh.no-ip. 10505 : 940 : bool use_variadic = false;
10506 : :
10507 : : /*
10508 : : * For a combining aggregate, we look up and deparse the corresponding
10509 : : * partial aggregate instead. This is necessary because our input
10510 : : * argument list has been replaced; the new argument list always has just
10511 : : * one element, which will point to a partial Aggref that supplies us with
10512 : : * transition states to combine.
10513 : : */
2849 tgl@sss.pgh.pa.us 10514 [ + + ]: 940 : if (DO_AGGSPLIT_COMBINE(aggref->aggsplit))
10515 : : {
10516 : : TargetEntry *tle;
10517 : :
2909 rhaas@postgresql.org 10518 [ - + ]: 130 : Assert(list_length(aggref->args) == 1);
1586 tgl@sss.pgh.pa.us 10519 : 130 : tle = linitial_node(TargetEntry, aggref->args);
10520 : 130 : resolve_special_varno((Node *) tle->expr, context,
10521 : : get_agg_combine_expr, original_aggref);
2909 rhaas@postgresql.org 10522 : 130 : return;
10523 : : }
10524 : :
10525 : : /*
10526 : : * Mark as PARTIAL, if appropriate. We look to the original aggref so as
10527 : : * to avoid printing this when recursing from the code just above.
10528 : : */
2849 tgl@sss.pgh.pa.us 10529 [ + + ]: 810 : if (DO_AGGSPLIT_SKIPFINAL(original_aggref->aggsplit))
2909 rhaas@postgresql.org 10530 : 30 : appendStringInfoString(buf, "PARTIAL ");
10531 : :
10532 : : /* Extract the argument types as seen by the parser */
3765 tgl@sss.pgh.pa.us 10533 : 810 : nargs = get_aggregate_argtypes(aggref, argtypes);
10534 : :
382 alvherre@alvh.no-ip. 10535 [ + + ]: 810 : if (!funcname)
10536 : 792 : funcname = generate_function_name(aggref->aggfnoid, nargs, NIL,
10537 : 792 : argtypes, aggref->aggvariadic,
10538 : : &use_variadic,
10539 : : context->special_exprkind);
10540 : :
10541 : : /* Print the aggregate name, schema-qualified if needed */
10542 : 810 : appendStringInfo(buf, "%s(%s", funcname,
5234 tgl@sss.pgh.pa.us 10543 [ + + ]: 810 : (aggref->aggdistinct != NIL) ? "DISTINCT " : "");
10544 : :
3765 10545 [ + + ]: 810 : if (AGGKIND_IS_ORDERED_SET(aggref->aggkind))
10546 : : {
10547 : : /*
10548 : : * Ordered-set aggregates do not use "*" syntax. Also, we needn't
10549 : : * worry about inserting VARIADIC. So we can just dump the direct
10550 : : * args as-is.
10551 : : */
10552 [ - + ]: 14 : Assert(!aggref->aggvariadic);
10553 : 14 : get_rule_expr((Node *) aggref->aggdirectargs, context, true);
10554 [ - + ]: 14 : Assert(aggref->aggorder != NIL);
10555 : 14 : appendStringInfoString(buf, ") WITHIN GROUP (ORDER BY ");
10556 : 14 : get_rule_orderby(aggref->aggorder, aggref->args, false, context);
10557 : : }
10558 : : else
10559 : : {
10560 : : /* aggstar can be set only in zero-argument aggregates */
10561 [ + + ]: 796 : if (aggref->aggstar)
10562 : 122 : appendStringInfoChar(buf, '*');
10563 : : else
10564 : : {
10565 : : ListCell *l;
10566 : : int i;
10567 : :
10568 : 674 : i = 0;
10569 [ + - + + : 1429 : foreach(l, aggref->args)
+ + ]
10570 : : {
10571 : 755 : TargetEntry *tle = (TargetEntry *) lfirst(l);
10572 : 755 : Node *arg = (Node *) tle->expr;
10573 : :
10574 [ - + ]: 755 : Assert(!IsA(arg, NamedArgExpr));
10575 [ + + ]: 755 : if (tle->resjunk)
10576 : 21 : continue;
10577 [ + + ]: 734 : if (i++ > 0)
10578 : : {
382 alvherre@alvh.no-ip. 10579 [ + + ]: 60 : if (is_json_objectagg)
10580 : : {
10581 : : /*
10582 : : * the ABSENT ON NULL and WITH UNIQUE args are printed
10583 : : * separately, so ignore them here
10584 : : */
10585 [ - + ]: 6 : if (i > 2)
382 alvherre@alvh.no-ip. 10586 :UBC 0 : break;
10587 : :
382 alvherre@alvh.no-ip. 10588 :CBC 6 : appendStringInfoString(buf, " : ");
10589 : : }
10590 : : else
10591 : 54 : appendStringInfoString(buf, ", ");
10592 : : }
3765 tgl@sss.pgh.pa.us 10593 [ + + + - ]: 734 : if (use_variadic && i == nargs)
10594 : 4 : appendStringInfoString(buf, "VARIADIC ");
10595 : 734 : get_rule_expr(arg, context, true);
10596 : : }
10597 : : }
10598 : :
10599 [ + + ]: 796 : if (aggref->aggorder != NIL)
10600 : : {
10601 : 37 : appendStringInfoString(buf, " ORDER BY ");
10602 : 37 : get_rule_orderby(aggref->aggorder, aggref->args, false, context);
10603 : : }
10604 : : }
10605 : :
382 alvherre@alvh.no-ip. 10606 [ + + ]: 810 : if (options)
10607 : 18 : appendStringInfoString(buf, options);
10608 : :
3925 noah@leadboat.com 10609 [ + + ]: 810 : if (aggref->aggfilter != NULL)
10610 : : {
10611 : 23 : appendStringInfoString(buf, ") FILTER (WHERE ");
10612 : 23 : get_rule_expr((Node *) aggref->aggfilter, context, false);
10613 : : }
10614 : :
8039 tgl@sss.pgh.pa.us 10615 : 810 : appendStringInfoChar(buf, ')');
10616 : : }
10617 : :
10618 : : /*
10619 : : * This is a helper function for get_agg_expr(). It's used when we deparse
10620 : : * a combining Aggref; resolve_special_varno locates the corresponding partial
10621 : : * Aggref and then calls this.
10622 : : */
10623 : : static void
1586 10624 : 130 : get_agg_combine_expr(Node *node, deparse_context *context, void *callback_arg)
10625 : : {
10626 : : Aggref *aggref;
10627 : 130 : Aggref *original_aggref = callback_arg;
10628 : :
2909 rhaas@postgresql.org 10629 [ - + ]: 130 : if (!IsA(node, Aggref))
2909 rhaas@postgresql.org 10630 [ # # ]:UBC 0 : elog(ERROR, "combining Aggref does not point to an Aggref");
10631 : :
2909 rhaas@postgresql.org 10632 :CBC 130 : aggref = (Aggref *) node;
10633 : 130 : get_agg_expr(aggref, context, original_aggref);
10634 : 130 : }
10635 : :
10636 : : /*
10637 : : * get_windowfunc_expr - Parse back a WindowFunc node
10638 : : */
10639 : : static void
591 andrew@dunslane.net 10640 : 120 : get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
10641 : : {
382 alvherre@alvh.no-ip. 10642 : 120 : get_windowfunc_expr_helper(wfunc, context, NULL, NULL, false);
10643 : 120 : }
10644 : :
10645 : :
10646 : : /*
10647 : : * get_windowfunc_expr_helper - subroutine for get_windowfunc_expr and
10648 : : * get_json_agg_constructor
10649 : : */
10650 : : static void
10651 : 126 : get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context,
10652 : : const char *funcname, const char *options,
10653 : : bool is_json_objectagg)
10654 : : {
5586 tgl@sss.pgh.pa.us 10655 : 126 : StringInfo buf = context->buf;
10656 : : Oid argtypes[FUNC_MAX_ARGS];
10657 : : int nargs;
10658 : : List *argnames;
10659 : : ListCell *l;
10660 : :
10661 [ - + ]: 126 : if (list_length(wfunc->args) > FUNC_MAX_ARGS)
5586 tgl@sss.pgh.pa.us 10662 [ # # ]:UBC 0 : ereport(ERROR,
10663 : : (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
10664 : : errmsg("too many arguments")));
5586 tgl@sss.pgh.pa.us 10665 :CBC 126 : nargs = 0;
3812 10666 : 126 : argnames = NIL;
5586 10667 [ + + + + : 198 : foreach(l, wfunc->args)
+ + ]
10668 : : {
5161 bruce@momjian.us 10669 : 72 : Node *arg = (Node *) lfirst(l);
10670 : :
3812 tgl@sss.pgh.pa.us 10671 [ - + ]: 72 : if (IsA(arg, NamedArgExpr))
3812 tgl@sss.pgh.pa.us 10672 :UBC 0 : argnames = lappend(argnames, ((NamedArgExpr *) arg)->name);
5302 tgl@sss.pgh.pa.us 10673 :CBC 72 : argtypes[nargs] = exprType(arg);
5586 10674 : 72 : nargs++;
10675 : : }
10676 : :
382 alvherre@alvh.no-ip. 10677 [ + + ]: 126 : if (!funcname)
10678 : 120 : funcname = generate_function_name(wfunc->winfnoid, nargs, argnames,
10679 : : argtypes, false, NULL,
10680 : : context->special_exprkind);
10681 : :
10682 : 126 : appendStringInfo(buf, "%s(", funcname);
10683 : :
10684 : : /* winstar can be set only in zero-argument aggregates */
5586 tgl@sss.pgh.pa.us 10685 [ + + ]: 126 : if (wfunc->winstar)
10686 : 12 : appendStringInfoChar(buf, '*');
10687 : : else
10688 : : {
382 alvherre@alvh.no-ip. 10689 [ + + ]: 114 : if (is_json_objectagg)
10690 : : {
10691 : 3 : get_rule_expr((Node *) linitial(wfunc->args), context, false);
10692 : 3 : appendStringInfoString(buf, " : ");
10693 : 3 : get_rule_expr((Node *) lsecond(wfunc->args), context, false);
10694 : : }
10695 : : else
10696 : 111 : get_rule_expr((Node *) wfunc->args, context, true);
10697 : : }
10698 : :
10699 [ + + ]: 126 : if (options)
10700 : 6 : appendStringInfoString(buf, options);
10701 : :
3925 noah@leadboat.com 10702 [ - + ]: 126 : if (wfunc->aggfilter != NULL)
10703 : : {
3925 noah@leadboat.com 10704 :UBC 0 : appendStringInfoString(buf, ") FILTER (WHERE ");
10705 : 0 : get_rule_expr((Node *) wfunc->aggfilter, context, false);
10706 : : }
10707 : :
5586 tgl@sss.pgh.pa.us 10708 :CBC 126 : appendStringInfoString(buf, ") OVER ");
10709 : :
10710 [ + + + - : 126 : foreach(l, context->windowClause)
+ + ]
10711 : : {
10712 : 21 : WindowClause *wc = (WindowClause *) lfirst(l);
10713 : :
10714 [ + - ]: 21 : if (wc->winref == wfunc->winref)
10715 : : {
10716 [ - + ]: 21 : if (wc->name)
5586 tgl@sss.pgh.pa.us 10717 :UBC 0 : appendStringInfoString(buf, quote_identifier(wc->name));
10718 : : else
5586 tgl@sss.pgh.pa.us 10719 :CBC 21 : get_rule_windowspec(wc, context->windowTList, context);
10720 : 21 : break;
10721 : : }
10722 : : }
10723 [ + + ]: 126 : if (l == NULL)
10724 : : {
10725 [ - + ]: 105 : if (context->windowClause)
5586 tgl@sss.pgh.pa.us 10726 [ # # ]:UBC 0 : elog(ERROR, "could not find window clause for winref %u",
10727 : : wfunc->winref);
10728 : :
10729 : : /*
10730 : : * In EXPLAIN, we don't have window context information available, so
10731 : : * we have to settle for this:
10732 : : */
5586 tgl@sss.pgh.pa.us 10733 :CBC 105 : appendStringInfoString(buf, "(?)");
10734 : : }
10735 : 126 : }
10736 : :
10737 : : /*
10738 : : * get_func_sql_syntax - Parse back a SQL-syntax function call
10739 : : *
10740 : : * Returns true if we successfully deparsed, false if we did not
10741 : : * recognize the function.
10742 : : */
10743 : : static bool
1257 10744 : 90 : get_func_sql_syntax(FuncExpr *expr, deparse_context *context)
10745 : : {
10746 : 90 : StringInfo buf = context->buf;
10747 : 90 : Oid funcoid = expr->funcid;
10748 : :
10749 [ + + + + : 90 : switch (funcoid)
+ + + + +
+ + + + +
+ - + ]
10750 : : {
10751 : 12 : case F_TIMEZONE_INTERVAL_TIMESTAMP:
10752 : : case F_TIMEZONE_INTERVAL_TIMESTAMPTZ:
10753 : : case F_TIMEZONE_INTERVAL_TIMETZ:
10754 : : case F_TIMEZONE_TEXT_TIMESTAMP:
10755 : : case F_TIMEZONE_TEXT_TIMESTAMPTZ:
10756 : : case F_TIMEZONE_TEXT_TIMETZ:
10757 : : /* AT TIME ZONE ... note reversed argument order */
10758 : 12 : appendStringInfoChar(buf, '(');
500 10759 : 12 : get_rule_expr_paren((Node *) lsecond(expr->args), context, false,
10760 : : (Node *) expr);
1257 10761 : 12 : appendStringInfoString(buf, " AT TIME ZONE ");
500 10762 : 12 : get_rule_expr_paren((Node *) linitial(expr->args), context, false,
10763 : : (Node *) expr);
1257 10764 : 12 : appendStringInfoChar(buf, ')');
10765 : 12 : return true;
10766 : :
184 michael@paquier.xyz 10767 :GNC 9 : case F_TIMEZONE_TIMESTAMP:
10768 : : case F_TIMEZONE_TIMESTAMPTZ:
10769 : : case F_TIMEZONE_TIMETZ:
10770 : : /* AT LOCAL */
10771 : 9 : appendStringInfoChar(buf, '(');
10772 : 9 : get_rule_expr_paren((Node *) linitial(expr->args), context, false,
10773 : : (Node *) expr);
10774 : 9 : appendStringInfoString(buf, " AT LOCAL)");
10775 : 9 : return true;
10776 : :
1257 tgl@sss.pgh.pa.us 10777 :CBC 3 : case F_OVERLAPS_TIMESTAMPTZ_INTERVAL_TIMESTAMPTZ_INTERVAL:
10778 : : case F_OVERLAPS_TIMESTAMPTZ_INTERVAL_TIMESTAMPTZ_TIMESTAMPTZ:
10779 : : case F_OVERLAPS_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ_INTERVAL:
10780 : : case F_OVERLAPS_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ:
10781 : : case F_OVERLAPS_TIMESTAMP_INTERVAL_TIMESTAMP_INTERVAL:
10782 : : case F_OVERLAPS_TIMESTAMP_INTERVAL_TIMESTAMP_TIMESTAMP:
10783 : : case F_OVERLAPS_TIMESTAMP_TIMESTAMP_TIMESTAMP_INTERVAL:
10784 : : case F_OVERLAPS_TIMESTAMP_TIMESTAMP_TIMESTAMP_TIMESTAMP:
10785 : : case F_OVERLAPS_TIMETZ_TIMETZ_TIMETZ_TIMETZ:
10786 : : case F_OVERLAPS_TIME_INTERVAL_TIME_INTERVAL:
10787 : : case F_OVERLAPS_TIME_INTERVAL_TIME_TIME:
10788 : : case F_OVERLAPS_TIME_TIME_TIME_INTERVAL:
10789 : : case F_OVERLAPS_TIME_TIME_TIME_TIME:
10790 : : /* (x1, x2) OVERLAPS (y1, y2) */
10791 : 3 : appendStringInfoString(buf, "((");
10792 : 3 : get_rule_expr((Node *) linitial(expr->args), context, false);
10793 : 3 : appendStringInfoString(buf, ", ");
10794 : 3 : get_rule_expr((Node *) lsecond(expr->args), context, false);
10795 : 3 : appendStringInfoString(buf, ") OVERLAPS (");
10796 : 3 : get_rule_expr((Node *) lthird(expr->args), context, false);
10797 : 3 : appendStringInfoString(buf, ", ");
10798 : 3 : get_rule_expr((Node *) lfourth(expr->args), context, false);
10799 : 3 : appendStringInfoString(buf, "))");
10800 : 3 : return true;
10801 : :
1104 peter@eisentraut.org 10802 : 9 : case F_EXTRACT_TEXT_DATE:
10803 : : case F_EXTRACT_TEXT_TIME:
10804 : : case F_EXTRACT_TEXT_TIMETZ:
10805 : : case F_EXTRACT_TEXT_TIMESTAMP:
10806 : : case F_EXTRACT_TEXT_TIMESTAMPTZ:
10807 : : case F_EXTRACT_TEXT_INTERVAL:
10808 : : /* EXTRACT (x FROM y) */
10809 : 9 : appendStringInfoString(buf, "EXTRACT(");
10810 : : {
10811 : 9 : Const *con = (Const *) linitial(expr->args);
10812 : :
10813 [ + - + - : 9 : Assert(IsA(con, Const) &&
- + ]
10814 : : con->consttype == TEXTOID &&
10815 : : !con->constisnull);
10816 : 9 : appendStringInfoString(buf, TextDatumGetCString(con->constvalue));
10817 : : }
10818 : 9 : appendStringInfoString(buf, " FROM ");
10819 : 9 : get_rule_expr((Node *) lsecond(expr->args), context, false);
10820 : 9 : appendStringInfoChar(buf, ')');
10821 : 9 : return true;
10822 : :
1257 tgl@sss.pgh.pa.us 10823 : 6 : case F_IS_NORMALIZED:
10824 : : /* IS xxx NORMALIZED */
194 drowley@postgresql.o 10825 :GNC 6 : appendStringInfoChar(buf, '(');
500 tgl@sss.pgh.pa.us 10826 :CBC 6 : get_rule_expr_paren((Node *) linitial(expr->args), context, false,
10827 : : (Node *) expr);
10828 : 6 : appendStringInfoString(buf, " IS");
1257 10829 [ + + ]: 6 : if (list_length(expr->args) == 2)
10830 : : {
10831 : 3 : Const *con = (Const *) lsecond(expr->args);
10832 : :
10833 [ + - + - : 3 : Assert(IsA(con, Const) &&
- + ]
10834 : : con->consttype == TEXTOID &&
10835 : : !con->constisnull);
10836 : 3 : appendStringInfo(buf, " %s",
10837 : 3 : TextDatumGetCString(con->constvalue));
10838 : : }
10839 : 6 : appendStringInfoString(buf, " NORMALIZED)");
10840 : 6 : return true;
10841 : :
10842 : 3 : case F_PG_COLLATION_FOR:
10843 : : /* COLLATION FOR */
10844 : 3 : appendStringInfoString(buf, "COLLATION FOR (");
10845 : 3 : get_rule_expr((Node *) linitial(expr->args), context, false);
10846 : 3 : appendStringInfoChar(buf, ')');
10847 : 3 : return true;
10848 : :
10849 : 6 : case F_NORMALIZE:
10850 : : /* NORMALIZE() */
10851 : 6 : appendStringInfoString(buf, "NORMALIZE(");
10852 : 6 : get_rule_expr((Node *) linitial(expr->args), context, false);
10853 [ + + ]: 6 : if (list_length(expr->args) == 2)
10854 : : {
10855 : 3 : Const *con = (Const *) lsecond(expr->args);
10856 : :
10857 [ + - + - : 3 : Assert(IsA(con, Const) &&
- + ]
10858 : : con->consttype == TEXTOID &&
10859 : : !con->constisnull);
10860 : 3 : appendStringInfo(buf, ", %s",
10861 : 3 : TextDatumGetCString(con->constvalue));
10862 : : }
10863 : 6 : appendStringInfoChar(buf, ')');
10864 : 6 : return true;
10865 : :
10866 : 6 : case F_OVERLAY_BIT_BIT_INT4:
10867 : : case F_OVERLAY_BIT_BIT_INT4_INT4:
10868 : : case F_OVERLAY_BYTEA_BYTEA_INT4:
10869 : : case F_OVERLAY_BYTEA_BYTEA_INT4_INT4:
10870 : : case F_OVERLAY_TEXT_TEXT_INT4:
10871 : : case F_OVERLAY_TEXT_TEXT_INT4_INT4:
10872 : : /* OVERLAY() */
10873 : 6 : appendStringInfoString(buf, "OVERLAY(");
10874 : 6 : get_rule_expr((Node *) linitial(expr->args), context, false);
10875 : 6 : appendStringInfoString(buf, " PLACING ");
10876 : 6 : get_rule_expr((Node *) lsecond(expr->args), context, false);
10877 : 6 : appendStringInfoString(buf, " FROM ");
10878 : 6 : get_rule_expr((Node *) lthird(expr->args), context, false);
10879 [ + + ]: 6 : if (list_length(expr->args) == 4)
10880 : : {
10881 : 3 : appendStringInfoString(buf, " FOR ");
10882 : 3 : get_rule_expr((Node *) lfourth(expr->args), context, false);
10883 : : }
10884 : 6 : appendStringInfoChar(buf, ')');
10885 : 6 : return true;
10886 : :
10887 : 3 : case F_POSITION_BIT_BIT:
10888 : : case F_POSITION_BYTEA_BYTEA:
10889 : : case F_POSITION_TEXT_TEXT:
10890 : : /* POSITION() ... extra parens since args are b_expr not a_expr */
10891 : 3 : appendStringInfoString(buf, "POSITION((");
10892 : 3 : get_rule_expr((Node *) lsecond(expr->args), context, false);
10893 : 3 : appendStringInfoString(buf, ") IN (");
10894 : 3 : get_rule_expr((Node *) linitial(expr->args), context, false);
10895 : 3 : appendStringInfoString(buf, "))");
10896 : 3 : return true;
10897 : :
10898 : 3 : case F_SUBSTRING_BIT_INT4:
10899 : : case F_SUBSTRING_BIT_INT4_INT4:
10900 : : case F_SUBSTRING_BYTEA_INT4:
10901 : : case F_SUBSTRING_BYTEA_INT4_INT4:
10902 : : case F_SUBSTRING_TEXT_INT4:
10903 : : case F_SUBSTRING_TEXT_INT4_INT4:
10904 : : /* SUBSTRING FROM/FOR (i.e., integer-position variants) */
10905 : 3 : appendStringInfoString(buf, "SUBSTRING(");
10906 : 3 : get_rule_expr((Node *) linitial(expr->args), context, false);
10907 : 3 : appendStringInfoString(buf, " FROM ");
10908 : 3 : get_rule_expr((Node *) lsecond(expr->args), context, false);
10909 [ + - ]: 3 : if (list_length(expr->args) == 3)
10910 : : {
10911 : 3 : appendStringInfoString(buf, " FOR ");
10912 : 3 : get_rule_expr((Node *) lthird(expr->args), context, false);
10913 : : }
10914 : 3 : appendStringInfoChar(buf, ')');
10915 : 3 : return true;
10916 : :
10917 : 3 : case F_SUBSTRING_TEXT_TEXT_TEXT:
10918 : : /* SUBSTRING SIMILAR/ESCAPE */
10919 : 3 : appendStringInfoString(buf, "SUBSTRING(");
10920 : 3 : get_rule_expr((Node *) linitial(expr->args), context, false);
10921 : 3 : appendStringInfoString(buf, " SIMILAR ");
10922 : 3 : get_rule_expr((Node *) lsecond(expr->args), context, false);
10923 : 3 : appendStringInfoString(buf, " ESCAPE ");
10924 : 3 : get_rule_expr((Node *) lthird(expr->args), context, false);
10925 : 3 : appendStringInfoChar(buf, ')');
10926 : 3 : return true;
10927 : :
10928 : 6 : case F_BTRIM_BYTEA_BYTEA:
10929 : : case F_BTRIM_TEXT:
10930 : : case F_BTRIM_TEXT_TEXT:
10931 : : /* TRIM() */
10932 : 6 : appendStringInfoString(buf, "TRIM(BOTH");
10933 [ + - ]: 6 : if (list_length(expr->args) == 2)
10934 : : {
10935 : 6 : appendStringInfoChar(buf, ' ');
10936 : 6 : get_rule_expr((Node *) lsecond(expr->args), context, false);
10937 : : }
10938 : 6 : appendStringInfoString(buf, " FROM ");
10939 : 6 : get_rule_expr((Node *) linitial(expr->args), context, false);
10940 : 6 : appendStringInfoChar(buf, ')');
10941 : 6 : return true;
10942 : :
1182 10943 : 6 : case F_LTRIM_BYTEA_BYTEA:
10944 : : case F_LTRIM_TEXT:
10945 : : case F_LTRIM_TEXT_TEXT:
10946 : : /* TRIM() */
1257 10947 : 6 : appendStringInfoString(buf, "TRIM(LEADING");
10948 [ + - ]: 6 : if (list_length(expr->args) == 2)
10949 : : {
10950 : 6 : appendStringInfoChar(buf, ' ');
10951 : 6 : get_rule_expr((Node *) lsecond(expr->args), context, false);
10952 : : }
10953 : 6 : appendStringInfoString(buf, " FROM ");
10954 : 6 : get_rule_expr((Node *) linitial(expr->args), context, false);
10955 : 6 : appendStringInfoChar(buf, ')');
10956 : 6 : return true;
10957 : :
1182 10958 : 6 : case F_RTRIM_BYTEA_BYTEA:
10959 : : case F_RTRIM_TEXT:
10960 : : case F_RTRIM_TEXT_TEXT:
10961 : : /* TRIM() */
1257 10962 : 6 : appendStringInfoString(buf, "TRIM(TRAILING");
10963 [ + + ]: 6 : if (list_length(expr->args) == 2)
10964 : : {
10965 : 3 : appendStringInfoChar(buf, ' ');
10966 : 3 : get_rule_expr((Node *) lsecond(expr->args), context, false);
10967 : : }
10968 : 6 : appendStringInfoString(buf, " FROM ");
10969 : 6 : get_rule_expr((Node *) linitial(expr->args), context, false);
10970 : 6 : appendStringInfoChar(buf, ')');
10971 : 6 : return true;
10972 : :
563 michael@paquier.xyz 10973 : 6 : case F_SYSTEM_USER:
10974 : 6 : appendStringInfoString(buf, "SYSTEM_USER");
10975 : 6 : return true;
10976 : :
1257 tgl@sss.pgh.pa.us 10977 :UBC 0 : case F_XMLEXISTS:
10978 : : /* XMLEXISTS ... extra parens because args are c_expr */
10979 : 0 : appendStringInfoString(buf, "XMLEXISTS((");
10980 : 0 : get_rule_expr((Node *) linitial(expr->args), context, false);
10981 : 0 : appendStringInfoString(buf, ") PASSING (");
10982 : 0 : get_rule_expr((Node *) lsecond(expr->args), context, false);
10983 : 0 : appendStringInfoString(buf, "))");
10984 : 0 : return true;
10985 : : }
1257 tgl@sss.pgh.pa.us 10986 :CBC 3 : return false;
10987 : : }
10988 : :
10989 : : /* ----------
10990 : : * get_coercion_expr
10991 : : *
10992 : : * Make a string representation of a value coerced to a specific type
10993 : : * ----------
10994 : : */
10995 : : static void
6238 10996 : 2189 : get_coercion_expr(Node *arg, deparse_context *context,
10997 : : Oid resulttype, int32 resulttypmod,
10998 : : Node *parentNode)
10999 : : {
11000 : 2189 : StringInfo buf = context->buf;
11001 : :
11002 : : /*
11003 : : * Since parse_coerce.c doesn't immediately collapse application of
11004 : : * length-coercion functions to constants, what we'll typically see in
11005 : : * such cases is a Const with typmod -1 and a length-coercion function
11006 : : * right above it. Avoid generating redundant output. However, beware of
11007 : : * suppressing casts when the user actually wrote something like
11008 : : * 'foo'::text::char(3).
11009 : : *
11010 : : * Note: it might seem that we are missing the possibility of needing to
11011 : : * print a COLLATE clause for such a Const. However, a Const could only
11012 : : * have nondefault collation in a post-constant-folding tree, in which the
11013 : : * length coercion would have been folded too. See also the special
11014 : : * handling of CollateExpr in coerce_to_target_type(): any collation
11015 : : * marking will be above the coercion node, not below it.
11016 : : */
11017 [ + - + + ]: 2189 : if (arg && IsA(arg, Const) &&
11018 [ + + ]: 234 : ((Const *) arg)->consttype == resulttype &&
11019 [ + - ]: 12 : ((Const *) arg)->consttypmod == -1)
11020 : : {
11021 : : /* Show the constant without normal ::typename decoration */
5943 11022 : 12 : get_const_expr((Const *) arg, context, -1);
11023 : : }
11024 : : else
11025 : : {
6238 11026 [ + + ]: 2177 : if (!PRETTY_PAREN(context))
11027 : 1998 : appendStringInfoChar(buf, '(');
11028 : 2177 : get_rule_expr_paren(arg, context, false, parentNode);
11029 [ + + ]: 2177 : if (!PRETTY_PAREN(context))
11030 : 1998 : appendStringInfoChar(buf, ')');
11031 : : }
11032 : :
11033 : : /*
11034 : : * Never emit resulttype(arg) functional notation. A pg_proc entry could
11035 : : * take precedence, and a resulttype in pg_temp would require schema
11036 : : * qualification that format_type_with_typemod() would usually omit. We've
11037 : : * standardized on arg::resulttype, but CAST(arg AS resulttype) notation
11038 : : * would work fine.
11039 : : */
11040 : 2189 : appendStringInfo(buf, "::%s",
11041 : : format_type_with_typemod(resulttype, resulttypmod));
11042 : 2189 : }
11043 : :
11044 : : /* ----------
11045 : : * get_const_expr
11046 : : *
11047 : : * Make a string representation of a Const
11048 : : *
11049 : : * showtype can be -1 to never show "::typename" decoration, or +1 to always
11050 : : * show it, or 0 to show it only if the constant wouldn't be assumed to be
11051 : : * the right type by default.
11052 : : *
11053 : : * If the Const's collation isn't default for its type, show that too.
11054 : : * We mustn't do this when showtype is -1 (since that means the caller will
11055 : : * print "::typename", and we can't put a COLLATE clause in between). It's
11056 : : * caller's responsibility that collation isn't missed in such cases.
11057 : : * ----------
11058 : : */
11059 : : static void
5943 11060 : 31500 : get_const_expr(Const *constval, deparse_context *context, int showtype)
11061 : : {
8960 11062 : 31500 : StringInfo buf = context->buf;
11063 : : Oid typoutput;
11064 : : bool typIsVarlena;
11065 : : char *extval;
3303 11066 : 31500 : bool needlabel = false;
11067 : :
8878 11068 [ + + ]: 31500 : if (constval->constisnull)
11069 : : {
11070 : : /*
11071 : : * Always label the type of a NULL constant to prevent misdecisions
11072 : : * about type when reparsing.
11073 : : */
3818 rhaas@postgresql.org 11074 : 415 : appendStringInfoString(buf, "NULL");
5943 tgl@sss.pgh.pa.us 11075 [ + + ]: 415 : if (showtype >= 0)
11076 : : {
6238 11077 : 393 : appendStringInfo(buf, "::%s",
11078 : : format_type_with_typemod(constval->consttype,
11079 : : constval->consttypmod));
4783 11080 : 393 : get_const_collation(constval, context);
11081 : : }
8878 11082 : 4399 : return;
11083 : : }
11084 : :
7252 11085 : 31085 : getTypeOutputInfo(constval->consttype,
11086 : : &typoutput, &typIsVarlena);
11087 : :
6585 11088 : 31085 : extval = OidOutputFunctionCall(typoutput, constval->constvalue);
11089 : :
8959 11090 [ + + + + ]: 31085 : switch (constval->consttype)
11091 : : {
11092 : 17479 : case INT4OID:
11093 : :
11094 : : /*
11095 : : * INT4 can be printed without any decoration, unless it is
11096 : : * negative; in that case print it as '-nnn'::integer to ensure
11097 : : * that the output will re-parse as a constant, not as a constant
11098 : : * plus operator. In most cases we could get away with printing
11099 : : * (-nnn) instead, because of the way that gram.y handles negative
11100 : : * literals; but that doesn't work for INT_MIN, and it doesn't
11101 : : * seem that much prettier anyway.
11102 : : */
3303 11103 [ + + ]: 17479 : if (extval[0] != '-')
11104 : 17219 : appendStringInfoString(buf, extval);
11105 : : else
11106 : : {
11107 : 260 : appendStringInfo(buf, "'%s'", extval);
2489 11108 : 260 : needlabel = true; /* we must attach a cast */
11109 : : }
3303 11110 : 17479 : break;
11111 : :
7910 peter_e@gmx.net 11112 : 530 : case NUMERICOID:
11113 : :
11114 : : /*
11115 : : * NUMERIC can be printed without quotes if it looks like a float
11116 : : * constant (not an integer, and not Infinity or NaN) and doesn't
11117 : : * have a leading sign (for the same reason as for INT4).
11118 : : */
3303 tgl@sss.pgh.pa.us 11119 [ + - ]: 530 : if (isdigit((unsigned char) extval[0]) &&
11120 [ + + ]: 530 : strcspn(extval, "eE.") != strlen(extval))
11121 : : {
11122 : 190 : appendStringInfoString(buf, extval);
11123 : : }
11124 : : else
11125 : : {
11126 : 340 : appendStringInfo(buf, "'%s'", extval);
2489 11127 : 340 : needlabel = true; /* we must attach a cast */
11128 : : }
7893 bruce@momjian.us 11129 : 530 : break;
11130 : :
7910 peter_e@gmx.net 11131 : 729 : case BOOLOID:
7893 bruce@momjian.us 11132 [ + + ]: 729 : if (strcmp(extval, "t") == 0)
3818 rhaas@postgresql.org 11133 : 359 : appendStringInfoString(buf, "true");
11134 : : else
11135 : 370 : appendStringInfoString(buf, "false");
7910 peter_e@gmx.net 11136 : 729 : break;
11137 : :
11138 : 12347 : default:
5699 tgl@sss.pgh.pa.us 11139 : 12347 : simple_quote_literal(buf, extval);
8959 11140 : 12347 : break;
11141 : : }
11142 : :
8961 11143 : 31085 : pfree(extval);
11144 : :
5943 11145 [ + + ]: 31085 : if (showtype < 0)
6238 11146 : 3984 : return;
11147 : :
11148 : : /*
11149 : : * For showtype == 0, append ::typename unless the constant will be
11150 : : * implicitly typed as the right type when it is read in.
11151 : : *
11152 : : * XXX this code has to be kept in sync with the behavior of the parser,
11153 : : * especially make_const.
11154 : : */
8959 11155 [ + + + + ]: 27101 : switch (constval->consttype)
11156 : : {
7910 peter_e@gmx.net 11157 : 745 : case BOOLOID:
11158 : : case UNKNOWNOID:
11159 : : /* These types can be left unlabeled */
7879 tgl@sss.pgh.pa.us 11160 : 745 : needlabel = false;
11161 : 745 : break;
3303 11162 : 15374 : case INT4OID:
11163 : : /* We determined above whether a label is needed */
11164 : 15374 : break;
7879 11165 : 530 : case NUMERICOID:
11166 : :
11167 : : /*
11168 : : * Float-looking constants will be typed as numeric, which we
11169 : : * checked above; but if there's a nondefault typmod we need to
11170 : : * show it.
11171 : : */
3303 11172 : 530 : needlabel |= (constval->consttypmod >= 0);
8959 11173 : 530 : break;
11174 : 10452 : default:
7879 11175 : 10452 : needlabel = true;
8959 11176 : 10452 : break;
11177 : : }
5943 11178 [ + + - + ]: 27101 : if (needlabel || showtype > 0)
7879 11179 : 11045 : appendStringInfo(buf, "::%s",
11180 : : format_type_with_typemod(constval->consttype,
11181 : : constval->consttypmod));
11182 : :
4783 11183 : 27101 : get_const_collation(constval, context);
11184 : : }
11185 : :
11186 : : /*
11187 : : * helper for get_const_expr: append COLLATE if needed
11188 : : */
11189 : : static void
11190 : 27494 : get_const_collation(Const *constval, deparse_context *context)
11191 : : {
11192 : 27494 : StringInfo buf = context->buf;
11193 : :
11194 [ + + ]: 27494 : if (OidIsValid(constval->constcollid))
11195 : : {
4753 bruce@momjian.us 11196 : 3911 : Oid typcollation = get_typcollation(constval->consttype);
11197 : :
4783 tgl@sss.pgh.pa.us 11198 [ + + ]: 3911 : if (constval->constcollid != typcollation)
11199 : : {
11200 : 35 : appendStringInfo(buf, " COLLATE %s",
11201 : : generate_collation_name(constval->constcollid));
11202 : : }
11203 : : }
9326 bruce@momjian.us 11204 : 27494 : }
11205 : :
11206 : : /*
11207 : : * get_json_path_spec - Parse back a JSON path specification
11208 : : */
11209 : : static void
24 amitlan@postgresql.o 11210 :GNC 216 : get_json_path_spec(Node *path_spec, deparse_context *context, bool showimplicit)
11211 : : {
11212 [ + - ]: 216 : if (IsA(path_spec, Const))
11213 : 216 : get_const_expr((Const *) path_spec, context, -1);
11214 : : else
24 amitlan@postgresql.o 11215 :UNC 0 : get_rule_expr(path_spec, context, showimplicit);
24 amitlan@postgresql.o 11216 :GNC 216 : }
11217 : :
11218 : : /*
11219 : : * get_json_format - Parse back a JsonFormat node
11220 : : */
11221 : : static void
382 alvherre@alvh.no-ip. 11222 :CBC 72 : get_json_format(JsonFormat *format, StringInfo buf)
11223 : : {
11224 [ + + ]: 72 : if (format->format_type == JS_FORMAT_DEFAULT)
382 alvherre@alvh.no-ip. 11225 :GBC 42 : return;
11226 : :
382 alvherre@alvh.no-ip. 11227 :UBC 0 : appendStringInfoString(buf,
382 alvherre@alvh.no-ip. 11228 [ - + ]:CBC 30 : format->format_type == JS_FORMAT_JSONB ?
11229 : : " FORMAT JSONB" : " FORMAT JSON");
11230 : :
11231 [ + + ]: 30 : if (format->encoding != JS_ENC_DEFAULT)
11232 : : {
11233 : : const char *encoding;
11234 : :
382 alvherre@alvh.no-ip. 11235 :GBC 3 : encoding =
11236 [ + - ]: 6 : format->encoding == JS_ENC_UTF16 ? "UTF16" :
11237 [ - + ]: 3 : format->encoding == JS_ENC_UTF32 ? "UTF32" : "UTF8";
11238 : :
11239 : 3 : appendStringInfo(buf, " ENCODING %s", encoding);
11240 : : }
11241 : : }
11242 : :
11243 : : /*
11244 : : * get_json_returning - Parse back a JsonReturning structure
11245 : : */
11246 : : static void
382 alvherre@alvh.no-ip. 11247 :CBC 66 : get_json_returning(JsonReturning *returning, StringInfo buf,
11248 : : bool json_format_by_default)
11249 : : {
11250 [ - + ]: 66 : if (!OidIsValid(returning->typid))
382 alvherre@alvh.no-ip. 11251 :UBC 0 : return;
11252 : :
382 alvherre@alvh.no-ip. 11253 :CBC 66 : appendStringInfo(buf, " RETURNING %s",
11254 : : format_type_with_typemod(returning->typid,
11255 : : returning->typmod));
11256 : :
11257 [ + + + + ]: 126 : if (!json_format_by_default ||
11258 : 60 : returning->format->format_type !=
11259 [ + + ]: 60 : (returning->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON))
382 alvherre@alvh.no-ip. 11260 :GBC 18 : get_json_format(returning->format, buf);
11261 : : }
11262 : :
11263 : : /*
11264 : : * get_json_constructor - Parse back a JsonConstructorExpr node
11265 : : */
11266 : : static void
382 alvherre@alvh.no-ip. 11267 :CBC 69 : get_json_constructor(JsonConstructorExpr *ctor, deparse_context *context,
11268 : : bool showimplicit)
11269 : : {
11270 : 69 : StringInfo buf = context->buf;
11271 : : const char *funcname;
11272 : : bool is_json_object;
11273 : : int curridx;
11274 : : ListCell *lc;
11275 : :
11276 [ + + ]: 69 : if (ctor->type == JSCTOR_JSON_OBJECTAGG)
11277 : : {
11278 : 9 : get_json_agg_constructor(ctor, context, "JSON_OBJECTAGG", true);
11279 : 9 : return;
11280 : : }
11281 [ + + ]: 60 : else if (ctor->type == JSCTOR_JSON_ARRAYAGG)
11282 : : {
11283 : 15 : get_json_agg_constructor(ctor, context, "JSON_ARRAYAGG", false);
11284 : 15 : return;
11285 : : }
11286 : :
11287 [ + + + + : 45 : switch (ctor->type)
+ - ]
11288 : : {
11289 : 6 : case JSCTOR_JSON_OBJECT:
11290 : 6 : funcname = "JSON_OBJECT";
11291 : 6 : break;
11292 : 6 : case JSCTOR_JSON_ARRAY:
11293 : 6 : funcname = "JSON_ARRAY";
11294 : 6 : break;
269 amitlan@postgresql.o 11295 :GNC 21 : case JSCTOR_JSON_PARSE:
11296 : 21 : funcname = "JSON";
11297 : 21 : break;
11298 : 6 : case JSCTOR_JSON_SCALAR:
11299 : 6 : funcname = "JSON_SCALAR";
11300 : 6 : break;
11301 : 6 : case JSCTOR_JSON_SERIALIZE:
11302 : 6 : funcname = "JSON_SERIALIZE";
11303 : 6 : break;
382 alvherre@alvh.no-ip. 11304 :UBC 0 : default:
381 11305 [ # # ]: 0 : elog(ERROR, "invalid JsonConstructorType %d", ctor->type);
11306 : : }
11307 : :
382 alvherre@alvh.no-ip. 11308 :CBC 45 : appendStringInfo(buf, "%s(", funcname);
11309 : :
11310 : 45 : is_json_object = ctor->type == JSCTOR_JSON_OBJECT;
11311 [ + - + + : 114 : foreach(lc, ctor->args)
+ + ]
11312 : : {
11313 : 69 : curridx = foreach_current_index(lc);
11314 [ + + ]: 69 : if (curridx > 0)
11315 : : {
11316 : : const char *sep;
11317 : :
11318 [ + + + + ]: 24 : sep = (is_json_object && (curridx % 2) != 0) ? " : " : ", ";
11319 : 24 : appendStringInfoString(buf, sep);
11320 : : }
11321 : :
11322 : 69 : get_rule_expr((Node *) lfirst(lc), context, true);
11323 : : }
11324 : :
11325 : 45 : get_json_constructor_options(ctor, buf);
194 drowley@postgresql.o 11326 :GNC 45 : appendStringInfoChar(buf, ')');
11327 : : }
11328 : :
11329 : : /*
11330 : : * Append options, if any, to the JSON constructor being deparsed
11331 : : */
11332 : : static void
382 alvherre@alvh.no-ip. 11333 :CBC 69 : get_json_constructor_options(JsonConstructorExpr *ctor, StringInfo buf)
11334 : : {
11335 [ + + ]: 69 : if (ctor->absent_on_null)
11336 : : {
11337 [ + - ]: 12 : if (ctor->type == JSCTOR_JSON_OBJECT ||
11338 [ - + ]: 12 : ctor->type == JSCTOR_JSON_OBJECTAGG)
382 alvherre@alvh.no-ip. 11339 :UBC 0 : appendStringInfoString(buf, " ABSENT ON NULL");
11340 : : }
11341 : : else
11342 : : {
382 alvherre@alvh.no-ip. 11343 [ + - ]:CBC 57 : if (ctor->type == JSCTOR_JSON_ARRAY ||
11344 [ + + ]: 57 : ctor->type == JSCTOR_JSON_ARRAYAGG)
11345 : 9 : appendStringInfoString(buf, " NULL ON NULL");
11346 : : }
11347 : :
11348 [ + + ]: 69 : if (ctor->unique)
11349 : 12 : appendStringInfoString(buf, " WITH UNIQUE KEYS");
11350 : :
11351 : : /*
11352 : : * Append RETURNING clause if needed; JSON() and JSON_SCALAR() don't
11353 : : * support one.
11354 : : */
269 amitlan@postgresql.o 11355 [ + + + + ]:GNC 69 : if (ctor->type != JSCTOR_JSON_PARSE && ctor->type != JSCTOR_JSON_SCALAR)
11356 : 42 : get_json_returning(ctor->returning, buf, true);
382 alvherre@alvh.no-ip. 11357 :CBC 69 : }
11358 : :
11359 : : /*
11360 : : * get_json_agg_constructor - Parse back an aggregate JsonConstructorExpr node
11361 : : */
11362 : : static void
11363 : 24 : get_json_agg_constructor(JsonConstructorExpr *ctor, deparse_context *context,
11364 : : const char *funcname, bool is_json_objectagg)
11365 : : {
11366 : : StringInfoData options;
11367 : :
11368 : 24 : initStringInfo(&options);
11369 : 24 : get_json_constructor_options(ctor, &options);
11370 : :
11371 [ + + ]: 24 : if (IsA(ctor->func, Aggref))
11372 : 18 : get_agg_expr_helper((Aggref *) ctor->func, context,
11373 : 18 : (Aggref *) ctor->func,
11374 : 18 : funcname, options.data, is_json_objectagg);
11375 [ + - ]: 6 : else if (IsA(ctor->func, WindowFunc))
11376 : 6 : get_windowfunc_expr_helper((WindowFunc *) ctor->func, context,
11377 : 6 : funcname, options.data,
11378 : : is_json_objectagg);
11379 : : else
382 alvherre@alvh.no-ip. 11380 [ # # ]:UBC 0 : elog(ERROR, "invalid JsonConstructorExpr underlying node type: %d",
11381 : : nodeTag(ctor->func));
382 alvherre@alvh.no-ip. 11382 :CBC 24 : }
11383 : :
11384 : : /*
11385 : : * simple_quote_literal - Format a string as a SQL literal, append to buf
11386 : : */
11387 : : static void
5699 tgl@sss.pgh.pa.us 11388 : 12875 : simple_quote_literal(StringInfo buf, const char *val)
11389 : : {
11390 : : const char *valptr;
11391 : :
11392 : : /*
11393 : : * We form the string literal according to the prevailing setting of
11394 : : * standard_conforming_strings; we never use E''. User is responsible for
11395 : : * making sure result is used correctly.
11396 : : */
11397 : 12875 : appendStringInfoChar(buf, '\'');
11398 [ + + ]: 130872 : for (valptr = val; *valptr; valptr++)
11399 : : {
11400 : 117997 : char ch = *valptr;
11401 : :
11402 [ + + + + : 117997 : if (SQL_STR_DOUBLE(ch, !standard_conforming_strings))
- + ]
11403 : 153 : appendStringInfoChar(buf, ch);
11404 : 117997 : appendStringInfoChar(buf, ch);
11405 : : }
11406 : 12875 : appendStringInfoChar(buf, '\'');
11407 : 12875 : }
11408 : :
11409 : :
11410 : : /* ----------
11411 : : * get_sublink_expr - Parse back a sublink
11412 : : * ----------
11413 : : */
11414 : : static void
7794 11415 : 203 : get_sublink_expr(SubLink *sublink, deparse_context *context)
11416 : : {
8960 11417 : 203 : StringInfo buf = context->buf;
9326 bruce@momjian.us 11418 : 203 : Query *query = (Query *) (sublink->subselect);
6682 tgl@sss.pgh.pa.us 11419 : 203 : char *opname = NULL;
11420 : : bool need_paren;
11421 : :
7677 11422 [ + + ]: 203 : if (sublink->subLinkType == ARRAY_SUBLINK)
3818 rhaas@postgresql.org 11423 : 8 : appendStringInfoString(buf, "ARRAY(");
11424 : : else
7677 tgl@sss.pgh.pa.us 11425 : 195 : appendStringInfoChar(buf, '(');
11426 : :
11427 : : /*
11428 : : * Note that we print the name of only the first operator, when there are
11429 : : * multiple combining operators. This is an approximation that could go
11430 : : * wrong in various scenarios (operators in different schemas, renamed
11431 : : * operators, etc) but there is not a whole lot we can do about it, since
11432 : : * the syntax allows only one operator to be shown.
11433 : : */
6682 11434 [ + + ]: 203 : if (sublink->testexpr)
11435 : : {
11436 [ + + ]: 9 : if (IsA(sublink->testexpr, OpExpr))
11437 : : {
11438 : : /* single combining operator */
6402 bruce@momjian.us 11439 : 3 : OpExpr *opexpr = (OpExpr *) sublink->testexpr;
11440 : :
6682 tgl@sss.pgh.pa.us 11441 : 3 : get_rule_expr(linitial(opexpr->args), context, true);
11442 : 3 : opname = generate_operator_name(opexpr->opno,
11443 : 3 : exprType(linitial(opexpr->args)),
11444 : 3 : exprType(lsecond(opexpr->args)));
11445 : : }
11446 [ + + ]: 6 : else if (IsA(sublink->testexpr, BoolExpr))
11447 : : {
11448 : : /* multiple combining operators, = or <> cases */
11449 : : char *sep;
11450 : : ListCell *l;
11451 : :
8820 11452 : 3 : appendStringInfoChar(buf, '(');
6682 11453 : 3 : sep = "";
11454 [ + - + + : 9 : foreach(l, ((BoolExpr *) sublink->testexpr)->args)
+ + ]
11455 : : {
2561 11456 : 6 : OpExpr *opexpr = lfirst_node(OpExpr, l);
11457 : :
6682 11458 : 6 : appendStringInfoString(buf, sep);
11459 : 6 : get_rule_expr(linitial(opexpr->args), context, true);
11460 [ + + ]: 6 : if (!opname)
11461 : 3 : opname = generate_operator_name(opexpr->opno,
2489 11462 : 3 : exprType(linitial(opexpr->args)),
11463 : 3 : exprType(lsecond(opexpr->args)));
6682 11464 : 6 : sep = ", ";
11465 : : }
7129 11466 : 3 : appendStringInfoChar(buf, ')');
11467 : : }
6682 11468 [ + - ]: 3 : else if (IsA(sublink->testexpr, RowCompareExpr))
11469 : : {
11470 : : /* multiple combining operators, < <= > >= cases */
11471 : 3 : RowCompareExpr *rcexpr = (RowCompareExpr *) sublink->testexpr;
11472 : :
11473 : 3 : appendStringInfoChar(buf, '(');
11474 : 3 : get_rule_expr((Node *) rcexpr->largs, context, true);
11475 : 3 : opname = generate_operator_name(linitial_oid(rcexpr->opnos),
11476 : 3 : exprType(linitial(rcexpr->largs)),
2489 11477 : 3 : exprType(linitial(rcexpr->rargs)));
6682 11478 : 3 : appendStringInfoChar(buf, ')');
11479 : : }
11480 : : else
6682 tgl@sss.pgh.pa.us 11481 [ # # ]:UBC 0 : elog(ERROR, "unrecognized testexpr type: %d",
11482 : : (int) nodeTag(sublink->testexpr));
11483 : : }
11484 : :
8917 tgl@sss.pgh.pa.us 11485 :CBC 203 : need_paren = true;
11486 : :
9091 bruce@momjian.us 11487 [ + + + - : 203 : switch (sublink->subLinkType)
+ - ]
11488 : : {
9326 11489 : 86 : case EXISTS_SUBLINK:
3818 rhaas@postgresql.org 11490 : 86 : appendStringInfoString(buf, "EXISTS ");
9326 bruce@momjian.us 11491 : 86 : break;
11492 : :
11493 : 6 : case ANY_SUBLINK:
2489 tgl@sss.pgh.pa.us 11494 [ + + ]: 6 : if (strcmp(opname, "=") == 0) /* Represent = ANY as IN */
3818 rhaas@postgresql.org 11495 : 3 : appendStringInfoString(buf, " IN ");
11496 : : else
6682 tgl@sss.pgh.pa.us 11497 : 3 : appendStringInfo(buf, " %s ANY ", opname);
9326 bruce@momjian.us 11498 : 6 : break;
11499 : :
11500 : 3 : case ALL_SUBLINK:
6682 tgl@sss.pgh.pa.us 11501 : 3 : appendStringInfo(buf, " %s ALL ", opname);
9326 bruce@momjian.us 11502 : 3 : break;
11503 : :
6682 tgl@sss.pgh.pa.us 11504 :UBC 0 : case ROWCOMPARE_SUBLINK:
11505 : 0 : appendStringInfo(buf, " %s ", opname);
9326 bruce@momjian.us 11506 : 0 : break;
11507 : :
8917 tgl@sss.pgh.pa.us 11508 :CBC 108 : case EXPR_SUBLINK:
11509 : : case MULTIEXPR_SUBLINK:
11510 : : case ARRAY_SUBLINK:
11511 : 108 : need_paren = false;
11512 : 108 : break;
11513 : :
5671 tgl@sss.pgh.pa.us 11514 :UBC 0 : case CTE_SUBLINK: /* shouldn't occur in a SubLink */
11515 : : default:
7567 11516 [ # # ]: 0 : elog(ERROR, "unrecognized sublink type: %d",
11517 : : (int) sublink->subLinkType);
11518 : : break;
11519 : : }
11520 : :
8917 tgl@sss.pgh.pa.us 11521 [ + + ]:CBC 203 : if (need_paren)
8820 11522 : 95 : appendStringInfoChar(buf, '(');
11523 : :
694 11524 : 203 : get_query_def(query, buf, context->namespaces, NULL, false,
11525 : : context->prettyFlags, context->wrapColumn,
11526 : : context->indentLevel);
11527 : :
8917 11528 [ + + ]: 203 : if (need_paren)
3818 rhaas@postgresql.org 11529 : 95 : appendStringInfoString(buf, "))");
11530 : : else
8820 tgl@sss.pgh.pa.us 11531 : 108 : appendStringInfoChar(buf, ')');
9365 bruce@momjian.us 11532 : 203 : }
11533 : :
11534 : :
11535 : : /* ----------
11536 : : * get_xmltable - Parse back a XMLTABLE function
11537 : : * ----------
11538 : : */
11539 : : static void
10 amitlan@postgresql.o 11540 :GNC 28 : get_xmltable(TableFunc *tf, deparse_context *context, bool showimplicit)
11541 : : {
2594 alvherre@alvh.no-ip. 11542 :CBC 28 : StringInfo buf = context->buf;
11543 : :
11544 : 28 : appendStringInfoString(buf, "XMLTABLE(");
11545 : :
11546 [ + + ]: 28 : if (tf->ns_uris != NIL)
11547 : : {
11548 : : ListCell *lc1,
11549 : : *lc2;
11550 : 5 : bool first = true;
11551 : :
11552 : 5 : appendStringInfoString(buf, "XMLNAMESPACES (");
11553 [ + - + + : 10 : forboth(lc1, tf->ns_uris, lc2, tf->ns_names)
+ - + + +
+ + - +
+ ]
11554 : : {
11555 : 5 : Node *expr = (Node *) lfirst(lc1);
948 peter@eisentraut.org 11556 : 5 : String *ns_node = lfirst_node(String, lc2);
11557 : :
2594 alvherre@alvh.no-ip. 11558 [ - + ]: 5 : if (!first)
2594 alvherre@alvh.no-ip. 11559 :UBC 0 : appendStringInfoString(buf, ", ");
11560 : : else
2594 alvherre@alvh.no-ip. 11561 :CBC 5 : first = false;
11562 : :
2036 tgl@sss.pgh.pa.us 11563 [ + - ]: 5 : if (ns_node != NULL)
11564 : : {
2594 alvherre@alvh.no-ip. 11565 : 5 : get_rule_expr(expr, context, showimplicit);
2036 tgl@sss.pgh.pa.us 11566 : 5 : appendStringInfo(buf, " AS %s", strVal(ns_node));
11567 : : }
11568 : : else
11569 : : {
2594 alvherre@alvh.no-ip. 11570 :UBC 0 : appendStringInfoString(buf, "DEFAULT ");
11571 : 0 : get_rule_expr(expr, context, showimplicit);
11572 : : }
11573 : : }
2594 alvherre@alvh.no-ip. 11574 :CBC 5 : appendStringInfoString(buf, "), ");
11575 : : }
11576 : :
11577 : 28 : appendStringInfoChar(buf, '(');
11578 : 28 : get_rule_expr((Node *) tf->rowexpr, context, showimplicit);
11579 : 28 : appendStringInfoString(buf, ") PASSING (");
11580 : 28 : get_rule_expr((Node *) tf->docexpr, context, showimplicit);
11581 : 28 : appendStringInfoChar(buf, ')');
11582 : :
11583 [ + - ]: 28 : if (tf->colexprs != NIL)
11584 : : {
11585 : : ListCell *l1;
11586 : : ListCell *l2;
11587 : : ListCell *l3;
11588 : : ListCell *l4;
11589 : : ListCell *l5;
11590 : 28 : int colnum = 0;
11591 : :
11592 : 28 : appendStringInfoString(buf, " COLUMNS ");
1872 tgl@sss.pgh.pa.us 11593 [ + - + + : 181 : forfive(l1, tf->colnames, l2, tf->coltypes, l3, tf->coltypmods,
+ - + + +
- + + + -
+ + + - +
+ + + + -
+ - + - +
- + + ]
11594 : : l4, tf->colexprs, l5, tf->coldefexprs)
11595 : : {
2594 alvherre@alvh.no-ip. 11596 : 153 : char *colname = strVal(lfirst(l1));
1872 tgl@sss.pgh.pa.us 11597 : 153 : Oid typid = lfirst_oid(l2);
11598 : 153 : int32 typmod = lfirst_int(l3);
11599 : 153 : Node *colexpr = (Node *) lfirst(l4);
11600 : 153 : Node *coldefexpr = (Node *) lfirst(l5);
11601 : 153 : bool ordinality = (tf->ordinalitycol == colnum);
2594 alvherre@alvh.no-ip. 11602 : 153 : bool notnull = bms_is_member(colnum, tf->notnulls);
11603 : :
11604 [ + + ]: 153 : if (colnum > 0)
11605 : 125 : appendStringInfoString(buf, ", ");
11606 : 153 : colnum++;
11607 : :
11608 [ + + ]: 289 : appendStringInfo(buf, "%s %s", quote_identifier(colname),
11609 : : ordinality ? "FOR ORDINALITY" :
11610 : 136 : format_type_with_typemod(typid, typmod));
11611 [ + + ]: 153 : if (ordinality)
11612 : 17 : continue;
11613 : :
11614 [ + + ]: 136 : if (coldefexpr != NULL)
11615 : : {
11616 : 17 : appendStringInfoString(buf, " DEFAULT (");
11617 : 17 : get_rule_expr((Node *) coldefexpr, context, showimplicit);
11618 : 17 : appendStringInfoChar(buf, ')');
11619 : : }
11620 [ + + ]: 136 : if (colexpr != NULL)
11621 : : {
11622 : 124 : appendStringInfoString(buf, " PATH (");
11623 : 124 : get_rule_expr((Node *) colexpr, context, showimplicit);
11624 : 124 : appendStringInfoChar(buf, ')');
11625 : : }
11626 [ + + ]: 136 : if (notnull)
11627 : 17 : appendStringInfoString(buf, " NOT NULL");
11628 : : }
11629 : : }
11630 : :
11631 : 28 : appendStringInfoChar(buf, ')');
11632 : 28 : }
11633 : :
11634 : : /*
11635 : : * get_json_nested_columns - Parse back nested JSON_TABLE columns
11636 : : */
11637 : : static void
6 amitlan@postgresql.o 11638 :GNC 51 : get_json_table_nested_columns(TableFunc *tf, JsonTablePlan *plan,
11639 : : deparse_context *context, bool showimplicit,
11640 : : bool needcomma)
11641 : : {
11642 [ + + ]: 51 : if (IsA(plan, JsonTablePathScan))
11643 : : {
11644 : 36 : JsonTablePathScan *scan = castNode(JsonTablePathScan, plan);
11645 : :
11646 [ + + ]: 36 : if (needcomma)
11647 : 24 : appendStringInfoChar(context->buf, ',');
11648 : :
11649 : 36 : appendStringInfoChar(context->buf, ' ');
11650 : 36 : appendContextKeyword(context, "NESTED PATH ", 0, 0, 0);
11651 : 36 : get_const_expr(scan->path->value, context, -1);
11652 : 36 : appendStringInfo(context->buf, " AS %s", quote_identifier(scan->path->name));
11653 : 36 : get_json_table_columns(tf, scan, context, showimplicit);
11654 : : }
11655 [ + - ]: 15 : else if (IsA(plan, JsonTableSiblingJoin))
11656 : : {
11657 : 15 : JsonTableSiblingJoin *join = (JsonTableSiblingJoin *) plan;
11658 : :
11659 : 15 : get_json_table_nested_columns(tf, join->lplan, context, showimplicit,
11660 : : needcomma);
11661 : 15 : get_json_table_nested_columns(tf, join->rplan, context, showimplicit,
11662 : : true);
11663 : : }
11664 : 51 : }
11665 : :
11666 : : /*
11667 : : * get_json_table_columns - Parse back JSON_TABLE columns
11668 : : */
11669 : : static void
11670 : 78 : get_json_table_columns(TableFunc *tf, JsonTablePathScan *scan,
11671 : : deparse_context *context,
11672 : : bool showimplicit)
11673 : : {
10 11674 : 78 : StringInfo buf = context->buf;
11675 : 78 : JsonExpr *jexpr = castNode(JsonExpr, tf->docexpr);
11676 : : ListCell *lc_colname;
11677 : : ListCell *lc_coltype;
11678 : : ListCell *lc_coltypmod;
11679 : : ListCell *lc_colvalexpr;
11680 : 78 : int colnum = 0;
11681 : :
11682 : 78 : appendStringInfoChar(buf, ' ');
11683 : 78 : appendContextKeyword(context, "COLUMNS (", 0, 0, 0);
11684 : :
11685 [ + + ]: 78 : if (PRETTY_INDENT(context))
11686 : 57 : context->indentLevel += PRETTYINDENT_VAR;
11687 : :
11688 [ + - + + : 405 : forfour(lc_colname, tf->colnames,
+ - + + +
- + + + -
+ + + + +
- + - + -
+ + ]
11689 : : lc_coltype, tf->coltypes,
11690 : : lc_coltypmod, tf->coltypmods,
11691 : : lc_colvalexpr, tf->colvalexprs)
11692 : : {
11693 : 351 : char *colname = strVal(lfirst(lc_colname));
11694 : : JsonExpr *colexpr;
11695 : : Oid typid;
11696 : : int32 typmod;
11697 : : bool ordinality;
11698 : : JsonBehaviorType default_behavior;
11699 : :
11700 : 351 : typid = lfirst_oid(lc_coltype);
11701 : 351 : typmod = lfirst_int(lc_coltypmod);
11702 : 351 : colexpr = castNode(JsonExpr, lfirst(lc_colvalexpr));
11703 : :
11704 : : /* Skip columns that don't belong to this scan. */
6 11705 [ + + + + ]: 351 : if (scan->colMin < 0 || colnum < scan->colMin)
11706 : : {
11707 : 132 : colnum++;
11708 : 132 : continue;
11709 : : }
11710 [ + + ]: 219 : if (colnum > scan->colMax)
11711 : 24 : break;
11712 : :
11713 [ + + ]: 195 : if (colnum > scan->colMin)
10 11714 : 129 : appendStringInfoString(buf, ", ");
11715 : :
11716 : 195 : colnum++;
11717 : :
11718 : 195 : ordinality = !colexpr;
11719 : :
11720 : 195 : appendContextKeyword(context, "", 0, 0, 0);
11721 : :
11722 [ + + ]: 381 : appendStringInfo(buf, "%s %s", quote_identifier(colname),
11723 : : ordinality ? "FOR ORDINALITY" :
11724 : 186 : format_type_with_typemod(typid, typmod));
11725 [ + + ]: 195 : if (ordinality)
11726 : 9 : continue;
11727 : :
11728 [ + + ]: 186 : if (colexpr->op == JSON_EXISTS_OP)
11729 : : {
11730 : 18 : appendStringInfoString(buf, " EXISTS");
11731 : 18 : default_behavior = JSON_BEHAVIOR_FALSE;
11732 : : }
11733 : : else
11734 : : {
11735 [ + + ]: 168 : if (colexpr->op == JSON_QUERY_OP)
11736 : : {
11737 : : char typcategory;
11738 : : bool typispreferred;
11739 : :
11740 : 87 : get_type_category_preferred(typid, &typcategory, &typispreferred);
11741 : :
11742 [ + + ]: 87 : if (typcategory == TYPCATEGORY_STRING)
10 amitlan@postgresql.o 11743 :UNC 0 : appendStringInfoString(buf,
10 amitlan@postgresql.o 11744 [ - + ]:GNC 18 : colexpr->format->format_type == JS_FORMAT_JSONB ?
11745 : : " FORMAT JSONB" : " FORMAT JSON");
11746 : : }
11747 : :
11748 : 168 : default_behavior = JSON_BEHAVIOR_NULL;
11749 : : }
11750 : :
11751 [ - + ]: 186 : if (jexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
10 amitlan@postgresql.o 11752 :UNC 0 : default_behavior = JSON_BEHAVIOR_ERROR;
11753 : :
10 amitlan@postgresql.o 11754 :GNC 186 : appendStringInfoString(buf, " PATH ");
11755 : :
11756 : 186 : get_json_path_spec(colexpr->path_spec, context, showimplicit);
11757 : :
11758 : 186 : get_json_expr_options(colexpr, context, default_behavior);
11759 : : }
11760 : :
6 11761 [ + + ]: 78 : if (scan->child)
11762 : 21 : get_json_table_nested_columns(tf, scan->child, context, showimplicit,
11763 : 21 : scan->colMin >= 0);
11764 : :
10 11765 [ + + ]: 78 : if (PRETTY_INDENT(context))
11766 : 57 : context->indentLevel -= PRETTYINDENT_VAR;
11767 : :
11768 : 78 : appendContextKeyword(context, ")", 0, 0, 0);
11769 : 78 : }
11770 : :
11771 : : /* ----------
11772 : : * get_json_table - Parse back a JSON_TABLE function
11773 : : * ----------
11774 : : */
11775 : : static void
11776 : 42 : get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit)
11777 : : {
11778 : 42 : StringInfo buf = context->buf;
11779 : 42 : JsonExpr *jexpr = castNode(JsonExpr, tf->docexpr);
11780 : 42 : JsonTablePathScan *root = castNode(JsonTablePathScan, tf->plan);
11781 : :
11782 : 42 : appendStringInfoString(buf, "JSON_TABLE(");
11783 : :
11784 [ + + ]: 42 : if (PRETTY_INDENT(context))
11785 : 21 : context->indentLevel += PRETTYINDENT_VAR;
11786 : :
11787 : 42 : appendContextKeyword(context, "", 0, 0, 0);
11788 : :
11789 : 42 : get_rule_expr(jexpr->formatted_expr, context, showimplicit);
11790 : :
11791 : 42 : appendStringInfoString(buf, ", ");
11792 : :
11793 : 42 : get_const_expr(root->path->value, context, -1);
11794 : :
11795 : 42 : appendStringInfo(buf, " AS %s", quote_identifier(root->path->name));
11796 : :
11797 [ + - ]: 42 : if (jexpr->passing_values)
11798 : : {
11799 : : ListCell *lc1,
11800 : : *lc2;
11801 : 42 : bool needcomma = false;
11802 : :
11803 : 42 : appendStringInfoChar(buf, ' ');
11804 : 42 : appendContextKeyword(context, "PASSING ", 0, 0, 0);
11805 : :
11806 [ + + ]: 42 : if (PRETTY_INDENT(context))
11807 : 21 : context->indentLevel += PRETTYINDENT_VAR;
11808 : :
11809 [ + - + + : 126 : forboth(lc1, jexpr->passing_names,
+ - + + +
+ + - +
+ ]
11810 : : lc2, jexpr->passing_values)
11811 : : {
11812 [ + + ]: 84 : if (needcomma)
11813 : 42 : appendStringInfoString(buf, ", ");
11814 : 84 : needcomma = true;
11815 : :
11816 : 84 : appendContextKeyword(context, "", 0, 0, 0);
11817 : :
11818 : 84 : get_rule_expr((Node *) lfirst(lc2), context, false);
11819 : 84 : appendStringInfo(buf, " AS %s",
11820 : 84 : quote_identifier((lfirst_node(String, lc1))->sval)
11821 : : );
11822 : : }
11823 : :
11824 [ + + ]: 42 : if (PRETTY_INDENT(context))
11825 : 21 : context->indentLevel -= PRETTYINDENT_VAR;
11826 : : }
11827 : :
6 11828 : 42 : get_json_table_columns(tf, castNode(JsonTablePathScan, tf->plan), context,
11829 : : showimplicit);
11830 : :
10 11831 [ - + ]: 42 : if (jexpr->on_error->btype != JSON_BEHAVIOR_EMPTY)
10 amitlan@postgresql.o 11832 :UNC 0 : get_json_behavior(jexpr->on_error, context, "ERROR");
11833 : :
10 amitlan@postgresql.o 11834 [ + + ]:GNC 42 : if (PRETTY_INDENT(context))
11835 : 21 : context->indentLevel -= PRETTYINDENT_VAR;
11836 : :
11837 : 42 : appendContextKeyword(context, ")", 0, 0, 0);
11838 : 42 : }
11839 : :
11840 : : /* ----------
11841 : : * get_tablefunc - Parse back a table function
11842 : : * ----------
11843 : : */
11844 : : static void
11845 : 70 : get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit)
11846 : : {
11847 : : /* XMLTABLE and JSON_TABLE are the only existing implementations. */
11848 : :
11849 [ + + ]: 70 : if (tf->functype == TFT_XMLTABLE)
11850 : 28 : get_xmltable(tf, context, showimplicit);
11851 [ + - ]: 42 : else if (tf->functype == TFT_JSON_TABLE)
11852 : 42 : get_json_table(tf, context, showimplicit);
11853 : 70 : }
11854 : :
11855 : : /* ----------
11856 : : * get_from_clause - Parse back a FROM clause
11857 : : *
11858 : : * "prefix" is the keyword that denotes the start of the list of FROM
11859 : : * elements. It is FROM when used to parse back SELECT and UPDATE, but
11860 : : * is USING when parsing back DELETE.
11861 : : * ----------
11862 : : */
11863 : : static void
6947 neilc@samurai.com 11864 :CBC 2173 : get_from_clause(Query *query, const char *prefix, deparse_context *context)
11865 : : {
8615 tgl@sss.pgh.pa.us 11866 : 2173 : StringInfo buf = context->buf;
7564 11867 : 2173 : bool first = true;
11868 : : ListCell *l;
11869 : :
11870 : : /*
11871 : : * We use the query's jointree as a guide to what to print. However, we
11872 : : * must ignore auto-added RTEs that are marked not inFromCl. (These can
11873 : : * only appear at the top level of the jointree, so it's sufficient to
11874 : : * check here.) This check also ensures we ignore the rule pseudo-RTEs
11875 : : * for NEW and OLD.
11876 : : */
8598 11877 [ + + + + : 4330 : foreach(l, query->jointree->fromlist)
+ + ]
11878 : : {
8424 bruce@momjian.us 11879 : 2157 : Node *jtnode = (Node *) lfirst(l);
11880 : :
8615 tgl@sss.pgh.pa.us 11881 [ + + ]: 2157 : if (IsA(jtnode, RangeTblRef))
11882 : : {
11883 : 1773 : int varno = ((RangeTblRef *) jtnode)->rtindex;
11884 : 1773 : RangeTblEntry *rte = rt_fetch(varno, query->rtable);
11885 : :
11886 [ + + ]: 1773 : if (!rte->inFromCl)
11887 : 186 : continue;
11888 : : }
11889 : :
7564 11890 [ + + ]: 1971 : if (first)
11891 : : {
6947 neilc@samurai.com 11892 : 1806 : appendContextKeyword(context, prefix,
11893 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
7564 tgl@sss.pgh.pa.us 11894 : 1806 : first = false;
11895 : :
4438 andrew@dunslane.net 11896 : 1806 : get_from_clause_item(jtnode, query, context);
11897 : : }
11898 : : else
11899 : : {
11900 : : StringInfoData itembuf;
11901 : :
7559 bruce@momjian.us 11902 : 165 : appendStringInfoString(buf, ", ");
11903 : :
11904 : : /*
11905 : : * Put the new FROM item's text into itembuf so we can decide
11906 : : * after we've got it whether or not it needs to go on a new line.
11907 : : */
4129 tgl@sss.pgh.pa.us 11908 : 165 : initStringInfo(&itembuf);
11909 : 165 : context->buf = &itembuf;
11910 : :
4438 andrew@dunslane.net 11911 : 165 : get_from_clause_item(jtnode, query, context);
11912 : :
11913 : : /* Restore context's output buffer */
11914 : 165 : context->buf = buf;
11915 : :
11916 : : /* Consider line-wrapping if enabled */
4129 tgl@sss.pgh.pa.us 11917 [ + - + - ]: 165 : if (PRETTY_INDENT(context) && context->wrapColumn >= 0)
11918 : : {
11919 : : /* Does the new item start with a new line? */
3807 11920 [ + - - + ]: 165 : if (itembuf.len > 0 && itembuf.data[0] == '\n')
11921 : : {
11922 : : /* If so, we shouldn't add anything */
11923 : : /* instead, remove any trailing spaces currently in buf */
3807 tgl@sss.pgh.pa.us 11924 :UBC 0 : removeStringInfoSpaces(buf);
11925 : : }
11926 : : else
11927 : : {
11928 : : char *trailing_nl;
11929 : :
11930 : : /* Locate the start of the current line in the buffer */
3807 tgl@sss.pgh.pa.us 11931 :CBC 165 : trailing_nl = strrchr(buf->data, '\n');
11932 [ - + ]: 165 : if (trailing_nl == NULL)
3807 tgl@sss.pgh.pa.us 11933 :UBC 0 : trailing_nl = buf->data;
11934 : : else
3807 tgl@sss.pgh.pa.us 11935 :CBC 165 : trailing_nl++;
11936 : :
11937 : : /*
11938 : : * Add a newline, plus some indentation, if the new item
11939 : : * would cause an overflow.
11940 : : */
11941 [ + - ]: 165 : if (strlen(trailing_nl) + itembuf.len > context->wrapColumn)
11942 : 165 : appendContextKeyword(context, "", -PRETTYINDENT_STD,
11943 : : PRETTYINDENT_STD,
11944 : : PRETTYINDENT_VAR);
11945 : : }
11946 : : }
11947 : :
11948 : : /* Add the new item */
1727 drowley@postgresql.o 11949 : 165 : appendBinaryStringInfo(buf, itembuf.data, itembuf.len);
11950 : :
11951 : : /* clean up */
4129 tgl@sss.pgh.pa.us 11952 : 165 : pfree(itembuf.data);
11953 : : }
11954 : : }
8615 11955 : 2173 : }
11956 : :
11957 : : static void
11958 : 3141 : get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
11959 : : {
11960 : 3141 : StringInfo buf = context->buf;
4122 11961 : 3141 : deparse_namespace *dpns = (deparse_namespace *) linitial(context->namespaces);
11962 : :
8615 11963 [ + + ]: 3141 : if (IsA(jtnode, RangeTblRef))
11964 : : {
11965 : 2556 : int varno = ((RangeTblRef *) jtnode)->rtindex;
11966 : 2556 : RangeTblEntry *rte = rt_fetch(varno, query->rtable);
4122 11967 : 2556 : deparse_columns *colinfo = deparse_columns_fetch(varno, dpns);
3797 11968 : 2556 : RangeTblFunction *rtfunc1 = NULL;
11969 : :
4268 11970 [ + + ]: 2556 : if (rte->lateral)
11971 : 42 : appendStringInfoString(buf, "LATERAL ");
11972 : :
11973 : : /* Print the FROM item proper */
8059 11974 [ + + + + : 2556 : switch (rte->rtekind)
+ + - ]
11975 : : {
11976 : 1973 : case RTE_RELATION:
11977 : : /* Normal relation RTE */
11978 : 3946 : appendStringInfo(buf, "%s%s",
11979 [ + + ]: 1973 : only_marker(rte),
11980 : : generate_relation_name(rte->relid,
11981 : : context->namespaces));
11982 : 1973 : break;
11983 : 136 : case RTE_SUBQUERY:
11984 : : /* Subquery RTE */
11985 : 136 : appendStringInfoChar(buf, '(');
7559 bruce@momjian.us 11986 : 136 : get_query_def(rte->subquery, buf, context->namespaces, NULL,
11987 : : true,
11988 : : context->prettyFlags, context->wrapColumn,
11989 : : context->indentLevel);
8059 tgl@sss.pgh.pa.us 11990 : 136 : appendStringInfoChar(buf, ')');
11991 : 136 : break;
8008 11992 : 315 : case RTE_FUNCTION:
11993 : : /* Function RTE */
3797 11994 : 315 : rtfunc1 = (RangeTblFunction *) linitial(rte->functions);
11995 : :
11996 : : /*
11997 : : * Omit ROWS FROM() syntax for just one function, unless it
11998 : : * has both a coldeflist and WITH ORDINALITY. If it has both,
11999 : : * we must use ROWS FROM() syntax to avoid ambiguity about
12000 : : * whether the coldeflist includes the ordinality column.
12001 : : */
12002 [ + + ]: 315 : if (list_length(rte->functions) == 1 &&
12003 [ - + - - ]: 300 : (rtfunc1->funccolnames == NIL || !rte->funcordinality))
12004 : : {
2467 12005 : 300 : get_rule_expr_funccall(rtfunc1->funcexpr, context, true);
12006 : : /* we'll print the coldeflist below, if it has one */
12007 : : }
12008 : : else
12009 : : {
12010 : : bool all_unnest;
12011 : : ListCell *lc;
12012 : :
12013 : : /*
12014 : : * If all the function calls in the list are to unnest,
12015 : : * and none need a coldeflist, then collapse the list back
12016 : : * down to UNNEST(args). (If we had more than one
12017 : : * built-in unnest function, this would get more
12018 : : * difficult.)
12019 : : *
12020 : : * XXX This is pretty ugly, since it makes not-terribly-
12021 : : * future-proof assumptions about what the parser would do
12022 : : * with the output; but the alternative is to emit our
12023 : : * nonstandard ROWS FROM() notation for what might have
12024 : : * been a perfectly spec-compliant multi-argument
12025 : : * UNNEST().
12026 : : */
3797 12027 : 15 : all_unnest = true;
12028 [ + - + + : 39 : foreach(lc, rte->functions)
+ + ]
12029 : : {
12030 : 33 : RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
12031 : :
12032 [ + - ]: 33 : if (!IsA(rtfunc->funcexpr, FuncExpr) ||
1259 12033 [ + + ]: 33 : ((FuncExpr *) rtfunc->funcexpr)->funcid != F_UNNEST_ANYARRAY ||
3797 12034 [ - + ]: 24 : rtfunc->funccolnames != NIL)
12035 : : {
12036 : 9 : all_unnest = false;
12037 : 9 : break;
12038 : : }
12039 : : }
12040 : :
12041 [ + + ]: 15 : if (all_unnest)
12042 : : {
12043 : 6 : List *allargs = NIL;
12044 : :
12045 [ + - + + : 24 : foreach(lc, rte->functions)
+ + ]
12046 : : {
12047 : 18 : RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
12048 : 18 : List *args = ((FuncExpr *) rtfunc->funcexpr)->args;
12049 : :
1707 12050 : 18 : allargs = list_concat(allargs, args);
12051 : : }
12052 : :
3797 12053 : 6 : appendStringInfoString(buf, "UNNEST(");
12054 : 6 : get_rule_expr((Node *) allargs, context, true);
12055 : 6 : appendStringInfoChar(buf, ')');
12056 : : }
12057 : : else
12058 : : {
12059 : 9 : int funcno = 0;
12060 : :
3778 noah@leadboat.com 12061 : 9 : appendStringInfoString(buf, "ROWS FROM(");
3797 tgl@sss.pgh.pa.us 12062 [ + - + + : 33 : foreach(lc, rte->functions)
+ + ]
12063 : : {
12064 : 24 : RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
12065 : :
12066 [ + + ]: 24 : if (funcno > 0)
12067 : 15 : appendStringInfoString(buf, ", ");
2467 12068 : 24 : get_rule_expr_funccall(rtfunc->funcexpr, context, true);
3797 12069 [ + + ]: 24 : if (rtfunc->funccolnames != NIL)
12070 : : {
12071 : : /* Reconstruct the column definition list */
12072 : 3 : appendStringInfoString(buf, " AS ");
12073 : 3 : get_from_clause_coldeflist(rtfunc,
12074 : : NULL,
12075 : : context);
12076 : : }
12077 : 24 : funcno++;
12078 : : }
12079 : 9 : appendStringInfoChar(buf, ')');
12080 : : }
12081 : : /* prevent printing duplicate coldeflist below */
12082 : 15 : rtfunc1 = NULL;
12083 : : }
3912 stark@mit.edu 12084 [ + + ]: 315 : if (rte->funcordinality)
12085 : 9 : appendStringInfoString(buf, " WITH ORDINALITY");
8008 tgl@sss.pgh.pa.us 12086 : 315 : break;
2594 alvherre@alvh.no-ip. 12087 : 34 : case RTE_TABLEFUNC:
12088 : 34 : get_tablefunc(rte->tablefunc, context, true);
12089 : 34 : break;
6465 mail@joeconway.com 12090 : 6 : case RTE_VALUES:
12091 : : /* Values list RTE */
3336 tgl@sss.pgh.pa.us 12092 : 6 : appendStringInfoChar(buf, '(');
6465 mail@joeconway.com 12093 : 6 : get_values_def(rte->values_lists, context);
3336 tgl@sss.pgh.pa.us 12094 : 6 : appendStringInfoChar(buf, ')');
6465 mail@joeconway.com 12095 : 6 : break;
5671 tgl@sss.pgh.pa.us 12096 : 92 : case RTE_CTE:
12097 : 92 : appendStringInfoString(buf, quote_identifier(rte->ctename));
12098 : 92 : break;
8059 tgl@sss.pgh.pa.us 12099 :UBC 0 : default:
7567 12100 [ # # ]: 0 : elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
12101 : : break;
12102 : : }
12103 : :
12104 : : /* Print the relation alias, if needed */
422 tgl@sss.pgh.pa.us 12105 :CBC 2556 : get_rte_alias(rte, varno, false, context);
12106 : :
12107 : : /* Print the column definitions or aliases, if needed */
3797 12108 [ + + - + ]: 2556 : if (rtfunc1 && rtfunc1->funccolnames != NIL)
12109 : : {
12110 : : /* Reconstruct the columndef list, which is also the aliases */
3797 tgl@sss.pgh.pa.us 12111 :UBC 0 : get_from_clause_coldeflist(rtfunc1, colinfo, context);
12112 : : }
12113 : : else
12114 : : {
12115 : : /* Else print column aliases as needed */
4122 tgl@sss.pgh.pa.us 12116 :CBC 2556 : get_column_alias_list(colinfo, context);
12117 : : }
12118 : :
12119 : : /* Tablesample clause must go after any alias */
3186 12120 [ + + + + ]: 2556 : if (rte->rtekind == RTE_RELATION && rte->tablesample)
12121 : 16 : get_tablesample_def(rte->tablesample, context);
12122 : : }
8615 12123 [ + - ]: 585 : else if (IsA(jtnode, JoinExpr))
12124 : : {
12125 : 585 : JoinExpr *j = (JoinExpr *) jtnode;
4122 12126 : 585 : deparse_columns *colinfo = deparse_columns_fetch(j->rtindex, dpns);
12127 : : bool need_paren_on_right;
12128 : :
7564 12129 : 1389 : need_paren_on_right = PRETTY_PAREN(context) &&
6867 12130 [ + + - + ]: 585 : !IsA(j->rarg, RangeTblRef) &&
1429 tgl@sss.pgh.pa.us 12131 [ # # # # ]:UBC 0 : !(IsA(j->rarg, JoinExpr) && ((JoinExpr *) j->rarg)->alias != NULL);
12132 : :
7564 tgl@sss.pgh.pa.us 12133 [ + + + + ]:CBC 585 : if (!PRETTY_PAREN(context) || j->alias != NULL)
7559 bruce@momjian.us 12134 : 420 : appendStringInfoChar(buf, '(');
12135 : :
8615 tgl@sss.pgh.pa.us 12136 : 585 : get_from_clause_item(j->larg, query, context);
12137 : :
4122 12138 [ + + + - : 585 : switch (j->jointype)
- ]
12139 : : {
12140 : 340 : case JOIN_INNER:
12141 [ + + ]: 340 : if (j->quals)
12142 : 319 : appendContextKeyword(context, " JOIN ",
12143 : : -PRETTYINDENT_STD,
12144 : : PRETTYINDENT_STD,
12145 : : PRETTYINDENT_JOIN);
12146 : : else
12147 : 21 : appendContextKeyword(context, " CROSS JOIN ",
12148 : : -PRETTYINDENT_STD,
12149 : : PRETTYINDENT_STD,
12150 : : PRETTYINDENT_JOIN);
12151 : 340 : break;
12152 : 194 : case JOIN_LEFT:
12153 : 194 : appendContextKeyword(context, " LEFT JOIN ",
12154 : : -PRETTYINDENT_STD,
12155 : : PRETTYINDENT_STD,
12156 : : PRETTYINDENT_JOIN);
12157 : 194 : break;
12158 : 51 : case JOIN_FULL:
12159 : 51 : appendContextKeyword(context, " FULL JOIN ",
12160 : : -PRETTYINDENT_STD,
12161 : : PRETTYINDENT_STD,
12162 : : PRETTYINDENT_JOIN);
12163 : 51 : break;
4122 tgl@sss.pgh.pa.us 12164 :UBC 0 : case JOIN_RIGHT:
12165 : 0 : appendContextKeyword(context, " RIGHT JOIN ",
12166 : : -PRETTYINDENT_STD,
12167 : : PRETTYINDENT_STD,
12168 : : PRETTYINDENT_JOIN);
12169 : 0 : break;
12170 : 0 : default:
12171 [ # # ]: 0 : elog(ERROR, "unrecognized join type: %d",
12172 : : (int) j->jointype);
12173 : : }
12174 : :
7564 tgl@sss.pgh.pa.us 12175 [ - + ]:CBC 585 : if (need_paren_on_right)
7559 bruce@momjian.us 12176 :UBC 0 : appendStringInfoChar(buf, '(');
8615 tgl@sss.pgh.pa.us 12177 :CBC 585 : get_from_clause_item(j->rarg, query, context);
7564 12178 [ - + ]: 585 : if (need_paren_on_right)
7559 bruce@momjian.us 12179 :UBC 0 : appendStringInfoChar(buf, ')');
12180 : :
4122 tgl@sss.pgh.pa.us 12181 [ + + ]:CBC 585 : if (j->usingClause)
12182 : : {
12183 : : ListCell *lc;
12184 : 212 : bool first = true;
12185 : :
3818 rhaas@postgresql.org 12186 : 212 : appendStringInfoString(buf, " USING (");
12187 : : /* Use the assigned names, not what's in usingClause */
4122 tgl@sss.pgh.pa.us 12188 [ + - + + : 502 : foreach(lc, colinfo->usingNames)
+ + ]
12189 : : {
12190 : 290 : char *colname = (char *) lfirst(lc);
12191 : :
12192 [ + + ]: 290 : if (first)
12193 : 212 : first = false;
12194 : : else
3818 rhaas@postgresql.org 12195 : 78 : appendStringInfoString(buf, ", ");
4122 tgl@sss.pgh.pa.us 12196 : 290 : appendStringInfoString(buf, quote_identifier(colname));
12197 : : }
12198 : 212 : appendStringInfoChar(buf, ')');
12199 : :
1110 peter@eisentraut.org 12200 [ + + ]: 212 : if (j->join_using_alias)
12201 : 6 : appendStringInfo(buf, " AS %s",
12202 : 6 : quote_identifier(j->join_using_alias->aliasname));
12203 : : }
4122 tgl@sss.pgh.pa.us 12204 [ + + ]: 373 : else if (j->quals)
12205 : : {
3818 rhaas@postgresql.org 12206 : 349 : appendStringInfoString(buf, " ON ");
4122 tgl@sss.pgh.pa.us 12207 [ + + ]: 349 : if (!PRETTY_PAREN(context))
12208 : 346 : appendStringInfoChar(buf, '(');
12209 : 349 : get_rule_expr(j->quals, context, false);
12210 [ + + ]: 349 : if (!PRETTY_PAREN(context))
12211 : 346 : appendStringInfoChar(buf, ')');
12212 : : }
2460 12213 [ + + ]: 24 : else if (j->jointype != JOIN_INNER)
12214 : : {
12215 : : /* If we didn't say CROSS JOIN above, we must provide an ON */
12216 : 3 : appendStringInfoString(buf, " ON TRUE");
12217 : : }
12218 : :
7564 12219 [ + + + + ]: 585 : if (!PRETTY_PAREN(context) || j->alias != NULL)
7559 bruce@momjian.us 12220 : 420 : appendStringInfoChar(buf, ')');
12221 : :
12222 : : /* Yes, it's correct to put alias after the right paren ... */
8615 tgl@sss.pgh.pa.us 12223 [ + + ]: 585 : if (j->alias != NULL)
12224 : : {
12225 : : /*
12226 : : * Note that it's correct to emit an alias clause if and only if
12227 : : * there was one originally. Otherwise we'd be converting a named
12228 : : * join to unnamed or vice versa, which creates semantic
12229 : : * subtleties we don't want. However, we might print a different
12230 : : * alias name than was there originally.
12231 : : */
12232 : 54 : appendStringInfo(buf, " %s",
1768 12233 : 54 : quote_identifier(get_rtable_name(j->rtindex,
12234 : : context)));
4122 12235 : 54 : get_column_alias_list(colinfo, context);
12236 : : }
12237 : : }
12238 : : else
7567 tgl@sss.pgh.pa.us 12239 [ # # ]:UBC 0 : elog(ERROR, "unrecognized node type: %d",
12240 : : (int) nodeTag(jtnode));
8615 tgl@sss.pgh.pa.us 12241 :CBC 3141 : }
12242 : :
12243 : : /*
12244 : : * get_rte_alias - print the relation's alias, if needed
12245 : : *
12246 : : * If printed, the alias is preceded by a space, or by " AS " if use_as is true.
12247 : : */
12248 : : static void
422 12249 : 2835 : get_rte_alias(RangeTblEntry *rte, int varno, bool use_as,
12250 : : deparse_context *context)
12251 : : {
12252 : 2835 : deparse_namespace *dpns = (deparse_namespace *) linitial(context->namespaces);
12253 : 2835 : char *refname = get_rtable_name(varno, context);
12254 : 2835 : deparse_columns *colinfo = deparse_columns_fetch(varno, dpns);
12255 : 2835 : bool printalias = false;
12256 : :
12257 [ + + ]: 2835 : if (rte->alias != NULL)
12258 : : {
12259 : : /* Always print alias if user provided one */
12260 : 1197 : printalias = true;
12261 : : }
12262 [ + + ]: 1638 : else if (colinfo->printaliases)
12263 : : {
12264 : : /* Always print alias if we need to print column aliases */
12265 : 137 : printalias = true;
12266 : : }
12267 [ + + ]: 1501 : else if (rte->rtekind == RTE_RELATION)
12268 : : {
12269 : : /*
12270 : : * No need to print alias if it's same as relation name (this would
12271 : : * normally be the case, but not if set_rtable_names had to resolve a
12272 : : * conflict).
12273 : : */
12274 [ + + ]: 1380 : if (strcmp(refname, get_relation_name(rte->relid)) != 0)
12275 : 19 : printalias = true;
12276 : : }
12277 [ - + ]: 121 : else if (rte->rtekind == RTE_FUNCTION)
12278 : : {
12279 : : /*
12280 : : * For a function RTE, always print alias. This covers possible
12281 : : * renaming of the function and/or instability of the FigureColname
12282 : : * rules for things that aren't simple functions. Note we'd need to
12283 : : * force it anyway for the columndef list case.
12284 : : */
422 tgl@sss.pgh.pa.us 12285 :UBC 0 : printalias = true;
12286 : : }
422 tgl@sss.pgh.pa.us 12287 [ + + ]:CBC 121 : else if (rte->rtekind == RTE_SUBQUERY ||
12288 [ + + ]: 109 : rte->rtekind == RTE_VALUES)
12289 : : {
12290 : : /*
12291 : : * For a subquery, always print alias. This makes the output
12292 : : * SQL-spec-compliant, even though we allow such aliases to be omitted
12293 : : * on input.
12294 : : */
12295 : 18 : printalias = true;
12296 : : }
12297 [ + + ]: 103 : else if (rte->rtekind == RTE_CTE)
12298 : : {
12299 : : /*
12300 : : * No need to print alias if it's same as CTE name (this would
12301 : : * normally be the case, but not if set_rtable_names had to resolve a
12302 : : * conflict).
12303 : : */
12304 [ + + ]: 72 : if (strcmp(refname, rte->ctename) != 0)
12305 : 11 : printalias = true;
12306 : : }
12307 : :
12308 [ + + ]: 2835 : if (printalias)
12309 [ + + ]: 1382 : appendStringInfo(context->buf, "%s%s",
12310 : : use_as ? " AS " : " ",
12311 : : quote_identifier(refname));
12312 : 2835 : }
12313 : :
12314 : : /*
12315 : : * get_column_alias_list - print column alias list for an RTE
12316 : : *
12317 : : * Caller must already have printed the relation's alias name.
12318 : : */
12319 : : static void
4122 12320 : 2610 : get_column_alias_list(deparse_columns *colinfo, deparse_context *context)
12321 : : {
7178 12322 : 2610 : StringInfo buf = context->buf;
12323 : : int i;
12324 : 2610 : bool first = true;
12325 : :
12326 : : /* Don't print aliases if not needed */
4122 12327 [ + + ]: 2610 : if (!colinfo->printaliases)
12328 : 2115 : return;
12329 : :
12330 [ + + ]: 3510 : for (i = 0; i < colinfo->num_new_cols; i++)
12331 : : {
12332 : 3015 : char *colname = colinfo->new_colnames[i];
12333 : :
7178 12334 [ + + ]: 3015 : if (first)
12335 : : {
12336 : 495 : appendStringInfoChar(buf, '(');
12337 : 495 : first = false;
12338 : : }
12339 : : else
3818 rhaas@postgresql.org 12340 : 2520 : appendStringInfoString(buf, ", ");
4122 tgl@sss.pgh.pa.us 12341 : 3015 : appendStringInfoString(buf, quote_identifier(colname));
12342 : : }
7178 12343 [ + - ]: 495 : if (!first)
12344 : 495 : appendStringInfoChar(buf, ')');
12345 : : }
12346 : :
12347 : : /*
12348 : : * get_from_clause_coldeflist - reproduce FROM clause coldeflist
12349 : : *
12350 : : * When printing a top-level coldeflist (which is syntactically also the
12351 : : * relation's column alias list), use column names from colinfo. But when
12352 : : * printing a coldeflist embedded inside ROWS FROM(), we prefer to use the
12353 : : * original coldeflist's names, which are available in rtfunc->funccolnames.
12354 : : * Pass NULL for colinfo to select the latter behavior.
12355 : : *
12356 : : * The coldeflist is appended immediately (no space) to buf. Caller is
12357 : : * responsible for ensuring that an alias or AS is present before it.
12358 : : */
12359 : : static void
3797 12360 : 3 : get_from_clause_coldeflist(RangeTblFunction *rtfunc,
12361 : : deparse_columns *colinfo,
12362 : : deparse_context *context)
12363 : : {
7899 12364 : 3 : StringInfo buf = context->buf;
12365 : : ListCell *l1;
12366 : : ListCell *l2;
12367 : : ListCell *l3;
12368 : : ListCell *l4;
12369 : : int i;
12370 : :
12371 : 3 : appendStringInfoChar(buf, '(');
12372 : :
4122 12373 : 3 : i = 0;
1872 12374 [ + - + + : 12 : forfour(l1, rtfunc->funccoltypes,
+ - + + +
- + + + -
+ + + + +
- + - + -
+ + ]
12375 : : l2, rtfunc->funccoltypmods,
12376 : : l3, rtfunc->funccolcollations,
12377 : : l4, rtfunc->funccolnames)
12378 : : {
4122 12379 : 9 : Oid atttypid = lfirst_oid(l1);
12380 : 9 : int32 atttypmod = lfirst_int(l2);
12381 : 9 : Oid attcollation = lfirst_oid(l3);
12382 : : char *attname;
12383 : :
3797 12384 [ - + ]: 9 : if (colinfo)
3797 tgl@sss.pgh.pa.us 12385 :UBC 0 : attname = colinfo->colnames[i];
12386 : : else
3797 tgl@sss.pgh.pa.us 12387 :CBC 9 : attname = strVal(lfirst(l4));
12388 : :
4122 12389 [ - + ]: 9 : Assert(attname); /* shouldn't be any dropped columns here */
12390 : :
7899 12391 [ + + ]: 9 : if (i > 0)
3818 rhaas@postgresql.org 12392 : 6 : appendStringInfoString(buf, ", ");
7899 tgl@sss.pgh.pa.us 12393 : 9 : appendStringInfo(buf, "%s %s",
12394 : : quote_identifier(attname),
12395 : : format_type_with_typemod(atttypid, atttypmod));
4741 12396 [ + + - + ]: 12 : if (OidIsValid(attcollation) &&
12397 : 3 : attcollation != get_typcollation(atttypid))
4770 tgl@sss.pgh.pa.us 12398 :UBC 0 : appendStringInfo(buf, " COLLATE %s",
12399 : : generate_collation_name(attcollation));
12400 : :
7899 tgl@sss.pgh.pa.us 12401 :CBC 9 : i++;
12402 : : }
12403 : :
12404 : 3 : appendStringInfoChar(buf, ')');
12405 : 3 : }
12406 : :
12407 : : /*
12408 : : * get_tablesample_def - print a TableSampleClause
12409 : : */
12410 : : static void
3186 12411 : 16 : get_tablesample_def(TableSampleClause *tablesample, deparse_context *context)
12412 : : {
12413 : 16 : StringInfo buf = context->buf;
12414 : : Oid argtypes[1];
12415 : : int nargs;
12416 : : ListCell *l;
12417 : :
12418 : : /*
12419 : : * We should qualify the handler's function name if it wouldn't be
12420 : : * resolved by lookup in the current search path.
12421 : : */
12422 : 16 : argtypes[0] = INTERNALOID;
12423 : 16 : appendStringInfo(buf, " TABLESAMPLE %s (",
12424 : : generate_function_name(tablesample->tsmhandler, 1,
12425 : : NIL, argtypes,
12426 : : false, NULL, EXPR_KIND_NONE));
12427 : :
12428 : 16 : nargs = 0;
12429 [ + - + + : 32 : foreach(l, tablesample->args)
+ + ]
12430 : : {
12431 [ - + ]: 16 : if (nargs++ > 0)
3186 tgl@sss.pgh.pa.us 12432 :UBC 0 : appendStringInfoString(buf, ", ");
3186 tgl@sss.pgh.pa.us 12433 :CBC 16 : get_rule_expr((Node *) lfirst(l), context, false);
12434 : : }
12435 : 16 : appendStringInfoChar(buf, ')');
12436 : :
12437 [ + + ]: 16 : if (tablesample->repeatable != NULL)
12438 : : {
12439 : 8 : appendStringInfoString(buf, " REPEATABLE (");
12440 : 8 : get_rule_expr((Node *) tablesample->repeatable, context, false);
12441 : 8 : appendStringInfoChar(buf, ')');
12442 : : }
12443 : 16 : }
12444 : :
12445 : : /*
12446 : : * get_opclass_name - fetch name of an index operator class
12447 : : *
12448 : : * The opclass name is appended (after a space) to buf.
12449 : : *
12450 : : * Output is suppressed if the opclass is the default for the given
12451 : : * actual_datatype. (If you don't want this behavior, just pass
12452 : : * InvalidOid for actual_datatype.)
12453 : : */
12454 : : static void
8228 12455 : 5625 : get_opclass_name(Oid opclass, Oid actual_datatype,
12456 : : StringInfo buf)
12457 : : {
12458 : : HeapTuple ht_opc;
12459 : : Form_pg_opclass opcrec;
12460 : : char *opcname;
12461 : : char *nspname;
12462 : :
5173 rhaas@postgresql.org 12463 : 5625 : ht_opc = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
8231 tgl@sss.pgh.pa.us 12464 [ - + ]: 5625 : if (!HeapTupleIsValid(ht_opc))
8231 tgl@sss.pgh.pa.us 12465 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for opclass %u", opclass);
8231 tgl@sss.pgh.pa.us 12466 :CBC 5625 : opcrec = (Form_pg_opclass) GETSTRUCT(ht_opc);
12467 : :
6322 12468 [ + + + + ]: 11232 : if (!OidIsValid(actual_datatype) ||
12469 : 5607 : GetDefaultOpClass(actual_datatype, opcrec->opcmethod) != opclass)
12470 : : {
12471 : : /* Okay, we need the opclass name. Do we need to qualify it? */
8017 12472 : 277 : opcname = NameStr(opcrec->opcname);
6322 12473 [ + - ]: 277 : if (OpclassIsVisible(opclass))
8017 12474 : 277 : appendStringInfo(buf, " %s", quote_identifier(opcname));
12475 : : else
12476 : : {
992 tgl@sss.pgh.pa.us 12477 :UBC 0 : nspname = get_namespace_name_or_temp(opcrec->opcnamespace);
8017 12478 : 0 : appendStringInfo(buf, " %s.%s",
12479 : : quote_identifier(nspname),
12480 : : quote_identifier(opcname));
12481 : : }
12482 : : }
8231 tgl@sss.pgh.pa.us 12483 :CBC 5625 : ReleaseSysCache(ht_opc);
12484 : 5625 : }
12485 : :
12486 : : /*
12487 : : * generate_opclass_name
12488 : : * Compute the name to display for an opclass specified by OID
12489 : : *
12490 : : * The result includes all necessary quoting and schema-prefixing.
12491 : : */
12492 : : char *
1476 akorotkov@postgresql 12493 : 3 : generate_opclass_name(Oid opclass)
12494 : : {
12495 : : StringInfoData buf;
12496 : :
12497 : 3 : initStringInfo(&buf);
12498 : 3 : get_opclass_name(opclass, InvalidOid, &buf);
12499 : :
1431 tgl@sss.pgh.pa.us 12500 : 3 : return &buf.data[1]; /* get_opclass_name() prepends space */
12501 : : }
12502 : :
12503 : : /*
12504 : : * processIndirection - take care of array and subfield assignment
12505 : : *
12506 : : * We strip any top-level FieldStore or assignment SubscriptingRef nodes that
12507 : : * appear in the input, printing them as decoration for the base column
12508 : : * name (which we assume the caller just printed). We might also need to
12509 : : * strip CoerceToDomain nodes, but only ones that appear above assignment
12510 : : * nodes.
12511 : : *
12512 : : * Returns the subexpression that's to be assigned.
12513 : : */
12514 : : static Node *
2811 12515 : 624 : processIndirection(Node *node, deparse_context *context)
12516 : : {
7249 12517 : 624 : StringInfo buf = context->buf;
2468 12518 : 624 : CoerceToDomain *cdomain = NULL;
12519 : :
12520 : : for (;;)
12521 : : {
7249 12522 [ - + ]: 777 : if (node == NULL)
7249 tgl@sss.pgh.pa.us 12523 :UBC 0 : break;
7249 tgl@sss.pgh.pa.us 12524 [ + + ]:CBC 777 : if (IsA(node, FieldStore))
12525 : : {
12526 : 54 : FieldStore *fstore = (FieldStore *) node;
12527 : : Oid typrelid;
12528 : : char *fieldname;
12529 : :
12530 : : /* lookup tuple type */
12531 : 54 : typrelid = get_typ_typrelid(fstore->resulttype);
12532 [ - + ]: 54 : if (!OidIsValid(typrelid))
7249 tgl@sss.pgh.pa.us 12533 [ # # ]:UBC 0 : elog(ERROR, "argument type %s of FieldStore is not a tuple type",
12534 : : format_type_be(fstore->resulttype));
12535 : :
12536 : : /*
12537 : : * Print the field name. There should only be one target field in
12538 : : * stored rules. There could be more than that in executable
12539 : : * target lists, but this function cannot be used for that case.
12540 : : */
5169 tgl@sss.pgh.pa.us 12541 [ - + ]:CBC 54 : Assert(list_length(fstore->fieldnums) == 1);
2253 alvherre@alvh.no-ip. 12542 : 54 : fieldname = get_attname(typrelid,
12543 : 54 : linitial_int(fstore->fieldnums), false);
2811 tgl@sss.pgh.pa.us 12544 : 54 : appendStringInfo(buf, ".%s", quote_identifier(fieldname));
12545 : :
12546 : : /*
12547 : : * We ignore arg since it should be an uninteresting reference to
12548 : : * the target column or subcolumn.
12549 : : */
7249 12550 : 54 : node = (Node *) linitial(fstore->newvals);
12551 : : }
1899 alvherre@alvh.no-ip. 12552 [ + + ]: 723 : else if (IsA(node, SubscriptingRef))
12553 : : {
12554 : 69 : SubscriptingRef *sbsref = (SubscriptingRef *) node;
12555 : :
12556 [ - + ]: 69 : if (sbsref->refassgnexpr == NULL)
7249 tgl@sss.pgh.pa.us 12557 :UBC 0 : break;
12558 : :
1899 alvherre@alvh.no-ip. 12559 :CBC 69 : printSubscripts(sbsref, context);
12560 : :
12561 : : /*
12562 : : * We ignore refexpr since it should be an uninteresting reference
12563 : : * to the target column or subcolumn.
12564 : : */
12565 : 69 : node = (Node *) sbsref->refassgnexpr;
12566 : : }
2468 tgl@sss.pgh.pa.us 12567 [ + + ]: 654 : else if (IsA(node, CoerceToDomain))
12568 : : {
12569 : 30 : cdomain = (CoerceToDomain *) node;
12570 : : /* If it's an explicit domain coercion, we're done */
12571 [ - + ]: 30 : if (cdomain->coercionformat != COERCE_IMPLICIT_CAST)
2468 tgl@sss.pgh.pa.us 12572 :UBC 0 : break;
12573 : : /* Tentatively descend past the CoerceToDomain */
2468 tgl@sss.pgh.pa.us 12574 :CBC 30 : node = (Node *) cdomain->arg;
12575 : : }
12576 : : else
7249 12577 : 624 : break;
12578 : : }
12579 : :
12580 : : /*
12581 : : * If we descended past a CoerceToDomain whose argument turned out not to
12582 : : * be a FieldStore or array assignment, back up to the CoerceToDomain.
12583 : : * (This is not enough to be fully correct if there are nested implicit
12584 : : * CoerceToDomains, but such cases shouldn't ever occur.)
12585 : : */
2468 12586 [ + + - + ]: 624 : if (cdomain && node == (Node *) cdomain->arg)
2468 tgl@sss.pgh.pa.us 12587 :UBC 0 : node = (Node *) cdomain;
12588 : :
7249 tgl@sss.pgh.pa.us 12589 :CBC 624 : return node;
12590 : : }
12591 : :
12592 : : static void
1899 alvherre@alvh.no-ip. 12593 : 197 : printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
12594 : : {
7249 tgl@sss.pgh.pa.us 12595 : 197 : StringInfo buf = context->buf;
12596 : : ListCell *lowlist_item;
12597 : : ListCell *uplist_item;
12598 : :
1899 alvherre@alvh.no-ip. 12599 : 197 : lowlist_item = list_head(sbsref->reflowerindexpr); /* could be NULL */
12600 [ + - + + : 394 : foreach(uplist_item, sbsref->refupperindexpr)
+ + ]
12601 : : {
7249 tgl@sss.pgh.pa.us 12602 : 197 : appendStringInfoChar(buf, '[');
12603 [ - + ]: 197 : if (lowlist_item)
12604 : : {
12605 : : /* If subexpression is NULL, get_rule_expr prints nothing */
7249 tgl@sss.pgh.pa.us 12606 :UBC 0 : get_rule_expr((Node *) lfirst(lowlist_item), context, false);
12607 : 0 : appendStringInfoChar(buf, ':');
1735 12608 : 0 : lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
12609 : : }
12610 : : /* If subexpression is NULL, get_rule_expr prints nothing */
7249 tgl@sss.pgh.pa.us 12611 :CBC 197 : get_rule_expr((Node *) lfirst(uplist_item), context, false);
12612 : 197 : appendStringInfoChar(buf, ']');
12613 : : }
8646 12614 : 197 : }
12615 : :
12616 : : /*
12617 : : * quote_identifier - Quote an identifier only if needed
12618 : : *
12619 : : * When quotes are needed, we palloc the required space; slightly
12620 : : * space-wasteful but well worth it for notational simplicity.
12621 : : */
12622 : : const char *
8025 12623 : 1136735 : quote_identifier(const char *ident)
12624 : : {
12625 : : /*
12626 : : * Can avoid quoting if ident starts with a lowercase letter or underscore
12627 : : * and contains only lowercase letters, digits, and underscores, *and* is
12628 : : * not any SQL keyword. Otherwise, supply quotes.
12629 : : */
8003 12630 : 1136735 : int nquotes = 0;
12631 : : bool safe;
12632 : : const char *ptr;
12633 : : char *result;
12634 : : char *optr;
12635 : :
12636 : : /*
12637 : : * would like to use <ctype.h> macros here, but they might yield unwanted
12638 : : * locale-specific results...
12639 : : */
8022 12640 [ + + - + : 1136735 : safe = ((ident[0] >= 'a' && ident[0] <= 'z') || ident[0] == '_');
+ + ]
12641 : :
8003 12642 [ + + ]: 9562060 : for (ptr = ident; *ptr; ptr++)
12643 : : {
12644 : 8425325 : char ch = *ptr;
12645 : :
12646 [ + + + - : 8425325 : if ((ch >= 'a' && ch <= 'z') ||
+ + ]
12647 [ + + + + ]: 1012079 : (ch >= '0' && ch <= '9') ||
12648 : : (ch == '_'))
12649 : : {
12650 : : /* okay */
12651 : : }
12652 : : else
12653 : : {
12654 : 30124 : safe = false;
12655 [ + + ]: 30124 : if (ch == '"')
12656 : 6 : nquotes++;
12657 : : }
12658 : : }
12659 : :
5015 rhaas@postgresql.org 12660 [ + + ]: 1136735 : if (quote_all_identifiers)
12661 : 5747 : safe = false;
12662 : :
8896 tgl@sss.pgh.pa.us 12663 [ + + ]: 1136735 : if (safe)
12664 : : {
12665 : : /*
12666 : : * Check for keyword. We quote keywords except for unreserved ones.
12667 : : * (In some cases we could avoid quoting a col_name or type_func_name
12668 : : * keyword, but it seems much harder than it's worth to tell that.)
12669 : : *
12670 : : * Note: ScanKeywordLookup() does case-insensitive comparison, but
12671 : : * that's fine, since we already know we have all-lower-case.
12672 : : */
1925 12673 : 1118577 : int kwnum = ScanKeywordLookup(ident, &ScanKeywords);
12674 : :
12675 [ + + + + ]: 1118577 : if (kwnum >= 0 && ScanKeywordCategories[kwnum] != UNRESERVED_KEYWORD)
8896 12676 : 1312 : safe = false;
12677 : : }
12678 : :
8959 12679 [ + + ]: 1136735 : if (safe)
12680 : 1117265 : return ident; /* no change needed */
12681 : :
8003 12682 : 19470 : result = (char *) palloc(strlen(ident) + nquotes + 2 + 1);
12683 : :
12684 : 19470 : optr = result;
12685 : 19470 : *optr++ = '"';
12686 [ + + ]: 113620 : for (ptr = ident; *ptr; ptr++)
12687 : : {
12688 : 94150 : char ch = *ptr;
12689 : :
12690 [ + + ]: 94150 : if (ch == '"')
12691 : 6 : *optr++ = '"';
12692 : 94150 : *optr++ = ch;
12693 : : }
12694 : 19470 : *optr++ = '"';
12695 : 19470 : *optr = '\0';
12696 : :
8959 12697 : 19470 : return result;
12698 : : }
12699 : :
12700 : : /*
12701 : : * quote_qualified_identifier - Quote a possibly-qualified identifier
12702 : : *
12703 : : * Return a name of the form qualifier.ident, or just ident if qualifier
12704 : : * is NULL, quoting each component if necessary. The result is palloc'd.
12705 : : */
12706 : : char *
5386 peter_e@gmx.net 12707 : 544007 : quote_qualified_identifier(const char *qualifier,
12708 : : const char *ident)
12709 : : {
12710 : : StringInfoData buf;
12711 : :
8025 tgl@sss.pgh.pa.us 12712 : 544007 : initStringInfo(&buf);
5386 peter_e@gmx.net 12713 [ + + ]: 544007 : if (qualifier)
12714 : 210802 : appendStringInfo(&buf, "%s.", quote_identifier(qualifier));
7379 neilc@samurai.com 12715 : 544007 : appendStringInfoString(&buf, quote_identifier(ident));
8025 tgl@sss.pgh.pa.us 12716 : 544007 : return buf.data;
12717 : : }
12718 : :
12719 : : /*
12720 : : * get_relation_name
12721 : : * Get the unqualified name of a relation specified by OID
12722 : : *
12723 : : * This differs from the underlying get_rel_name() function in that it will
12724 : : * throw error instead of silently returning NULL if the OID is bad.
12725 : : */
12726 : : static char *
4912 12727 : 7353 : get_relation_name(Oid relid)
12728 : : {
12729 : 7353 : char *relname = get_rel_name(relid);
12730 : :
12731 [ - + ]: 7353 : if (!relname)
4912 tgl@sss.pgh.pa.us 12732 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
4912 tgl@sss.pgh.pa.us 12733 :CBC 7353 : return relname;
12734 : : }
12735 : :
12736 : : /*
12737 : : * generate_relation_name
12738 : : * Compute the name to display for a relation specified by OID
12739 : : *
12740 : : * The result includes all necessary quoting and schema-prefixing.
12741 : : *
12742 : : * If namespaces isn't NIL, it must be a list of deparse_namespace nodes.
12743 : : * We will forcibly qualify the relation name if it equals any CTE name
12744 : : * visible in the namespace list.
12745 : : */
12746 : : static char *
5669 12747 : 3590 : generate_relation_name(Oid relid, List *namespaces)
12748 : : {
12749 : : HeapTuple tp;
12750 : : Form_pg_class reltup;
12751 : : bool need_qual;
12752 : : ListCell *nslist;
12753 : : char *relname;
12754 : : char *nspname;
12755 : : char *result;
12756 : :
5173 rhaas@postgresql.org 12757 : 3590 : tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
8017 tgl@sss.pgh.pa.us 12758 [ - + ]: 3590 : if (!HeapTupleIsValid(tp))
7567 tgl@sss.pgh.pa.us 12759 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
8017 tgl@sss.pgh.pa.us 12760 :CBC 3590 : reltup = (Form_pg_class) GETSTRUCT(tp);
5669 12761 : 3590 : relname = NameStr(reltup->relname);
12762 : :
12763 : : /* Check for conflicting CTE name */
12764 : 3590 : need_qual = false;
12765 [ + + + + : 6079 : foreach(nslist, namespaces)
+ + ]
12766 : : {
12767 : 2489 : deparse_namespace *dpns = (deparse_namespace *) lfirst(nslist);
12768 : : ListCell *ctlist;
12769 : :
12770 [ + + + + : 2519 : foreach(ctlist, dpns->ctes)
+ + ]
12771 : : {
12772 : 30 : CommonTableExpr *cte = (CommonTableExpr *) lfirst(ctlist);
12773 : :
12774 [ - + ]: 30 : if (strcmp(cte->ctename, relname) == 0)
12775 : : {
5669 tgl@sss.pgh.pa.us 12776 :UBC 0 : need_qual = true;
12777 : 0 : break;
12778 : : }
12779 : : }
5669 tgl@sss.pgh.pa.us 12780 [ - + ]:CBC 2489 : if (need_qual)
5669 tgl@sss.pgh.pa.us 12781 :UBC 0 : break;
12782 : : }
12783 : :
12784 : : /* Otherwise, qualify the name if not visible in search path */
5669 tgl@sss.pgh.pa.us 12785 [ + - ]:CBC 3590 : if (!need_qual)
12786 : 3590 : need_qual = !RelationIsVisible(relid);
12787 : :
12788 [ + + ]: 3590 : if (need_qual)
992 12789 : 1170 : nspname = get_namespace_name_or_temp(reltup->relnamespace);
12790 : : else
5669 12791 : 2420 : nspname = NULL;
12792 : :
12793 : 3590 : result = quote_qualified_identifier(nspname, relname);
12794 : :
8017 12795 : 3590 : ReleaseSysCache(tp);
12796 : :
12797 : 3590 : return result;
12798 : : }
12799 : :
12800 : : /*
12801 : : * generate_qualified_relation_name
12802 : : * Compute the name to display for a relation specified by OID
12803 : : *
12804 : : * As above, but unconditionally schema-qualify the name.
12805 : : */
12806 : : static char *
3068 12807 : 3621 : generate_qualified_relation_name(Oid relid)
12808 : : {
12809 : : HeapTuple tp;
12810 : : Form_pg_class reltup;
12811 : : char *relname;
12812 : : char *nspname;
12813 : : char *result;
12814 : :
12815 : 3621 : tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
12816 [ - + ]: 3621 : if (!HeapTupleIsValid(tp))
3068 tgl@sss.pgh.pa.us 12817 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
3068 tgl@sss.pgh.pa.us 12818 :CBC 3621 : reltup = (Form_pg_class) GETSTRUCT(tp);
12819 : 3621 : relname = NameStr(reltup->relname);
12820 : :
992 12821 : 3621 : nspname = get_namespace_name_or_temp(reltup->relnamespace);
3068 12822 [ - + ]: 3621 : if (!nspname)
3068 tgl@sss.pgh.pa.us 12823 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for namespace %u",
12824 : : reltup->relnamespace);
12825 : :
3068 tgl@sss.pgh.pa.us 12826 :CBC 3621 : result = quote_qualified_identifier(nspname, relname);
12827 : :
12828 : 3621 : ReleaseSysCache(tp);
12829 : :
12830 : 3621 : return result;
12831 : : }
12832 : :
12833 : : /*
12834 : : * generate_function_name
12835 : : * Compute the name to display for a function specified by OID,
12836 : : * given that it is being called with the specified actual arg names and
12837 : : * types. (Those matter because of ambiguous-function resolution rules.)
12838 : : *
12839 : : * If we're dealing with a potentially variadic function (in practice, this
12840 : : * means a FuncExpr or Aggref, not some other way of calling a function), then
12841 : : * has_variadic must specify whether variadic arguments have been merged,
12842 : : * and *use_variadic_p will be set to indicate whether to print VARIADIC in
12843 : : * the output. For non-FuncExpr cases, has_variadic should be false and
12844 : : * use_variadic_p can be NULL.
12845 : : *
12846 : : * The result includes all necessary quoting and schema-prefixing.
12847 : : */
12848 : : static char *
4101 12849 : 5529 : generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
12850 : : bool has_variadic, bool *use_variadic_p,
12851 : : ParseExprKind special_exprkind)
12852 : : {
12853 : : char *result;
12854 : : HeapTuple proctup;
12855 : : Form_pg_proc procform;
12856 : : char *proname;
12857 : : bool use_variadic;
12858 : : char *nspname;
12859 : : FuncDetailCode p_result;
12860 : : Oid p_funcid;
12861 : : Oid p_rettype;
12862 : : bool p_retset;
12863 : : int p_nvargs;
12864 : : Oid p_vatype;
12865 : : Oid *p_true_typeids;
3256 andres@anarazel.de 12866 : 5529 : bool force_qualify = false;
12867 : :
5173 rhaas@postgresql.org 12868 : 5529 : proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
8017 tgl@sss.pgh.pa.us 12869 [ - + ]: 5529 : if (!HeapTupleIsValid(proctup))
7567 tgl@sss.pgh.pa.us 12870 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for function %u", funcid);
8017 tgl@sss.pgh.pa.us 12871 :CBC 5529 : procform = (Form_pg_proc) GETSTRUCT(proctup);
12872 : 5529 : proname = NameStr(procform->proname);
12873 : :
12874 : : /*
12875 : : * Due to parser hacks to avoid needing to reserve CUBE, we need to force
12876 : : * qualification in some special cases.
12877 : : */
3256 andres@anarazel.de 12878 [ - + ]: 5529 : if (special_exprkind == EXPR_KIND_GROUP_BY)
12879 : : {
3256 andres@anarazel.de 12880 [ # # # # ]:UBC 0 : if (strcmp(proname, "cube") == 0 || strcmp(proname, "rollup") == 0)
12881 : 0 : force_qualify = true;
12882 : : }
12883 : :
12884 : : /*
12885 : : * Determine whether VARIADIC should be printed. We must do this first
12886 : : * since it affects the lookup rules in func_get_detail().
12887 : : *
12888 : : * We always print VARIADIC if the function has a merged variadic-array
12889 : : * argument. Note that this is always the case for functions taking a
12890 : : * VARIADIC argument type other than VARIADIC ANY. If we omitted VARIADIC
12891 : : * and printed the array elements as separate arguments, the call could
12892 : : * match a newer non-VARIADIC function.
12893 : : */
4101 tgl@sss.pgh.pa.us 12894 [ + + ]:CBC 5529 : if (use_variadic_p)
12895 : : {
12896 : : /* Parser should not have set funcvariadic unless fn is variadic */
3664 12897 [ + + - + ]: 4714 : Assert(!has_variadic || OidIsValid(procform->provariadic));
12898 : 4714 : use_variadic = has_variadic;
4101 12899 : 4714 : *use_variadic_p = use_variadic;
12900 : : }
12901 : : else
12902 : : {
3664 12903 [ - + ]: 815 : Assert(!has_variadic);
4101 12904 : 815 : use_variadic = false;
12905 : : }
12906 : :
12907 : : /*
12908 : : * The idea here is to schema-qualify only if the parser would fail to
12909 : : * resolve the correct function given the unqualified func name with the
12910 : : * specified argtypes and VARIADIC flag. But if we already decided to
12911 : : * force qualification, then we can skip the lookup and pretend we didn't
12912 : : * find it.
12913 : : */
3256 andres@anarazel.de 12914 [ + - ]: 5529 : if (!force_qualify)
12915 : 5529 : p_result = func_get_detail(list_make1(makeString(proname)),
12916 : : NIL, argnames, nargs, argtypes,
1039 tgl@sss.pgh.pa.us 12917 :GIC 5529 : !use_variadic, true, false,
12918 : : &p_funcid, &p_rettype,
12919 : : &p_retset, &p_nvargs, &p_vatype,
3256 andres@anarazel.de 12920 :CBC 5529 : &p_true_typeids, NULL);
12921 : : else
12922 : : {
3256 andres@anarazel.de 12923 :UBC 0 : p_result = FUNCDETAIL_NOTFOUND;
12924 : 0 : p_funcid = InvalidOid;
12925 : : }
12926 : :
5586 tgl@sss.pgh.pa.us 12927 [ + + + + ]:CBC 5529 : if ((p_result == FUNCDETAIL_NORMAL ||
12928 [ + + ]: 602 : p_result == FUNCDETAIL_AGGREGATE ||
12929 : 4981 : p_result == FUNCDETAIL_WINDOWFUNC) &&
7590 12930 [ + - ]: 4981 : p_funcid == funcid)
8017 12931 : 4981 : nspname = NULL;
12932 : : else
992 12933 : 548 : nspname = get_namespace_name_or_temp(procform->pronamespace);
12934 : :
8017 12935 : 5529 : result = quote_qualified_identifier(nspname, proname);
12936 : :
12937 : 5529 : ReleaseSysCache(proctup);
12938 : :
12939 : 5529 : return result;
12940 : : }
12941 : :
12942 : : /*
12943 : : * generate_operator_name
12944 : : * Compute the name to display for an operator specified by OID,
12945 : : * given that it is being called with the specified actual arg types.
12946 : : * (Arg types matter because of ambiguous-operator resolution rules.
12947 : : * Pass InvalidOid for unused arg of a unary operator.)
12948 : : *
12949 : : * The result includes all necessary quoting and schema-prefixing,
12950 : : * plus the OPERATOR() decoration needed to use a qualified operator name
12951 : : * in an expression.
12952 : : */
12953 : : static char *
12954 : 28572 : generate_operator_name(Oid operid, Oid arg1, Oid arg2)
12955 : : {
12956 : : StringInfoData buf;
12957 : : HeapTuple opertup;
12958 : : Form_pg_operator operform;
12959 : : char *oprname;
12960 : : char *nspname;
12961 : : Operator p_result;
12962 : :
12963 : 28572 : initStringInfo(&buf);
12964 : :
5173 rhaas@postgresql.org 12965 : 28572 : opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operid));
8017 tgl@sss.pgh.pa.us 12966 [ - + ]: 28572 : if (!HeapTupleIsValid(opertup))
7567 tgl@sss.pgh.pa.us 12967 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for operator %u", operid);
8017 tgl@sss.pgh.pa.us 12968 :CBC 28572 : operform = (Form_pg_operator) GETSTRUCT(opertup);
12969 : 28572 : oprname = NameStr(operform->oprname);
12970 : :
12971 : : /*
12972 : : * The idea here is to schema-qualify only if the parser would fail to
12973 : : * resolve the correct operator given the unqualified op name with the
12974 : : * specified argtypes.
12975 : : */
12976 [ + + - ]: 28572 : switch (operform->oprkind)
12977 : : {
12978 : 28557 : case 'b':
6606 12979 : 28557 : p_result = oper(NULL, list_make1(makeString(oprname)), arg1, arg2,
12980 : : true, -1);
8017 12981 : 28557 : break;
12982 : 15 : case 'l':
6606 12983 : 15 : p_result = left_oper(NULL, list_make1(makeString(oprname)), arg2,
12984 : : true, -1);
8017 12985 : 15 : break;
8017 tgl@sss.pgh.pa.us 12986 :UBC 0 : default:
7567 12987 [ # # ]: 0 : elog(ERROR, "unrecognized oprkind: %d", operform->oprkind);
12988 : : p_result = NULL; /* keep compiler quiet */
12989 : : break;
12990 : : }
12991 : :
8017 tgl@sss.pgh.pa.us 12992 [ + + + - ]:CBC 28572 : if (p_result != NULL && oprid(p_result) == operid)
12993 : 28567 : nspname = NULL;
12994 : : else
12995 : : {
992 12996 : 5 : nspname = get_namespace_name_or_temp(operform->oprnamespace);
8017 12997 : 5 : appendStringInfo(&buf, "OPERATOR(%s.", quote_identifier(nspname));
12998 : : }
12999 : :
7379 neilc@samurai.com 13000 : 28572 : appendStringInfoString(&buf, oprname);
13001 : :
8017 tgl@sss.pgh.pa.us 13002 [ + + ]: 28572 : if (nspname)
13003 : 5 : appendStringInfoChar(&buf, ')');
13004 : :
13005 [ + + ]: 28572 : if (p_result != NULL)
13006 : 28567 : ReleaseSysCache(p_result);
13007 : :
13008 : 28572 : ReleaseSysCache(opertup);
13009 : :
13010 : 28572 : return buf.data;
13011 : : }
13012 : :
13013 : : /*
13014 : : * generate_operator_clause --- generate a binary-operator WHERE clause
13015 : : *
13016 : : * This is used for internally-generated-and-executed SQL queries, where
13017 : : * precision is essential and readability is secondary. The basic
13018 : : * requirement is to append "leftop op rightop" to buf, where leftop and
13019 : : * rightop are given as strings and are assumed to yield types leftoptype
13020 : : * and rightoptype; the operator is identified by OID. The complexity
13021 : : * comes from needing to be sure that the parser will select the desired
13022 : : * operator when the query is parsed. We always name the operator using
13023 : : * OPERATOR(schema.op) syntax, so as to avoid search-path uncertainties.
13024 : : * We have to emit casts too, if either input isn't already the input type
13025 : : * of the operator; else we are at the mercy of the parser's heuristics for
13026 : : * ambiguous-operator resolution. The caller must ensure that leftop and
13027 : : * rightop are suitable arguments for a cast operation; it's best to insert
13028 : : * parentheses if they aren't just variables or parameters.
13029 : : */
13030 : : void
2218 13031 : 3043 : generate_operator_clause(StringInfo buf,
13032 : : const char *leftop, Oid leftoptype,
13033 : : Oid opoid,
13034 : : const char *rightop, Oid rightoptype)
13035 : : {
13036 : : HeapTuple opertup;
13037 : : Form_pg_operator operform;
13038 : : char *oprname;
13039 : : char *nspname;
13040 : :
13041 : 3043 : opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(opoid));
13042 [ - + ]: 3043 : if (!HeapTupleIsValid(opertup))
2218 tgl@sss.pgh.pa.us 13043 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for operator %u", opoid);
2218 tgl@sss.pgh.pa.us 13044 :CBC 3043 : operform = (Form_pg_operator) GETSTRUCT(opertup);
13045 [ - + ]: 3043 : Assert(operform->oprkind == 'b');
13046 : 3043 : oprname = NameStr(operform->oprname);
13047 : :
13048 : 3043 : nspname = get_namespace_name(operform->oprnamespace);
13049 : :
13050 : 3043 : appendStringInfoString(buf, leftop);
13051 [ + + ]: 3043 : if (leftoptype != operform->oprleft)
13052 : 512 : add_cast_to(buf, operform->oprleft);
13053 : 3043 : appendStringInfo(buf, " OPERATOR(%s.", quote_identifier(nspname));
13054 : 3043 : appendStringInfoString(buf, oprname);
13055 : 3043 : appendStringInfo(buf, ") %s", rightop);
13056 [ + + ]: 3043 : if (rightoptype != operform->oprright)
13057 : 409 : add_cast_to(buf, operform->oprright);
13058 : :
13059 : 3043 : ReleaseSysCache(opertup);
13060 : 3043 : }
13061 : :
13062 : : /*
13063 : : * Add a cast specification to buf. We spell out the type name the hard way,
13064 : : * intentionally not using format_type_be(). This is to avoid corner cases
13065 : : * for CHARACTER, BIT, and perhaps other types, where specifying the type
13066 : : * using SQL-standard syntax results in undesirable data truncation. By
13067 : : * doing it this way we can be certain that the cast will have default (-1)
13068 : : * target typmod.
13069 : : */
13070 : : static void
13071 : 921 : add_cast_to(StringInfo buf, Oid typid)
13072 : : {
13073 : : HeapTuple typetup;
13074 : : Form_pg_type typform;
13075 : : char *typname;
13076 : : char *nspname;
13077 : :
13078 : 921 : typetup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
13079 [ - + ]: 921 : if (!HeapTupleIsValid(typetup))
2218 tgl@sss.pgh.pa.us 13080 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for type %u", typid);
2218 tgl@sss.pgh.pa.us 13081 :CBC 921 : typform = (Form_pg_type) GETSTRUCT(typetup);
13082 : :
13083 : 921 : typname = NameStr(typform->typname);
992 13084 : 921 : nspname = get_namespace_name_or_temp(typform->typnamespace);
13085 : :
2218 13086 : 921 : appendStringInfo(buf, "::%s.%s",
13087 : : quote_identifier(nspname), quote_identifier(typname));
13088 : :
13089 : 921 : ReleaseSysCache(typetup);
13090 : 921 : }
13091 : :
13092 : : /*
13093 : : * generate_qualified_type_name
13094 : : * Compute the name to display for a type specified by OID
13095 : : *
13096 : : * This is different from format_type_be() in that we unconditionally
13097 : : * schema-qualify the name. That also means no special syntax for
13098 : : * SQL-standard type names ... although in current usage, this should
13099 : : * only get used for domains, so such cases wouldn't occur anyway.
13100 : : */
13101 : : static char *
2356 13102 : 7 : generate_qualified_type_name(Oid typid)
13103 : : {
13104 : : HeapTuple tp;
13105 : : Form_pg_type typtup;
13106 : : char *typname;
13107 : : char *nspname;
13108 : : char *result;
13109 : :
13110 : 7 : tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
13111 [ - + ]: 7 : if (!HeapTupleIsValid(tp))
2356 tgl@sss.pgh.pa.us 13112 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for type %u", typid);
2356 tgl@sss.pgh.pa.us 13113 :CBC 7 : typtup = (Form_pg_type) GETSTRUCT(tp);
13114 : 7 : typname = NameStr(typtup->typname);
13115 : :
992 13116 : 7 : nspname = get_namespace_name_or_temp(typtup->typnamespace);
2356 13117 [ - + ]: 7 : if (!nspname)
2356 tgl@sss.pgh.pa.us 13118 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for namespace %u",
13119 : : typtup->typnamespace);
13120 : :
2356 tgl@sss.pgh.pa.us 13121 :CBC 7 : result = quote_qualified_identifier(nspname, typname);
13122 : :
13123 : 7 : ReleaseSysCache(tp);
13124 : :
13125 : 7 : return result;
13126 : : }
13127 : :
13128 : : /*
13129 : : * generate_collation_name
13130 : : * Compute the name to display for a collation specified by OID
13131 : : *
13132 : : * The result includes all necessary quoting and schema-prefixing.
13133 : : */
13134 : : char *
4814 peter_e@gmx.net 13135 : 143 : generate_collation_name(Oid collid)
13136 : : {
13137 : : HeapTuple tp;
13138 : : Form_pg_collation colltup;
13139 : : char *collname;
13140 : : char *nspname;
13141 : : char *result;
13142 : :
13143 : 143 : tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid));
13144 [ - + ]: 143 : if (!HeapTupleIsValid(tp))
4814 peter_e@gmx.net 13145 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for collation %u", collid);
4814 peter_e@gmx.net 13146 :CBC 143 : colltup = (Form_pg_collation) GETSTRUCT(tp);
13147 : 143 : collname = NameStr(colltup->collname);
13148 : :
13149 [ - + ]: 143 : if (!CollationIsVisible(collid))
992 tgl@sss.pgh.pa.us 13150 :UBC 0 : nspname = get_namespace_name_or_temp(colltup->collnamespace);
13151 : : else
4814 peter_e@gmx.net 13152 :CBC 143 : nspname = NULL;
13153 : :
13154 : 143 : result = quote_qualified_identifier(nspname, collname);
13155 : :
13156 : 143 : ReleaseSysCache(tp);
13157 : :
13158 : 143 : return result;
13159 : : }
13160 : :
13161 : : /*
13162 : : * Given a C string, produce a TEXT datum.
13163 : : *
13164 : : * We assume that the input was palloc'd and may be freed.
13165 : : */
13166 : : static text *
7284 tgl@sss.pgh.pa.us 13167 : 20179 : string_to_text(char *str)
13168 : : {
13169 : : text *result;
13170 : :
5864 13171 : 20179 : result = cstring_to_text(str);
7284 13172 : 20179 : pfree(str);
13173 : 20179 : return result;
13174 : : }
13175 : :
13176 : : /*
13177 : : * Generate a C string representing a relation options from text[] datum.
13178 : : */
13179 : : static void
1476 akorotkov@postgresql 13180 : 120 : get_reloptions(StringInfo buf, Datum reloptions)
13181 : : {
13182 : : Datum *options;
13183 : : int noptions;
13184 : : int i;
13185 : :
653 peter@eisentraut.org 13186 : 120 : deconstruct_array_builtin(DatumGetArrayTypeP(reloptions), TEXTOID,
13187 : : &options, NULL, &noptions);
13188 : :
1476 akorotkov@postgresql 13189 [ + + ]: 250 : for (i = 0; i < noptions; i++)
13190 : : {
13191 : 130 : char *option = TextDatumGetCString(options[i]);
13192 : : char *name;
13193 : : char *separator;
13194 : : char *value;
13195 : :
13196 : : /*
13197 : : * Each array element should have the form name=value. If the "=" is
13198 : : * missing for some reason, treat it like an empty value.
13199 : : */
13200 : 130 : name = option;
13201 : 130 : separator = strchr(option, '=');
13202 [ + - ]: 130 : if (separator)
13203 : : {
13204 : 130 : *separator = '\0';
13205 : 130 : value = separator + 1;
13206 : : }
13207 : : else
1476 akorotkov@postgresql 13208 :UBC 0 : value = "";
13209 : :
1476 akorotkov@postgresql 13210 [ + + ]:CBC 130 : if (i > 0)
13211 : 10 : appendStringInfoString(buf, ", ");
13212 : 130 : appendStringInfo(buf, "%s=", quote_identifier(name));
13213 : :
13214 : : /*
13215 : : * In general we need to quote the value; but to avoid unnecessary
13216 : : * clutter, do not quote if it is an identifier that would not need
13217 : : * quoting. (We could also allow numbers, but that is a bit trickier
13218 : : * than it looks --- for example, are leading zeroes significant? We
13219 : : * don't want to assume very much here about what custom reloptions
13220 : : * might mean.)
13221 : : */
13222 [ + + ]: 130 : if (quote_identifier(value) == value)
13223 : 4 : appendStringInfoString(buf, value);
13224 : : else
13225 : 126 : simple_quote_literal(buf, value);
13226 : :
13227 : 130 : pfree(option);
13228 : : }
13229 : 120 : }
13230 : :
13231 : : /*
13232 : : * Generate a C string representing a relation's reloptions, or NULL if none.
13233 : : */
13234 : : static char *
6496 bruce@momjian.us 13235 : 3425 : flatten_reloptions(Oid relid)
13236 : : {
13237 : 3425 : char *result = NULL;
13238 : : HeapTuple tuple;
13239 : : Datum reloptions;
13240 : : bool isnull;
13241 : :
5173 rhaas@postgresql.org 13242 : 3425 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
6495 tgl@sss.pgh.pa.us 13243 [ - + ]: 3425 : if (!HeapTupleIsValid(tuple))
6495 tgl@sss.pgh.pa.us 13244 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
13245 : :
6495 tgl@sss.pgh.pa.us 13246 :CBC 3425 : reloptions = SysCacheGetAttr(RELOID, tuple,
13247 : : Anum_pg_class_reloptions, &isnull);
13248 [ + + ]: 3425 : if (!isnull)
13249 : : {
13250 : : StringInfoData buf;
13251 : :
3026 13252 : 105 : initStringInfo(&buf);
1476 akorotkov@postgresql 13253 : 105 : get_reloptions(&buf, reloptions);
13254 : :
3026 tgl@sss.pgh.pa.us 13255 : 105 : result = buf.data;
13256 : : }
13257 : :
6495 13258 : 3425 : ReleaseSysCache(tuple);
13259 : :
6496 bruce@momjian.us 13260 : 3425 : return result;
13261 : : }
13262 : :
13263 : : /*
13264 : : * get_range_partbound_string
13265 : : * A C string representation of one range partition bound
13266 : : */
13267 : : char *
2439 rhaas@postgresql.org 13268 : 2310 : get_range_partbound_string(List *bound_datums)
13269 : : {
13270 : : deparse_context context;
13271 : 2310 : StringInfo buf = makeStringInfo();
13272 : : ListCell *cell;
13273 : : char *sep;
13274 : :
13275 : 2310 : memset(&context, 0, sizeof(deparse_context));
13276 : 2310 : context.buf = buf;
13277 : :
1277 drowley@postgresql.o 13278 : 2310 : appendStringInfoChar(buf, '(');
2439 rhaas@postgresql.org 13279 : 2310 : sep = "";
13280 [ + - + + : 5016 : foreach(cell, bound_datums)
+ + ]
13281 : : {
13282 : : PartitionRangeDatum *datum =
331 tgl@sss.pgh.pa.us 13283 : 2706 : lfirst_node(PartitionRangeDatum, cell);
13284 : :
2439 rhaas@postgresql.org 13285 : 2706 : appendStringInfoString(buf, sep);
13286 [ + + ]: 2706 : if (datum->kind == PARTITION_RANGE_DATUM_MINVALUE)
13287 : 111 : appendStringInfoString(buf, "MINVALUE");
13288 [ + + ]: 2595 : else if (datum->kind == PARTITION_RANGE_DATUM_MAXVALUE)
13289 : 60 : appendStringInfoString(buf, "MAXVALUE");
13290 : : else
13291 : : {
13292 : 2535 : Const *val = castNode(Const, datum->value);
13293 : :
13294 : 2535 : get_const_expr(val, &context, -1);
13295 : : }
13296 : 2706 : sep = ", ";
13297 : : }
2434 peter_e@gmx.net 13298 : 2310 : appendStringInfoChar(buf, ')');
13299 : :
2439 rhaas@postgresql.org 13300 : 2310 : return buf->data;
13301 : : }
13302 : :
13303 : : /*
13304 : : * get_list_partvalue_string
13305 : : * A C string representation of one list partition value
13306 : : */
13307 : : char *
7 akorotkov@postgresql 13308 :GNC 3 : get_list_partvalue_string(Const *val)
13309 : : {
13310 : : deparse_context context;
13311 : 3 : StringInfo buf = makeStringInfo();
13312 : :
13313 : 3 : memset(&context, 0, sizeof(deparse_context));
13314 : 3 : context.buf = buf;
13315 : :
13316 : 3 : get_const_expr(val, &context, -1);
13317 : :
13318 : 3 : return buf->data;
13319 : : }
|