| /*------------------------------------------------------------------------- |
| * |
| * ruleutils.c |
| * Functions to convert stored expressions/querytrees back to |
| * source text |
| * |
| * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * |
| * IDENTIFICATION |
| * src/backend/utils/adt/ruleutils.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| |
| #include <ctype.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| |
| #include "access/amapi.h" |
| #include "access/htup_details.h" |
| #include "access/relation.h" |
| #include "access/sysattr.h" |
| #include "access/table.h" |
| #include "catalog/pg_aggregate.h" |
| #include "catalog/pg_am.h" |
| #include "catalog/pg_authid.h" |
| #include "catalog/pg_collation.h" |
| #include "catalog/pg_constraint.h" |
| #include "catalog/pg_depend.h" |
| #include "catalog/pg_language.h" |
| #include "catalog/pg_opclass.h" |
| #include "catalog/pg_operator.h" |
| #include "catalog/pg_partitioned_table.h" |
| #include "catalog/pg_proc.h" |
| #include "catalog/pg_statistic_ext.h" |
| #include "catalog/pg_task.h" |
| #include "catalog/pg_trigger.h" |
| #include "catalog/pg_type.h" |
| #include "commands/defrem.h" |
| #include "commands/tablecmds.h" |
| #include "commands/tablespace.h" |
| #include "common/fe_memutils.h" |
| #include "common/keywords.h" |
| #include "executor/spi.h" |
| #include "funcapi.h" |
| #include "mb/pg_wchar.h" |
| #include "miscadmin.h" |
| #include "nodes/makefuncs.h" |
| #include "nodes/nodeFuncs.h" |
| #include "nodes/pathnodes.h" |
| #include "optimizer/optimizer.h" |
| #include "parser/parse_agg.h" |
| #include "parser/parse_expr.h" |
| #include "parser/parse_func.h" |
| #include "parser/parse_node.h" |
| #include "parser/parse_oper.h" |
| #include "parser/parse_cte.h" |
| #include "parser/parse_relation.h" |
| #include "parser/parser.h" |
| #include "parser/parsetree.h" |
| #include "rewrite/rewriteHandler.h" |
| #include "rewrite/rewriteManip.h" |
| #include "rewrite/rewriteSupport.h" |
| #include "utils/array.h" |
| #include "utils/builtins.h" |
| #include "utils/fmgroids.h" |
| #include "utils/guc.h" |
| #include "utils/hsearch.h" |
| #include "utils/lsyscache.h" |
| #include "utils/partcache.h" |
| #include "utils/rel.h" |
| #include "utils/ruleutils.h" |
| #include "utils/snapmgr.h" |
| #include "utils/syscache.h" |
| #include "utils/typcache.h" |
| #include "utils/varlena.h" |
| #include "utils/xml.h" |
| |
| #include "cdb/cdbhash.h" |
| |
| /* ---------- |
| * Pretty formatting constants |
| * ---------- |
| */ |
| |
| /* Indent counts */ |
| #define PRETTYINDENT_STD 8 |
| #define PRETTYINDENT_JOIN 4 |
| #define PRETTYINDENT_VAR 4 |
| |
| #define PRETTYINDENT_LIMIT 40 /* wrap limit */ |
| |
| /* Pretty flags */ |
| #define PRETTYFLAG_PAREN 0x0001 |
| #define PRETTYFLAG_INDENT 0x0002 |
| #define PRETTYFLAG_SCHEMA 0x0004 |
| |
| /* Default line length for pretty-print wrapping: 0 means wrap always */ |
| #define WRAP_COLUMN_DEFAULT 0 |
| |
| /* macros to test if pretty action needed */ |
| #define PRETTY_PAREN(context) ((context)->prettyFlags & PRETTYFLAG_PAREN) |
| #define PRETTY_INDENT(context) ((context)->prettyFlags & PRETTYFLAG_INDENT) |
| #define PRETTY_SCHEMA(context) ((context)->prettyFlags & PRETTYFLAG_SCHEMA) |
| |
| /* macros for negative operator only used in current file */ |
| #define Int4NegOperator 558 |
| #define NumericNegOperator 1751 |
| |
| /* ---------- |
| * Local data types |
| * ---------- |
| */ |
| |
| /* Context info needed for invoking a recursive querytree display routine */ |
| typedef struct |
| { |
| StringInfo buf; /* output buffer to append to */ |
| List *namespaces; /* List of deparse_namespace nodes */ |
| List *windowClause; /* Current query level's WINDOW clause */ |
| List *windowTList; /* targetlist for resolving WINDOW clause */ |
| List *groupClause; /* Current query level's GROUP BY clause */ |
| int prettyFlags; /* enabling of pretty-print functions */ |
| int wrapColumn; /* max line length, or -1 for no limit */ |
| int indentLevel; /* current indent level for pretty-print */ |
| bool varprefix; /* true to print prefixes on Vars */ |
| ParseExprKind special_exprkind; /* set only for exprkinds needing special |
| * handling */ |
| Bitmapset *appendparents; /* if not null, map child Vars of these relids |
| * back to the parent rel */ |
| } deparse_context; |
| |
| /* |
| * Each level of query context around a subtree needs a level of Var namespace. |
| * A Var having varlevelsup=N refers to the N'th item (counting from 0) in |
| * the current context's namespaces list. |
| * |
| * rtable is the list of actual RTEs from the Query or PlannedStmt. |
| * rtable_names holds the alias name to be used for each RTE (either a C |
| * string, or NULL for nameless RTEs such as unnamed joins). |
| * rtable_columns holds the column alias names to be used for each RTE. |
| * |
| * subplans is a list of Plan trees for SubPlans and CTEs (it's only used |
| * in the PlannedStmt case). |
| * ctes is a list of CommonTableExpr nodes (only used in the Query case). |
| * appendrels, if not null (it's only used in the PlannedStmt case), is an |
| * array of AppendRelInfo nodes, indexed by child relid. We use that to map |
| * child-table Vars to their inheritance parents. |
| * |
| * In some cases we need to make names of merged JOIN USING columns unique |
| * across the whole query, not only per-RTE. If so, unique_using is true |
| * and using_names is a list of C strings representing names already assigned |
| * to USING columns. |
| * |
| * When deparsing plan trees, there is always just a single item in the |
| * deparse_namespace list (since a plan tree never contains Vars with |
| * varlevelsup > 0). We store the Plan node that is the immediate |
| * parent of the expression to be deparsed, as well as a list of that |
| * Plan's ancestors. In addition, we store its outer and inner subplan nodes, |
| * as well as their targetlists, and the index tlist if the current plan node |
| * might contain INDEX_VAR Vars. (These fields could be derived on-the-fly |
| * from the current Plan node, but it seems notationally clearer to set them |
| * up as separate fields.) |
| */ |
| typedef struct |
| { |
| List *rtable; /* List of RangeTblEntry nodes */ |
| List *rtable_names; /* Parallel list of names for RTEs */ |
| List *rtable_columns; /* Parallel list of deparse_columns structs */ |
| List *subplans; /* List of Plan trees for SubPlans */ |
| List *ctes; /* List of CommonTableExpr nodes */ |
| AppendRelInfo **appendrels; /* Array of AppendRelInfo nodes, or NULL */ |
| /* Workspace for column alias assignment: */ |
| bool unique_using; /* Are we making USING names globally unique */ |
| List *using_names; /* List of assigned names for USING columns */ |
| /* Remaining fields are used only when deparsing a Plan tree: */ |
| Plan *plan; /* immediate parent of current expression */ |
| List *ancestors; /* ancestors of plan */ |
| Plan *outer_plan; /* outer subnode, or NULL if none */ |
| Plan *inner_plan; /* inner subnode, or NULL if none */ |
| List *outer_tlist; /* referent for OUTER_VAR Vars */ |
| List *inner_tlist; /* referent for INNER_VAR Vars */ |
| List *index_tlist; /* referent for INDEX_VAR Vars */ |
| /* Special namespace representing a function signature: */ |
| char *funcname; |
| int numargs; |
| char **argnames; |
| } deparse_namespace; |
| |
| /* |
| * Per-relation data about column alias names. |
| * |
| * Selecting aliases is unreasonably complicated because of the need to dump |
| * rules/views whose underlying tables may have had columns added, deleted, or |
| * renamed since the query was parsed. We must nonetheless print the rule/view |
| * in a form that can be reloaded and will produce the same results as before. |
| * |
| * For each RTE used in the query, we must assign column aliases that are |
| * unique within that RTE. SQL does not require this of the original query, |
| * but due to factors such as *-expansion we need to be able to uniquely |
| * reference every column in a decompiled query. As long as we qualify all |
| * column references, per-RTE uniqueness is sufficient for that. |
| * |
| * However, we can't ensure per-column name uniqueness for unnamed join RTEs, |
| * since they just inherit column names from their input RTEs, and we can't |
| * rename the columns at the join level. Most of the time this isn't an issue |
| * because we don't need to reference the join's output columns as such; we |
| * can reference the input columns instead. That approach can fail for merged |
| * JOIN USING columns, however, so when we have one of those in an unnamed |
| * join, we have to make that column's alias globally unique across the whole |
| * query to ensure it can be referenced unambiguously. |
| * |
| * Another problem is that a JOIN USING clause requires the columns to be |
| * merged to have the same aliases in both input RTEs, and that no other |
| * columns in those RTEs or their children conflict with the USING names. |
| * To handle that, we do USING-column alias assignment in a recursive |
| * traversal of the query's jointree. When descending through a JOIN with |
| * USING, we preassign the USING column names to the child columns, overriding |
| * other rules for column alias assignment. We also mark each RTE with a list |
| * of all USING column names selected for joins containing that RTE, so that |
| * when we assign other columns' aliases later, we can avoid conflicts. |
| * |
| * Another problem is that if a JOIN's input tables have had columns added or |
| * deleted since the query was parsed, we must generate a column alias list |
| * for the join that matches the current set of input columns --- otherwise, a |
| * change in the number of columns in the left input would throw off matching |
| * of aliases to columns of the right input. Thus, positions in the printable |
| * column alias list are not necessarily one-for-one with varattnos of the |
| * JOIN, so we need a separate new_colnames[] array for printing purposes. |
| */ |
| typedef struct |
| { |
| /* |
| * colnames is an array containing column aliases to use for columns that |
| * existed when the query was parsed. Dropped columns have NULL entries. |
| * This array can be directly indexed by varattno to get a Var's name. |
| * |
| * Non-NULL entries are guaranteed unique within the RTE, *except* when |
| * this is for an unnamed JOIN RTE. In that case we merely copy up names |
| * from the two input RTEs. |
| * |
| * During the recursive descent in set_using_names(), forcible assignment |
| * of a child RTE's column name is represented by pre-setting that element |
| * of the child's colnames array. So at that stage, NULL entries in this |
| * array just mean that no name has been preassigned, not necessarily that |
| * the column is dropped. |
| */ |
| int num_cols; /* length of colnames[] array */ |
| char **colnames; /* array of C strings and NULLs */ |
| |
| /* |
| * new_colnames is an array containing column aliases to use for columns |
| * that would exist if the query was re-parsed against the current |
| * definitions of its base tables. This is what to print as the column |
| * alias list for the RTE. This array does not include dropped columns, |
| * but it will include columns added since original parsing. Indexes in |
| * it therefore have little to do with current varattno values. As above, |
| * entries are unique unless this is for an unnamed JOIN RTE. (In such an |
| * RTE, we never actually print this array, but we must compute it anyway |
| * for possible use in computing column names of upper joins.) The |
| * parallel array is_new_col marks which of these columns are new since |
| * original parsing. Entries with is_new_col false must match the |
| * non-NULL colnames entries one-for-one. |
| */ |
| int num_new_cols; /* length of new_colnames[] array */ |
| char **new_colnames; /* array of C strings */ |
| bool *is_new_col; /* array of bool flags */ |
| |
| /* This flag tells whether we should actually print a column alias list */ |
| bool printaliases; |
| |
| /* This list has all names used as USING names in joins above this RTE */ |
| List *parentUsing; /* names assigned to parent merged columns */ |
| |
| /* |
| * If this struct is for a JOIN RTE, we fill these fields during the |
| * set_using_names() pass to describe its relationship to its child RTEs. |
| * |
| * leftattnos and rightattnos are arrays with one entry per existing |
| * output column of the join (hence, indexable by join varattno). For a |
| * simple reference to a column of the left child, leftattnos[i] is the |
| * child RTE's attno and rightattnos[i] is zero; and conversely for a |
| * column of the right child. But for merged columns produced by JOIN |
| * USING/NATURAL JOIN, both leftattnos[i] and rightattnos[i] are nonzero. |
| * Note that a simple reference might be to a child RTE column that's been |
| * dropped; but that's OK since the column could not be used in the query. |
| * |
| * If it's a JOIN USING, usingNames holds the alias names selected for the |
| * merged columns (these might be different from the original USING list, |
| * if we had to modify names to achieve uniqueness). |
| */ |
| int leftrti; /* rangetable index of left child */ |
| int rightrti; /* rangetable index of right child */ |
| int *leftattnos; /* left-child varattnos of join cols, or 0 */ |
| int *rightattnos; /* right-child varattnos of join cols, or 0 */ |
| List *usingNames; /* names assigned to merged columns */ |
| } deparse_columns; |
| |
| /* This macro is analogous to rt_fetch(), but for deparse_columns structs */ |
| #define deparse_columns_fetch(rangetable_index, dpns) \ |
| ((deparse_columns *) list_nth((dpns)->rtable_columns, (rangetable_index)-1)) |
| |
| /* |
| * Entry in set_rtable_names' hash table |
| */ |
| typedef struct |
| { |
| char name[NAMEDATALEN]; /* Hash key --- must be first */ |
| int counter; /* Largest addition used so far for name */ |
| } NameHashEntry; |
| |
| /* Callback signature for resolve_special_varno() */ |
| typedef void (*rsv_callback) (Node *node, deparse_context *context, |
| void *callback_arg); |
| |
| |
| /* ---------- |
| * Global data |
| * ---------- |
| */ |
| static SPIPlanPtr plan_getrulebyoid = NULL; |
| static const char *query_getrulebyoid = "SELECT * FROM pg_catalog.pg_rewrite WHERE oid = $1"; |
| static SPIPlanPtr plan_getviewrule = NULL; |
| static const char *query_getviewrule = "SELECT * FROM pg_catalog.pg_rewrite WHERE ev_class = $1 AND rulename = $2"; |
| |
| /* GUC parameters */ |
| bool quote_all_identifiers = false; |
| |
| |
| /* ---------- |
| * Local functions |
| * |
| * Most of these functions used to use fixed-size buffers to build their |
| * results. Now, they take an (already initialized) StringInfo object |
| * as a parameter, and append their text output to its contents. |
| * ---------- |
| */ |
| static char *deparse_expression_pretty(Node *expr, List *dpcontext, |
| bool forceprefix, bool showimplicit, |
| int prettyFlags, int startIndent); |
| static char *pg_get_viewdef_worker(Oid viewoid, |
| int prettyFlags, int wrapColumn); |
| static char *pg_get_triggerdef_worker(Oid trigid, bool pretty); |
| static int decompile_column_index_array(Datum column_index_array, Oid relId, |
| StringInfo buf); |
| static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags); |
| static char *pg_get_indexdef_worker(Oid indexrelid, int colno, |
| const Oid *excludeOps, |
| bool attrsOnly, bool keysOnly, |
| bool showTblSpc, bool inherits, |
| int prettyFlags, bool missing_ok); |
| static char *pg_get_statisticsobj_worker(Oid statextid, bool columns_only, |
| bool missing_ok); |
| static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags, |
| bool attrsOnly, bool missing_ok); |
| static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, |
| int prettyFlags, bool missing_ok); |
| static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname, |
| int prettyFlags); |
| static int print_function_arguments(StringInfo buf, HeapTuple proctup, |
| bool print_table_args, bool print_defaults); |
| static void print_function_rettype(StringInfo buf, HeapTuple proctup); |
| static void print_function_trftypes(StringInfo buf, HeapTuple proctup); |
| static void print_function_sqlbody(StringInfo buf, HeapTuple proctup); |
| static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces, |
| Bitmapset *rels_used); |
| static void set_deparse_for_query(deparse_namespace *dpns, Query *query, |
| List *parent_namespaces); |
| static void set_simple_column_names(deparse_namespace *dpns); |
| static bool has_dangerous_join_using(deparse_namespace *dpns, Node *jtnode); |
| static void set_using_names(deparse_namespace *dpns, Node *jtnode, |
| List *parentUsing); |
| static void set_relation_column_names(deparse_namespace *dpns, |
| RangeTblEntry *rte, |
| deparse_columns *colinfo); |
| static void set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte, |
| deparse_columns *colinfo); |
| static bool colname_is_unique(const char *colname, deparse_namespace *dpns, |
| deparse_columns *colinfo); |
| static char *make_colname_unique(char *colname, deparse_namespace *dpns, |
| deparse_columns *colinfo); |
| static void expand_colnames_array_to(deparse_columns *colinfo, int n); |
| static void identify_join_columns(JoinExpr *j, RangeTblEntry *jrte, |
| deparse_columns *colinfo); |
| static char *get_rtable_name(int rtindex, deparse_context *context); |
| static void set_deparse_plan(deparse_namespace *dpns, Plan *plan); |
| static Plan *find_recursive_union(deparse_namespace *dpns, |
| WorkTableScan *wtscan); |
| static void push_child_plan(deparse_namespace *dpns, Plan *plan, |
| deparse_namespace *save_dpns); |
| static void pop_child_plan(deparse_namespace *dpns, |
| deparse_namespace *save_dpns); |
| static void push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell, |
| deparse_namespace *save_dpns); |
| static void pop_ancestor_plan(deparse_namespace *dpns, |
| deparse_namespace *save_dpns); |
| static void make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, |
| int prettyFlags); |
| static void make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, |
| int prettyFlags, int wrapColumn); |
| static void get_query_def(Query *query, StringInfo buf, List *parentnamespace, |
| TupleDesc resultDesc, bool colNamesVisible, |
| int prettyFlags, int wrapColumn, int startIndent); |
| static void get_values_def(List *values_lists, deparse_context *context); |
| static void get_with_clause(Query *query, deparse_context *context); |
| static void get_select_query_def(Query *query, deparse_context *context, |
| TupleDesc resultDesc, bool colNamesVisible); |
| static void get_insert_query_def(Query *query, deparse_context *context, |
| bool colNamesVisible); |
| static void get_update_query_def(Query *query, deparse_context *context, |
| bool colNamesVisible); |
| static void get_update_query_targetlist_def(Query *query, List *targetList, |
| deparse_context *context, |
| RangeTblEntry *rte); |
| static void get_delete_query_def(Query *query, deparse_context *context, |
| bool colNamesVisible); |
| static void get_utility_query_def(Query *query, deparse_context *context); |
| static void get_basic_select_query(Query *query, deparse_context *context, |
| TupleDesc resultDesc, bool colNamesVisible); |
| static void get_target_list(List *targetList, deparse_context *context, |
| TupleDesc resultDesc, bool colNamesVisible); |
| static void get_setop_query(Node *setOp, Query *query, |
| deparse_context *context, |
| TupleDesc resultDesc, bool colNamesVisible); |
| static Node *get_rule_sortgroupclause(Index ref, List *tlist, |
| bool force_colno, |
| deparse_context *context); |
| static void get_rule_groupingset(GroupingSet *gset, List *targetlist, |
| bool omit_parens, deparse_context *context); |
| static void get_rule_orderby(List *orderList, List *targetList, |
| bool force_colno, deparse_context *context); |
| static void get_rule_windowspec(WindowClause *wc, List *targetList, |
| deparse_context *context); |
| static char *get_variable(Var *var, int levelsup, bool istoplevel, |
| deparse_context *context); |
| static void get_special_variable(Node *node, deparse_context *context, |
| void *callback_arg); |
| static void resolve_special_varno(Node *node, deparse_context *context, |
| rsv_callback callback, void *callback_arg); |
| static Node *find_param_referent(Param *param, deparse_context *context, |
| deparse_namespace **dpns_p, ListCell **ancestor_cell_p); |
| static void get_parameter(Param *param, deparse_context *context); |
| static const char *get_simple_binary_op_name(OpExpr *expr); |
| static bool isSimpleNode(Node *node, Node *parentNode, int prettyFlags); |
| static void appendContextKeyword(deparse_context *context, const char *str, |
| int indentBefore, int indentAfter, int indentPlus); |
| static void removeStringInfoSpaces(StringInfo str); |
| static void get_rule_expr(Node *node, deparse_context *context, |
| bool showimplicit); |
| static void get_rule_expr_toplevel(Node *node, deparse_context *context, |
| bool showimplicit); |
| static void get_rule_list_toplevel(List *lst, deparse_context *context, |
| bool showimplicit); |
| static void get_rule_expr_funccall(Node *node, deparse_context *context, |
| bool showimplicit); |
| static bool looks_like_function(Node *node); |
| static void get_oper_expr(OpExpr *expr, deparse_context *context); |
| static void get_func_expr(FuncExpr *expr, deparse_context *context, |
| bool showimplicit); |
| static void get_dqa_expr(DQAExpr *dqa_expr,deparse_context *context); |
| static void get_agg_expr(Aggref *aggref, deparse_context *context, |
| Aggref *original_aggref); |
| static void get_agg_combine_expr(Node *node, deparse_context *context, |
| void *callback_arg); |
| static void get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context); |
| static bool get_func_sql_syntax(FuncExpr *expr, deparse_context *context); |
| static void get_coercion_expr(Node *arg, deparse_context *context, |
| Oid resulttype, int32 resulttypmod, |
| Node *parentNode); |
| static void get_const_expr(Const *constval, deparse_context *context, |
| int showtype); |
| static void get_const_collation(Const *constval, deparse_context *context); |
| static void simple_quote_literal(StringInfo buf, const char *val); |
| static void get_sublink_expr(SubLink *sublink, deparse_context *context); |
| static void get_tablefunc(TableFunc *tf, deparse_context *context, |
| bool showimplicit); |
| static void get_from_clause(Query *query, const char *prefix, |
| deparse_context *context); |
| static void get_from_clause_item(Node *jtnode, Query *query, |
| deparse_context *context); |
| static void get_column_alias_list(deparse_columns *colinfo, |
| deparse_context *context); |
| static void get_from_clause_coldeflist(RangeTblFunction *rtfunc, |
| deparse_columns *colinfo, |
| deparse_context *context); |
| static void get_tablesample_def(TableSampleClause *tablesample, |
| deparse_context *context); |
| static void get_opclass_name(Oid opclass, Oid actual_datatype, |
| StringInfo buf); |
| static Node *processIndirection(Node *node, deparse_context *context); |
| static void printSubscripts(SubscriptingRef *sbsref, deparse_context *context); |
| static char *get_relation_name(Oid relid); |
| static char *generate_relation_name(Oid relid, List *namespaces); |
| static char *generate_qualified_relation_name(Oid relid); |
| static char *generate_function_name(Oid funcid, int nargs, |
| List *argnames, Oid *argtypes, |
| bool has_variadic, bool *use_variadic_p, |
| ParseExprKind special_exprkind); |
| static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2); |
| static void add_cast_to(StringInfo buf, Oid typid); |
| static char *generate_qualified_type_name(Oid typid); |
| static text *string_to_text(char *str); |
| static char *flatten_reloptions(Oid relid); |
| static void get_reloptions(StringInfo buf, Datum reloptions); |
| |
| #define only_marker(rte) ((rte)->inh ? "" : "ONLY ") |
| |
| |
| /* ---------- |
| * pg_get_ruledef - Do it all and return a text |
| * that could be used as a statement |
| * to recreate the rule |
| * ---------- |
| */ |
| Datum |
| pg_get_ruledef(PG_FUNCTION_ARGS) |
| { |
| Oid ruleoid = PG_GETARG_OID(0); |
| int prettyFlags; |
| char *res; |
| |
| prettyFlags = PRETTYFLAG_INDENT; |
| |
| res = pg_get_ruledef_worker(ruleoid, prettyFlags); |
| |
| if (res == NULL) |
| PG_RETURN_NULL(); |
| |
| PG_RETURN_TEXT_P(string_to_text(res)); |
| } |
| |
| |
| Datum |
| pg_get_ruledef_ext(PG_FUNCTION_ARGS) |
| { |
| Oid ruleoid = PG_GETARG_OID(0); |
| bool pretty = PG_GETARG_BOOL(1); |
| int prettyFlags; |
| char *res; |
| |
| prettyFlags = pretty ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT | PRETTYFLAG_SCHEMA) : PRETTYFLAG_INDENT; |
| |
| res = pg_get_ruledef_worker(ruleoid, prettyFlags); |
| |
| if (res == NULL) |
| PG_RETURN_NULL(); |
| |
| PG_RETURN_TEXT_P(string_to_text(res)); |
| } |
| |
| |
| static char * |
| pg_get_ruledef_worker(Oid ruleoid, int prettyFlags) |
| { |
| Datum args[1]; |
| char nulls[1]; |
| int spirc; |
| HeapTuple ruletup; |
| TupleDesc rulettc; |
| StringInfoData buf; |
| |
| /* |
| * Do this first so that string is alloc'd in outer context not SPI's. |
| */ |
| initStringInfo(&buf); |
| |
| /* |
| * Connect to SPI manager |
| */ |
| if (SPI_connect() != SPI_OK_CONNECT) |
| elog(ERROR, "SPI_connect failed"); |
| |
| /* |
| * On the first call prepare the plan to lookup pg_rewrite. We read |
| * pg_rewrite over the SPI manager instead of using the syscache to be |
| * checked for read access on pg_rewrite. |
| */ |
| if (plan_getrulebyoid == NULL) |
| { |
| Oid argtypes[1]; |
| SPIPlanPtr plan; |
| |
| argtypes[0] = OIDOID; |
| plan = SPI_prepare(query_getrulebyoid, 1, argtypes); |
| if (plan == NULL) |
| elog(ERROR, "SPI_prepare failed for \"%s\"", query_getrulebyoid); |
| SPI_keepplan(plan); |
| plan_getrulebyoid = plan; |
| } |
| |
| /* |
| * Get the pg_rewrite tuple for this rule |
| */ |
| args[0] = ObjectIdGetDatum(ruleoid); |
| nulls[0] = ' '; |
| spirc = SPI_execute_plan(plan_getrulebyoid, args, nulls, true, 0); |
| if (spirc != SPI_OK_SELECT) |
| elog(ERROR, "failed to get pg_rewrite tuple for rule %u", ruleoid); |
| if (SPI_processed != 1) |
| { |
| /* |
| * There is no tuple data available here, just keep the output buffer |
| * empty. |
| */ |
| } |
| else |
| { |
| /* |
| * Get the rule's definition and put it into executor's memory |
| */ |
| ruletup = SPI_tuptable->vals[0]; |
| rulettc = SPI_tuptable->tupdesc; |
| make_ruledef(&buf, ruletup, rulettc, prettyFlags); |
| } |
| |
| /* |
| * Disconnect from SPI manager |
| */ |
| if (SPI_finish() != SPI_OK_FINISH) |
| elog(ERROR, "SPI_finish failed"); |
| |
| if (buf.len == 0) |
| return NULL; |
| |
| return buf.data; |
| } |
| |
| |
| /* ---------- |
| * pg_get_viewdef - Mainly the same thing, but we |
| * only return the SELECT part of a view |
| * ---------- |
| */ |
| Datum |
| pg_get_viewdef(PG_FUNCTION_ARGS) |
| { |
| /* By OID */ |
| Oid viewoid = PG_GETARG_OID(0); |
| int prettyFlags; |
| char *res; |
| |
| prettyFlags = PRETTYFLAG_INDENT; |
| |
| res = pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT); |
| |
| if (res == NULL) |
| PG_RETURN_NULL(); |
| |
| PG_RETURN_TEXT_P(string_to_text(res)); |
| } |
| |
| |
| Datum |
| pg_get_viewdef_ext(PG_FUNCTION_ARGS) |
| { |
| /* By OID */ |
| Oid viewoid = PG_GETARG_OID(0); |
| bool pretty = PG_GETARG_BOOL(1); |
| int prettyFlags; |
| char *res; |
| |
| prettyFlags = pretty ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT | PRETTYFLAG_SCHEMA) : PRETTYFLAG_INDENT; |
| |
| res = pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT); |
| |
| if (res == NULL) |
| PG_RETURN_NULL(); |
| |
| PG_RETURN_TEXT_P(string_to_text(res)); |
| } |
| |
| Datum |
| pg_get_viewdef_wrap(PG_FUNCTION_ARGS) |
| { |
| /* By OID */ |
| Oid viewoid = PG_GETARG_OID(0); |
| int wrap = PG_GETARG_INT32(1); |
| int prettyFlags; |
| char *res; |
| |
| /* calling this implies we want pretty printing */ |
| prettyFlags = PRETTYFLAG_PAREN | PRETTYFLAG_INDENT | PRETTYFLAG_SCHEMA; |
| |
| res = pg_get_viewdef_worker(viewoid, prettyFlags, wrap); |
| |
| if (res == NULL) |
| PG_RETURN_NULL(); |
| |
| PG_RETURN_TEXT_P(string_to_text(res)); |
| } |
| |
| Datum |
| pg_get_viewdef_name(PG_FUNCTION_ARGS) |
| { |
| /* By qualified name */ |
| text *viewname = PG_GETARG_TEXT_PP(0); |
| int prettyFlags; |
| RangeVar *viewrel; |
| Oid viewoid; |
| char *res; |
| |
| prettyFlags = PRETTYFLAG_INDENT; |
| |
| /* Look up view name. Can't lock it - we might not have privileges. */ |
| viewrel = makeRangeVarFromNameList(textToQualifiedNameList(viewname)); |
| viewoid = RangeVarGetRelid(viewrel, NoLock, false); |
| |
| res = pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT); |
| |
| if (res == NULL) |
| PG_RETURN_NULL(); |
| |
| PG_RETURN_TEXT_P(string_to_text(res)); |
| } |
| |
| |
| Datum |
| pg_get_viewdef_name_ext(PG_FUNCTION_ARGS) |
| { |
| /* By qualified name */ |
| text *viewname = PG_GETARG_TEXT_PP(0); |
| bool pretty = PG_GETARG_BOOL(1); |
| int prettyFlags; |
| RangeVar *viewrel; |
| Oid viewoid; |
| char *res; |
| |
| prettyFlags = pretty ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT | PRETTYFLAG_SCHEMA) : PRETTYFLAG_INDENT; |
| |
| /* Look up view name. Can't lock it - we might not have privileges. */ |
| viewrel = makeRangeVarFromNameList(textToQualifiedNameList(viewname)); |
| viewoid = RangeVarGetRelid(viewrel, NoLock, false); |
| |
| res = pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT); |
| |
| if (res == NULL) |
| PG_RETURN_NULL(); |
| |
| PG_RETURN_TEXT_P(string_to_text(res)); |
| } |
| |
| /* |
| * Common code for by-OID and by-name variants of pg_get_viewdef |
| */ |
| static char * |
| pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn) |
| { |
| Datum args[2]; |
| char nulls[2]; |
| int spirc; |
| HeapTuple ruletup; |
| TupleDesc rulettc; |
| StringInfoData buf; |
| |
| /* |
| * Do this first so that string is alloc'd in outer context not SPI's. |
| */ |
| initStringInfo(&buf); |
| |
| /* |
| * Connect to SPI manager |
| */ |
| if (SPI_connect() != SPI_OK_CONNECT) |
| elog(ERROR, "SPI_connect failed"); |
| |
| /* |
| * On the first call prepare the plan to lookup pg_rewrite. We read |
| * pg_rewrite over the SPI manager instead of using the syscache to be |
| * checked for read access on pg_rewrite. |
| */ |
| if (plan_getviewrule == NULL) |
| { |
| Oid argtypes[2]; |
| SPIPlanPtr plan; |
| |
| argtypes[0] = OIDOID; |
| argtypes[1] = NAMEOID; |
| plan = SPI_prepare(query_getviewrule, 2, argtypes); |
| if (plan == NULL) |
| elog(ERROR, "SPI_prepare failed for \"%s\"", query_getviewrule); |
| SPI_keepplan(plan); |
| plan_getviewrule = plan; |
| } |
| |
| /* |
| * Get the pg_rewrite tuple for the view's SELECT rule |
| */ |
| args[0] = ObjectIdGetDatum(viewoid); |
| args[1] = DirectFunctionCall1(namein, CStringGetDatum(ViewSelectRuleName)); |
| nulls[0] = ' '; |
| nulls[1] = ' '; |
| spirc = SPI_execute_plan(plan_getviewrule, args, nulls, true, 0); |
| if (spirc != SPI_OK_SELECT) |
| elog(ERROR, "failed to get pg_rewrite tuple for view %u", viewoid); |
| if (SPI_processed != 1) |
| { |
| /* |
| * There is no tuple data available here, just keep the output buffer |
| * empty. |
| */ |
| } |
| else |
| { |
| /* |
| * Get the rule's definition and put it into executor's memory |
| */ |
| ruletup = SPI_tuptable->vals[0]; |
| rulettc = SPI_tuptable->tupdesc; |
| make_viewdef(&buf, ruletup, rulettc, prettyFlags, wrapColumn); |
| } |
| |
| /* |
| * Disconnect from SPI manager |
| */ |
| if (SPI_finish() != SPI_OK_FINISH) |
| elog(ERROR, "SPI_finish failed"); |
| |
| if (buf.len == 0) |
| return NULL; |
| |
| return buf.data; |
| } |
| |
| /* ---------- |
| * pg_get_triggerdef - Get the definition of a trigger |
| * ---------- |
| */ |
| Datum |
| pg_get_triggerdef(PG_FUNCTION_ARGS) |
| { |
| Oid trigid = PG_GETARG_OID(0); |
| char *res; |
| |
| res = pg_get_triggerdef_worker(trigid, false); |
| |
| if (res == NULL) |
| PG_RETURN_NULL(); |
| |
| PG_RETURN_TEXT_P(string_to_text(res)); |
| } |
| |
| Datum |
| pg_get_triggerdef_ext(PG_FUNCTION_ARGS) |
| { |
| Oid trigid = PG_GETARG_OID(0); |
| bool pretty = PG_GETARG_BOOL(1); |
| char *res; |
| |
| res = pg_get_triggerdef_worker(trigid, pretty); |
| |
| if (res == NULL) |
| PG_RETURN_NULL(); |
| |
| PG_RETURN_TEXT_P(string_to_text(res)); |
| } |
| |
| static char * |
| pg_get_triggerdef_worker(Oid trigid, bool pretty) |
| { |
| HeapTuple ht_trig; |
| Form_pg_trigger trigrec; |
| StringInfoData buf; |
| Relation tgrel; |
| ScanKeyData skey[1]; |
| SysScanDesc tgscan; |
| int findx = 0; |
| char *tgname; |
| char *tgoldtable; |
| char *tgnewtable; |
| Datum value; |
| bool isnull; |
| |
| /* |
| * Fetch the pg_trigger tuple by the Oid of the trigger |
| */ |
| tgrel = table_open(TriggerRelationId, AccessShareLock); |
| |
| ScanKeyInit(&skey[0], |
| Anum_pg_trigger_oid, |
| BTEqualStrategyNumber, F_OIDEQ, |
| ObjectIdGetDatum(trigid)); |
| |
| tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true, |
| NULL, 1, skey); |
| |
| ht_trig = systable_getnext(tgscan); |
| |
| if (!HeapTupleIsValid(ht_trig)) |
| { |
| systable_endscan(tgscan); |
| table_close(tgrel, AccessShareLock); |
| return NULL; |
| } |
| |
| trigrec = (Form_pg_trigger) GETSTRUCT(ht_trig); |
| |
| /* |
| * Start the trigger definition. Note that the trigger's name should never |
| * be schema-qualified, but the trigger rel's name may be. |
| */ |
| initStringInfo(&buf); |
| |
| tgname = NameStr(trigrec->tgname); |
| appendStringInfo(&buf, "CREATE %sTRIGGER %s ", |
| OidIsValid(trigrec->tgconstraint) ? "CONSTRAINT " : "", |
| quote_identifier(tgname)); |
| |
| if (TRIGGER_FOR_BEFORE(trigrec->tgtype)) |
| appendStringInfoString(&buf, "BEFORE"); |
| else if (TRIGGER_FOR_AFTER(trigrec->tgtype)) |
| appendStringInfoString(&buf, "AFTER"); |
| else if (TRIGGER_FOR_INSTEAD(trigrec->tgtype)) |
| appendStringInfoString(&buf, "INSTEAD OF"); |
| else |
| elog(ERROR, "unexpected tgtype value: %d", trigrec->tgtype); |
| |
| if (TRIGGER_FOR_INSERT(trigrec->tgtype)) |
| { |
| appendStringInfoString(&buf, " INSERT"); |
| findx++; |
| } |
| if (TRIGGER_FOR_DELETE(trigrec->tgtype)) |
| { |
| if (findx > 0) |
| appendStringInfoString(&buf, " OR DELETE"); |
| else |
| appendStringInfoString(&buf, " DELETE"); |
| findx++; |
| } |
| if (TRIGGER_FOR_UPDATE(trigrec->tgtype)) |
| { |
| if (findx > 0) |
| appendStringInfoString(&buf, " OR UPDATE"); |
| else |
| appendStringInfoString(&buf, " UPDATE"); |
| findx++; |
| /* tgattr is first var-width field, so OK to access directly */ |
| if (trigrec->tgattr.dim1 > 0) |
| { |
| int i; |
| |
| appendStringInfoString(&buf, " OF "); |
| for (i = 0; i < trigrec->tgattr.dim1; i++) |
| { |
| char *attname; |
| |
| if (i > 0) |
| appendStringInfoString(&buf, ", "); |
| attname = get_attname(trigrec->tgrelid, |
| trigrec->tgattr.values[i], false); |
| appendStringInfoString(&buf, quote_identifier(attname)); |
| } |
| } |
| } |
| if (TRIGGER_FOR_TRUNCATE(trigrec->tgtype)) |
| { |
| if (findx > 0) |
| appendStringInfoString(&buf, " OR TRUNCATE"); |
| else |
| appendStringInfoString(&buf, " TRUNCATE"); |
| findx++; |
| } |
| |
| /* |
| * In non-pretty mode, always schema-qualify the target table name for |
| * safety. In pretty mode, schema-qualify only if not visible. |
| */ |
| appendStringInfo(&buf, " ON %s ", |
| pretty ? |
| generate_relation_name(trigrec->tgrelid, NIL) : |
| generate_qualified_relation_name(trigrec->tgrelid)); |
| |
| if (OidIsValid(trigrec->tgconstraint)) |
| { |
| if (OidIsValid(trigrec->tgconstrrelid)) |
| appendStringInfo(&buf, "FROM %s ", |
| generate_relation_name(trigrec->tgconstrrelid, NIL)); |
| if (!trigrec->tgdeferrable) |
| appendStringInfoString(&buf, "NOT "); |
| appendStringInfoString(&buf, "DEFERRABLE INITIALLY "); |
| if (trigrec->tginitdeferred) |
| appendStringInfoString(&buf, "DEFERRED "); |
| else |
| appendStringInfoString(&buf, "IMMEDIATE "); |
| } |
| |
| value = fastgetattr(ht_trig, Anum_pg_trigger_tgoldtable, |
| tgrel->rd_att, &isnull); |
| if (!isnull) |
| tgoldtable = NameStr(*DatumGetName(value)); |
| else |
| tgoldtable = NULL; |
| value = fastgetattr(ht_trig, Anum_pg_trigger_tgnewtable, |
| tgrel->rd_att, &isnull); |
| if (!isnull) |
| tgnewtable = NameStr(*DatumGetName(value)); |
| else |
| tgnewtable = NULL; |
| if (tgoldtable != NULL || tgnewtable != NULL) |
| { |
| appendStringInfoString(&buf, "REFERENCING "); |
| if (tgoldtable != NULL) |
| appendStringInfo(&buf, "OLD TABLE AS %s ", |
| quote_identifier(tgoldtable)); |
| if (tgnewtable != NULL) |
| appendStringInfo(&buf, "NEW TABLE AS %s ", |
| quote_identifier(tgnewtable)); |
| } |
| |
| if (TRIGGER_FOR_ROW(trigrec->tgtype)) |
| appendStringInfoString(&buf, "FOR EACH ROW "); |
| else |
| appendStringInfoString(&buf, "FOR EACH STATEMENT "); |
| |
| /* If the trigger has a WHEN qualification, add that */ |
| value = fastgetattr(ht_trig, Anum_pg_trigger_tgqual, |
| tgrel->rd_att, &isnull); |
| if (!isnull) |
| { |
| Node *qual; |
| char relkind; |
| deparse_context context; |
| deparse_namespace dpns; |
| RangeTblEntry *oldrte; |
| RangeTblEntry *newrte; |
| |
| appendStringInfoString(&buf, "WHEN ("); |
| |
| qual = stringToNode(TextDatumGetCString(value)); |
| |
| relkind = get_rel_relkind(trigrec->tgrelid); |
| |
| /* Build minimal OLD and NEW RTEs for the rel */ |
| oldrte = makeNode(RangeTblEntry); |
| oldrte->rtekind = RTE_RELATION; |
| oldrte->relid = trigrec->tgrelid; |
| oldrte->relkind = relkind; |
| oldrte->rellockmode = AccessShareLock; |
| oldrte->alias = makeAlias("old", NIL); |
| oldrte->eref = oldrte->alias; |
| oldrte->lateral = false; |
| oldrte->inh = false; |
| oldrte->inFromCl = true; |
| |
| newrte = makeNode(RangeTblEntry); |
| newrte->rtekind = RTE_RELATION; |
| newrte->relid = trigrec->tgrelid; |
| newrte->relkind = relkind; |
| newrte->rellockmode = AccessShareLock; |
| newrte->alias = makeAlias("new", NIL); |
| newrte->eref = newrte->alias; |
| newrte->lateral = false; |
| newrte->inh = false; |
| newrte->inFromCl = true; |
| |
| /* Build two-element rtable */ |
| memset(&dpns, 0, sizeof(dpns)); |
| dpns.rtable = list_make2(oldrte, newrte); |
| dpns.subplans = NIL; |
| dpns.ctes = NIL; |
| dpns.appendrels = NULL; |
| set_rtable_names(&dpns, NIL, NULL); |
| set_simple_column_names(&dpns); |
| |
| /* Set up context with one-deep namespace stack */ |
| context.buf = &buf; |
| context.namespaces = list_make1(&dpns); |
| context.windowClause = NIL; |
| context.windowTList = NIL; |
| context.varprefix = true; |
| context.prettyFlags = pretty ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT | PRETTYFLAG_SCHEMA) : PRETTYFLAG_INDENT; |
| context.wrapColumn = WRAP_COLUMN_DEFAULT; |
| context.indentLevel = PRETTYINDENT_STD; |
| context.special_exprkind = EXPR_KIND_NONE; |
| context.appendparents = NULL; |
| |
| get_rule_expr(qual, &context, false); |
| |
| appendStringInfoString(&buf, ") "); |
| } |
| |
| appendStringInfo(&buf, "EXECUTE FUNCTION %s(", |
| generate_function_name(trigrec->tgfoid, 0, |
| NIL, NULL, |
| false, NULL, EXPR_KIND_NONE)); |
| |
| if (trigrec->tgnargs > 0) |
| { |
| char *p; |
| int i; |
| |
| value = fastgetattr(ht_trig, Anum_pg_trigger_tgargs, |
| tgrel->rd_att, &isnull); |
| if (isnull) |
| elog(ERROR, "tgargs is null for trigger %u", trigid); |
| p = (char *) VARDATA_ANY(DatumGetByteaPP(value)); |
| for (i = 0; i < trigrec->tgnargs; i++) |
| { |
| if (i > 0) |
| appendStringInfoString(&buf, ", "); |
| simple_quote_literal(&buf, p); |
| /* advance p to next string embedded in tgargs */ |
| while (*p) |
| p++; |
| p++; |
| } |
| } |
| |
| /* We deliberately do not put semi-colon at end */ |
| appendStringInfoChar(&buf, ')'); |
| |
| /* Clean up */ |
| systable_endscan(tgscan); |
| |
| table_close(tgrel, AccessShareLock); |
| |
| return buf.data; |
| } |
| |
| /* ---------- |
| * pg_get_indexdef - Get the definition of an index |
| * |
| * In the extended version, there is a colno argument as well as pretty bool. |
| * if colno == 0, we want a complete index definition. |
| * if colno > 0, we only want the Nth index key's variable or expression. |
| * |
| * Note that the SQL-function versions of this omit any info about the |
| * index tablespace; this is intentional because pg_dump wants it that way. |
| * However pg_get_indexdef_string() includes the index tablespace. |
| * ---------- |
| */ |
| Datum |
| pg_get_indexdef(PG_FUNCTION_ARGS) |
| { |
| Oid indexrelid = PG_GETARG_OID(0); |
| int prettyFlags; |
| char *res; |
| |
| prettyFlags = PRETTYFLAG_INDENT; |
| |
| res = pg_get_indexdef_worker(indexrelid, 0, NULL, |
| false, false, |
| false, false, |
| prettyFlags, true); |
| |
| if (res == NULL) |
| PG_RETURN_NULL(); |
| |
| PG_RETURN_TEXT_P(string_to_text(res)); |
| } |
| |
| Datum |
| pg_get_indexdef_ext(PG_FUNCTION_ARGS) |
| { |
| Oid indexrelid = PG_GETARG_OID(0); |
| int32 colno = PG_GETARG_INT32(1); |
| bool pretty = PG_GETARG_BOOL(2); |
| int prettyFlags; |
| char *res; |
| |
| prettyFlags = pretty ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT | PRETTYFLAG_SCHEMA) : PRETTYFLAG_INDENT; |
| |
| res = pg_get_indexdef_worker(indexrelid, colno, NULL, |
| colno != 0, false, |
| false, false, |
| prettyFlags, true); |
| |
| if (res == NULL) |
| PG_RETURN_NULL(); |
| |
| PG_RETURN_TEXT_P(string_to_text(res)); |
| } |
| |
| /* |
| * Internal version for use by ALTER TABLE. |
| * Includes a tablespace clause in the result. |
| * Returns a palloc'd C string; no pretty-printing. |
| */ |
| char * |
| pg_get_indexdef_string(Oid indexrelid) |
| { |
| return pg_get_indexdef_worker(indexrelid, 0, NULL, |
| false, false, |
| true, true, |
| 0, false); |
| } |
| |
| /* Internal version that just reports the key-column definitions */ |
| char * |
| pg_get_indexdef_columns(Oid indexrelid, bool pretty) |
| { |
| int prettyFlags; |
| |
| prettyFlags = pretty ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT | PRETTYFLAG_SCHEMA) : PRETTYFLAG_INDENT; |
| |
| return pg_get_indexdef_worker(indexrelid, 0, NULL, |
| true, true, |
| false, false, |
| prettyFlags, false); |
| } |
| |
| /* |
| * Internal workhorse to decompile an index definition. |
| * |
| * This is now used for exclusion constraints as well: if excludeOps is not |
| * NULL then it points to an array of exclusion operator OIDs. |
| */ |
| static char * |
| pg_get_indexdef_worker(Oid indexrelid, int colno, |
| const Oid *excludeOps, |
| bool attrsOnly, bool keysOnly, |
| bool showTblSpc, bool inherits, |
| int prettyFlags, bool missing_ok) |
| { |
| /* might want a separate isConstraint parameter later */ |
| bool isConstraint = (excludeOps != NULL); |
| HeapTuple ht_idx; |
| HeapTuple ht_idxrel; |
| HeapTuple ht_am; |
| Form_pg_index idxrec; |
| Form_pg_class idxrelrec; |
| Form_pg_am amrec; |
| IndexAmRoutine *amroutine; |
| List *indexprs; |
| ListCell *indexpr_item; |
| List *context; |
| Oid indrelid; |
| int keyno; |
| Datum indcollDatum; |
| Datum indclassDatum; |
| Datum indoptionDatum; |
| bool isnull; |
| oidvector *indcollation; |
| oidvector *indclass; |
| int2vector *indoption; |
| StringInfoData buf; |
| char *str; |
| char *sep; |
| |
| /* |
| * Fetch the pg_index tuple by the Oid of the index |
| */ |
| ht_idx = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexrelid)); |
| if (!HeapTupleIsValid(ht_idx)) |
| { |
| if (missing_ok) |
| return NULL; |
| elog(ERROR, "cache lookup failed for index %u", indexrelid); |
| } |
| idxrec = (Form_pg_index) GETSTRUCT(ht_idx); |
| |
| indrelid = idxrec->indrelid; |
| Assert(indexrelid == idxrec->indexrelid); |
| |
| /* Must get indcollation, indclass, and indoption the hard way */ |
| indcollDatum = SysCacheGetAttr(INDEXRELID, ht_idx, |
| Anum_pg_index_indcollation, &isnull); |
| Assert(!isnull); |
| indcollation = (oidvector *) DatumGetPointer(indcollDatum); |
| |
| indclassDatum = SysCacheGetAttr(INDEXRELID, ht_idx, |
| Anum_pg_index_indclass, &isnull); |
| Assert(!isnull); |
| indclass = (oidvector *) DatumGetPointer(indclassDatum); |
| |
| indoptionDatum = SysCacheGetAttr(INDEXRELID, ht_idx, |
| Anum_pg_index_indoption, &isnull); |
| Assert(!isnull); |
| indoption = (int2vector *) DatumGetPointer(indoptionDatum); |
| |
| /* |
| * Fetch the pg_class tuple of the index relation |
| */ |
| ht_idxrel = SearchSysCache1(RELOID, ObjectIdGetDatum(indexrelid)); |
| if (!HeapTupleIsValid(ht_idxrel)) |
| elog(ERROR, "cache lookup failed for relation %u", indexrelid); |
| idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel); |
| |
| /* |
| * Fetch the pg_am tuple of the index' access method |
| */ |
| ht_am = SearchSysCache1(AMOID, ObjectIdGetDatum(idxrelrec->relam)); |
| if (!HeapTupleIsValid(ht_am)) |
| elog(ERROR, "cache lookup failed for access method %u", |
| idxrelrec->relam); |
| amrec = (Form_pg_am) GETSTRUCT(ht_am); |
| |
| /* Fetch the index AM's API struct */ |
| amroutine = GetIndexAmRoutine(amrec->amhandler); |
| |
| /* |
| * Get the index expressions, if any. (NOTE: we do not use the relcache |
| * versions of the expressions and predicate, because we want to display |
| * non-const-folded expressions.) |
| */ |
| if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs, NULL)) |
| { |
| Datum exprsDatum; |
| bool isnull; |
| char *exprsString; |
| |
| exprsDatum = SysCacheGetAttr(INDEXRELID, ht_idx, |
| Anum_pg_index_indexprs, &isnull); |
| Assert(!isnull); |
| exprsString = TextDatumGetCString(exprsDatum); |
| indexprs = (List *) stringToNode(exprsString); |
| pfree(exprsString); |
| } |
| else |
| indexprs = NIL; |
| |
| indexpr_item = list_head(indexprs); |
| |
| context = deparse_context_for(get_relation_name(indrelid), indrelid); |
| |
| /* |
| * Start the index definition. Note that the index's name should never be |
| * schema-qualified, but the indexed rel's name may be. |
| */ |
| initStringInfo(&buf); |
| |
| if (!attrsOnly) |
| { |
| if (!isConstraint) |
| appendStringInfo(&buf, "CREATE %sINDEX %s ON %s%s USING %s (", |
| idxrec->indisunique ? "UNIQUE " : "", |
| quote_identifier(NameStr(idxrelrec->relname)), |
| idxrelrec->relkind == RELKIND_PARTITIONED_INDEX |
| && !inherits ? "ONLY " : "", |
| (prettyFlags & PRETTYFLAG_SCHEMA) ? |
| generate_relation_name(indrelid, NIL) : |
| generate_qualified_relation_name(indrelid), |
| quote_identifier(NameStr(amrec->amname))); |
| else /* currently, must be EXCLUDE constraint */ |
| appendStringInfo(&buf, "EXCLUDE USING %s (", |
| quote_identifier(NameStr(amrec->amname))); |
| } |
| |
| /* |
| * Report the indexed attributes |
| */ |
| sep = ""; |
| for (keyno = 0; keyno < idxrec->indnatts; keyno++) |
| { |
| AttrNumber attnum = idxrec->indkey.values[keyno]; |
| Oid keycoltype; |
| Oid keycolcollation; |
| |
| /* |
| * Ignore non-key attributes if told to. |
| */ |
| if (keysOnly && keyno >= idxrec->indnkeyatts) |
| break; |
| |
| /* Otherwise, print INCLUDE to divide key and non-key attrs. */ |
| if (!colno && keyno == idxrec->indnkeyatts) |
| { |
| appendStringInfoString(&buf, ") INCLUDE ("); |
| sep = ""; |
| } |
| |
| if (!colno) |
| appendStringInfoString(&buf, sep); |
| sep = ", "; |
| |
| if (attnum != 0) |
| { |
| /* Simple index column */ |
| char *attname; |
| int32 keycoltypmod; |
| |
| attname = get_attname(indrelid, attnum, false); |
| if (!colno || colno == keyno + 1) |
| appendStringInfoString(&buf, quote_identifier(attname)); |
| get_atttypetypmodcoll(indrelid, attnum, |
| &keycoltype, &keycoltypmod, |
| &keycolcollation); |
| } |
| else |
| { |
| /* expressional index */ |
| Node *indexkey; |
| |
| if (indexpr_item == NULL) |
| elog(ERROR, "too few entries in indexprs list"); |
| indexkey = (Node *) lfirst(indexpr_item); |
| indexpr_item = lnext(indexprs, indexpr_item); |
| /* Deparse */ |
| str = deparse_expression_pretty(indexkey, context, false, false, |
| prettyFlags, 0); |
| if (!colno || colno == keyno + 1) |
| { |
| /* Need parens if it's not a bare function call */ |
| if (looks_like_function(indexkey)) |
| appendStringInfoString(&buf, str); |
| else |
| appendStringInfo(&buf, "(%s)", str); |
| } |
| keycoltype = exprType(indexkey); |
| keycolcollation = exprCollation(indexkey); |
| } |
| |
| /* Print additional decoration for (selected) key columns */ |
| if (!attrsOnly && keyno < idxrec->indnkeyatts && |
| (!colno || colno == keyno + 1)) |
| { |
| int16 opt = indoption->values[keyno]; |
| Oid indcoll = indcollation->values[keyno]; |
| Datum attoptions = get_attoptions(indexrelid, keyno + 1); |
| bool has_options = attoptions != (Datum) 0; |
| |
| /* Add collation, if not default for column */ |
| if (OidIsValid(indcoll) && indcoll != keycolcollation) |
| appendStringInfo(&buf, " COLLATE %s", |
| generate_collation_name((indcoll))); |
| |
| /* Add the operator class name, if not default */ |
| get_opclass_name(indclass->values[keyno], |
| has_options ? InvalidOid : keycoltype, &buf); |
| |
| if (has_options) |
| { |
| appendStringInfoString(&buf, " ("); |
| get_reloptions(&buf, attoptions); |
| appendStringInfoChar(&buf, ')'); |
| } |
| |
| /* Add options if relevant */ |
| if (amroutine->amcanorder) |
| { |
| /* if it supports sort ordering, report DESC and NULLS opts */ |
| if (opt & INDOPTION_DESC) |
| { |
| appendStringInfoString(&buf, " DESC"); |
| /* NULLS FIRST is the default in this case */ |
| if (!(opt & INDOPTION_NULLS_FIRST)) |
| appendStringInfoString(&buf, " NULLS LAST"); |
| } |
| else |
| { |
| if (opt & INDOPTION_NULLS_FIRST) |
| appendStringInfoString(&buf, " NULLS FIRST"); |
| } |
| } |
| |
| /* Add the exclusion operator if relevant */ |
| if (excludeOps != NULL) |
| appendStringInfo(&buf, " WITH %s", |
| generate_operator_name(excludeOps[keyno], |
| keycoltype, |
| keycoltype)); |
| } |
| } |
| |
| if (!attrsOnly) |
| { |
| appendStringInfoChar(&buf, ')'); |
| |
| /* |
| * If it has options, append "WITH (options)" |
| */ |
| str = flatten_reloptions(indexrelid); |
| if (str) |
| { |
| appendStringInfo(&buf, " WITH (%s)", str); |
| pfree(str); |
| } |
| |
| /* |
| * Print tablespace, but only if requested |
| */ |
| if (showTblSpc) |
| { |
| Oid tblspc; |
| |
| tblspc = get_rel_tablespace(indexrelid); |
| if (OidIsValid(tblspc)) |
| { |
| if (isConstraint) |
| appendStringInfoString(&buf, " USING INDEX"); |
| appendStringInfo(&buf, " TABLESPACE %s", |
| quote_identifier(get_tablespace_name(tblspc))); |
| } |
| } |
| |
| /* |
| * If it's a partial index, decompile and append the predicate |
| */ |
| if (!heap_attisnull(ht_idx, Anum_pg_index_indpred, NULL)) |
| { |
| Node *node; |
| Datum predDatum; |
| bool isnull; |
| char *predString; |
| |
| /* Convert text string to node tree */ |
| predDatum = SysCacheGetAttr(INDEXRELID, ht_idx, |
| Anum_pg_index_indpred, &isnull); |
| Assert(!isnull); |
| predString = TextDatumGetCString(predDatum); |
| node = (Node *) stringToNode(predString); |
| pfree(predString); |
| |
| /* Deparse */ |
| str = deparse_expression_pretty(node, context, false, false, |
| prettyFlags, 0); |
| if (isConstraint) |
| appendStringInfo(&buf, " WHERE (%s)", str); |
| else |
| appendStringInfo(&buf, " WHERE %s", str); |
| } |
| } |
| |
| /* Clean up */ |
| ReleaseSysCache(ht_idx); |
| ReleaseSysCache(ht_idxrel); |
| ReleaseSysCache(ht_am); |
| |
| return buf.data; |
| } |
| |
| /* |
| * pg_get_statisticsobjdef |
| * Get the definition of an extended statistics object |
| */ |
| Datum |
| pg_get_statisticsobjdef(PG_FUNCTION_ARGS) |
| { |
| Oid statextid = PG_GETARG_OID(0); |
| char *res; |
| |
| res = pg_get_statisticsobj_worker(statextid, false, true); |
| |
| if (res == NULL) |
| PG_RETURN_NULL(); |
| |
| PG_RETURN_TEXT_P(string_to_text(res)); |
| } |
| |
| /* |
| * Internal version for use by ALTER TABLE. |
| * Includes a tablespace clause in the result. |
| * Returns a palloc'd C string; no pretty-printing. |
| */ |
| char * |
| pg_get_statisticsobjdef_string(Oid statextid) |
| { |
| return pg_get_statisticsobj_worker(statextid, false, false); |
| } |
| |
| /* |
| * pg_get_statisticsobjdef_columns |
| * Get columns and expressions for an extended statistics object |
| */ |
| Datum |
| pg_get_statisticsobjdef_columns(PG_FUNCTION_ARGS) |
| { |
| Oid statextid = PG_GETARG_OID(0); |
| char *res; |
| |
| res = pg_get_statisticsobj_worker(statextid, true, true); |
| |
| if (res == NULL) |
| PG_RETURN_NULL(); |
| |
| PG_RETURN_TEXT_P(string_to_text(res)); |
| } |
| |
| /* |
| * Internal workhorse to decompile an extended statistics object. |
| */ |
| static char * |
| pg_get_statisticsobj_worker(Oid statextid, bool columns_only, bool missing_ok) |
| { |
| Form_pg_statistic_ext statextrec; |
| HeapTuple statexttup; |
| StringInfoData buf; |
| int colno; |
| char *nsp; |
| ArrayType *arr; |
| char *enabled; |
| Datum datum; |
| bool isnull; |
| bool ndistinct_enabled; |
| bool dependencies_enabled; |
| bool mcv_enabled; |
| int i; |
| List *context; |
| ListCell *lc; |
| List *exprs = NIL; |
| bool has_exprs; |
| int ncolumns; |
| |
| statexttup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statextid)); |
| |
| if (!HeapTupleIsValid(statexttup)) |
| { |
| if (missing_ok) |
| return NULL; |
| elog(ERROR, "cache lookup failed for statistics object %u", statextid); |
| } |
| |
| /* has the statistics expressions? */ |
| has_exprs = !heap_attisnull(statexttup, Anum_pg_statistic_ext_stxexprs, NULL); |
| |
| statextrec = (Form_pg_statistic_ext) GETSTRUCT(statexttup); |
| |
| /* |
| * Get the statistics expressions, if any. (NOTE: we do not use the |
| * relcache versions of the expressions, because we want to display |
| * non-const-folded expressions.) |
| */ |
| if (has_exprs) |
| { |
| Datum exprsDatum; |
| bool isnull; |
| char *exprsString; |
| |
| exprsDatum = SysCacheGetAttr(STATEXTOID, statexttup, |
| Anum_pg_statistic_ext_stxexprs, &isnull); |
| Assert(!isnull); |
| exprsString = TextDatumGetCString(exprsDatum); |
| exprs = (List *) stringToNode(exprsString); |
| pfree(exprsString); |
| } |
| else |
| exprs = NIL; |
| |
| /* count the number of columns (attributes and expressions) */ |
| ncolumns = statextrec->stxkeys.dim1 + list_length(exprs); |
| |
| initStringInfo(&buf); |
| |
| if (!columns_only) |
| { |
| nsp = get_namespace_name(statextrec->stxnamespace); |
| appendStringInfo(&buf, "CREATE STATISTICS %s", |
| quote_qualified_identifier(nsp, |
| NameStr(statextrec->stxname))); |
| |
| /* |
| * Decode the stxkind column so that we know which stats types to |
| * print. |
| */ |
| datum = SysCacheGetAttr(STATEXTOID, statexttup, |
| Anum_pg_statistic_ext_stxkind, &isnull); |
| Assert(!isnull); |
| arr = DatumGetArrayTypeP(datum); |
| if (ARR_NDIM(arr) != 1 || |
| ARR_HASNULL(arr) || |
| ARR_ELEMTYPE(arr) != CHAROID) |
| elog(ERROR, "stxkind is not a 1-D char array"); |
| enabled = (char *) ARR_DATA_PTR(arr); |
| |
| ndistinct_enabled = false; |
| dependencies_enabled = false; |
| mcv_enabled = false; |
| |
| for (i = 0; i < ARR_DIMS(arr)[0]; i++) |
| { |
| if (enabled[i] == STATS_EXT_NDISTINCT) |
| ndistinct_enabled = true; |
| else if (enabled[i] == STATS_EXT_DEPENDENCIES) |
| dependencies_enabled = true; |
| else if (enabled[i] == STATS_EXT_MCV) |
| mcv_enabled = true; |
| |
| /* ignore STATS_EXT_EXPRESSIONS (it's built automatically) */ |
| } |
| |
| /* |
| * If any option is disabled, then we'll need to append the types |
| * clause to show which options are enabled. We omit the types clause |
| * on purpose when all options are enabled, so a pg_dump/pg_restore |
| * will create all statistics types on a newer postgres version, if |
| * the statistics had all options enabled on the original version. |
| * |
| * But if the statistics is defined on just a single column, it has to |
| * be an expression statistics. In that case we don't need to specify |
| * kinds. |
| */ |
| if ((!ndistinct_enabled || !dependencies_enabled || !mcv_enabled) && |
| (ncolumns > 1)) |
| { |
| bool gotone = false; |
| |
| appendStringInfoString(&buf, " ("); |
| |
| if (ndistinct_enabled) |
| { |
| appendStringInfoString(&buf, "ndistinct"); |
| gotone = true; |
| } |
| |
| if (dependencies_enabled) |
| { |
| appendStringInfo(&buf, "%sdependencies", gotone ? ", " : ""); |
| gotone = true; |
| } |
| |
| if (mcv_enabled) |
| appendStringInfo(&buf, "%smcv", gotone ? ", " : ""); |
| |
| appendStringInfoChar(&buf, ')'); |
| } |
| |
| appendStringInfoString(&buf, " ON "); |
| } |
| |
| /* decode simple column references */ |
| for (colno = 0; colno < statextrec->stxkeys.dim1; colno++) |
| { |
| AttrNumber attnum = statextrec->stxkeys.values[colno]; |
| char *attname; |
| |
| if (colno > 0) |
| appendStringInfoString(&buf, ", "); |
| |
| attname = get_attname(statextrec->stxrelid, attnum, false); |
| |
| appendStringInfoString(&buf, quote_identifier(attname)); |
| } |
| |
| context = deparse_context_for(get_relation_name(statextrec->stxrelid), |
| statextrec->stxrelid); |
| |
| foreach(lc, exprs) |
| { |
| Node *expr = (Node *) lfirst(lc); |
| char *str; |
| int prettyFlags = PRETTYFLAG_PAREN; |
| |
| str = deparse_expression_pretty(expr, context, false, false, |
| prettyFlags, 0); |
| |
| if (colno > 0) |
| appendStringInfoString(&buf, ", "); |
| |
| /* Need parens if it's not a bare function call */ |
| if (looks_like_function(expr)) |
| appendStringInfoString(&buf, str); |
| else |
| appendStringInfo(&buf, "(%s)", str); |
| |
| colno++; |
| } |
| |
| if (!columns_only) |
| appendStringInfo(&buf, " FROM %s", |
| generate_relation_name(statextrec->stxrelid, NIL)); |
| |
| ReleaseSysCache(statexttup); |
| |
| return buf.data; |
| } |
| |
| /* |
| * Generate text array of expressions for statistics object. |
| */ |
| Datum |
| pg_get_statisticsobjdef_expressions(PG_FUNCTION_ARGS) |
| { |
| Oid statextid = PG_GETARG_OID(0); |
| Form_pg_statistic_ext statextrec; |
| HeapTuple statexttup; |
| Datum datum; |
| bool isnull; |
| List *context; |
| ListCell *lc; |
| List *exprs = NIL; |
| bool has_exprs; |
| char *tmp; |
| ArrayBuildState *astate = NULL; |
| |
| statexttup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statextid)); |
| |
| if (!HeapTupleIsValid(statexttup)) |
| PG_RETURN_NULL(); |
| |
| /* Does the stats object have expressions? */ |
| has_exprs = !heap_attisnull(statexttup, Anum_pg_statistic_ext_stxexprs, NULL); |
| |
| /* no expressions? we're done */ |
| if (!has_exprs) |
| { |
| ReleaseSysCache(statexttup); |
| PG_RETURN_NULL(); |
| } |
| |
| statextrec = (Form_pg_statistic_ext) GETSTRUCT(statexttup); |
| |
| /* |
| * Get the statistics expressions, and deparse them into text values. |
| */ |
| datum = SysCacheGetAttr(STATEXTOID, statexttup, |
| Anum_pg_statistic_ext_stxexprs, &isnull); |
| |
| Assert(!isnull); |
| tmp = TextDatumGetCString(datum); |
| exprs = (List *) stringToNode(tmp); |
| pfree(tmp); |
| |
| context = deparse_context_for(get_relation_name(statextrec->stxrelid), |
| statextrec->stxrelid); |
| |
| foreach(lc, exprs) |
| { |
| Node *expr = (Node *) lfirst(lc); |
| char *str; |
| int prettyFlags = PRETTYFLAG_INDENT; |
| |
| str = deparse_expression_pretty(expr, context, false, false, |
| prettyFlags, 0); |
| |
| astate = accumArrayResult(astate, |
| PointerGetDatum(cstring_to_text(str)), |
| false, |
| TEXTOID, |
| CurrentMemoryContext); |
| } |
| |
| ReleaseSysCache(statexttup); |
| |
| PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext)); |
| } |
| |
| /* |
| * pg_get_partkeydef |
| * |
| * Returns the partition key specification, ie, the following: |
| * |
| * PARTITION BY { RANGE | LIST | HASH } (column opt_collation opt_opclass [, ...]) |
| */ |
| Datum |
| pg_get_partkeydef(PG_FUNCTION_ARGS) |
| { |
| Oid relid = PG_GETARG_OID(0); |
| char *res; |
| |
| res = pg_get_partkeydef_worker(relid, PRETTYFLAG_INDENT, false, true); |
| |
| if (res == NULL) |
| PG_RETURN_NULL(); |
| |
| PG_RETURN_TEXT_P(string_to_text(res)); |
| } |
| |
| /* Internal version that just reports the column definitions */ |
| char * |
| pg_get_partkeydef_columns(Oid relid, bool pretty) |
| { |
| int prettyFlags; |
| |
| prettyFlags = pretty ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT | PRETTYFLAG_SCHEMA) : PRETTYFLAG_INDENT; |
| |
| return pg_get_partkeydef_worker(relid, prettyFlags, true, false); |
| } |
| |
| /* |
| * Internal workhorse to decompile a partition key definition. |
| */ |
| static char * |
| pg_get_partkeydef_worker(Oid relid, int prettyFlags, |
| bool attrsOnly, bool missing_ok) |
| { |
| Form_pg_partitioned_table form; |
| HeapTuple tuple; |
| oidvector *partclass; |
| oidvector *partcollation; |
| List *partexprs; |
| ListCell *partexpr_item; |
| List *context; |
| Datum datum; |
| bool isnull; |
| StringInfoData buf; |
| int keyno; |
| char *str; |
| char *sep; |
| |
| tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid)); |
| if (!HeapTupleIsValid(tuple)) |
| { |
| if (missing_ok) |
| return NULL; |
| elog(ERROR, "cache lookup failed for partition key of %u", relid); |
| } |
| |
| form = (Form_pg_partitioned_table) GETSTRUCT(tuple); |
| |
| Assert(form->partrelid == relid); |
| |
| /* Must get partclass and partcollation the hard way */ |
| datum = SysCacheGetAttr(PARTRELID, tuple, |
| Anum_pg_partitioned_table_partclass, &isnull); |
| Assert(!isnull); |
| partclass = (oidvector *) DatumGetPointer(datum); |
| |
| datum = SysCacheGetAttr(PARTRELID, tuple, |
| Anum_pg_partitioned_table_partcollation, &isnull); |
| Assert(!isnull); |
| partcollation = (oidvector *) DatumGetPointer(datum); |
| |
| |
| /* |
| * Get the expressions, if any. (NOTE: we do not use the relcache |
| * versions of the expressions, because we want to display |
| * non-const-folded expressions.) |
| */ |
| if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs, NULL)) |
| { |
| Datum exprsDatum; |
| bool isnull; |
| char *exprsString; |
| |
| exprsDatum = SysCacheGetAttr(PARTRELID, tuple, |
| Anum_pg_partitioned_table_partexprs, &isnull); |
| Assert(!isnull); |
| exprsString = TextDatumGetCString(exprsDatum); |
| partexprs = (List *) stringToNode(exprsString); |
| |
| if (!IsA(partexprs, List)) |
| elog(ERROR, "unexpected node type found in partexprs: %d", |
| (int) nodeTag(partexprs)); |
| |
| pfree(exprsString); |
| } |
| else |
| partexprs = NIL; |
| |
| partexpr_item = list_head(partexprs); |
| context = deparse_context_for(get_relation_name(relid), relid); |
| |
| initStringInfo(&buf); |
| |
| switch (form->partstrat) |
| { |
| case PARTITION_STRATEGY_HASH: |
| if (!attrsOnly) |
| appendStringInfoString(&buf, "HASH"); |
| break; |
| case PARTITION_STRATEGY_LIST: |
| if (!attrsOnly) |
| appendStringInfoString(&buf, "LIST"); |
| break; |
| case PARTITION_STRATEGY_RANGE: |
| if (!attrsOnly) |
| appendStringInfoString(&buf, "RANGE"); |
| break; |
| default: |
| elog(ERROR, "unexpected partition strategy: %d", |
| (int) form->partstrat); |
| } |
| |
| if (!attrsOnly) |
| appendStringInfoString(&buf, " ("); |
| sep = ""; |
| for (keyno = 0; keyno < form->partnatts; keyno++) |
| { |
| AttrNumber attnum = form->partattrs.values[keyno]; |
| Oid keycoltype; |
| Oid keycolcollation; |
| Oid partcoll; |
| |
| appendStringInfoString(&buf, sep); |
| sep = ", "; |
| if (attnum != 0) |
| { |
| /* Simple attribute reference */ |
| char *attname; |
| int32 keycoltypmod; |
| |
| attname = get_attname(relid, attnum, false); |
| appendStringInfoString(&buf, quote_identifier(attname)); |
| get_atttypetypmodcoll(relid, attnum, |
| &keycoltype, &keycoltypmod, |
| &keycolcollation); |
| } |
| else |
| { |
| /* Expression */ |
| Node *partkey; |
| |
| if (partexpr_item == NULL) |
| elog(ERROR, "too few entries in partexprs list"); |
| partkey = (Node *) lfirst(partexpr_item); |
| partexpr_item = lnext(partexprs, partexpr_item); |
| |
| /* Deparse */ |
| str = deparse_expression_pretty(partkey, context, false, false, |
| prettyFlags, 0); |
| /* Need parens if it's not a bare function call */ |
| if (looks_like_function(partkey)) |
| appendStringInfoString(&buf, str); |
| else |
| appendStringInfo(&buf, "(%s)", str); |
| |
| keycoltype = exprType(partkey); |
| keycolcollation = exprCollation(partkey); |
| } |
| |
| /* Add collation, if not default for column */ |
| partcoll = partcollation->values[keyno]; |
| if (!attrsOnly && OidIsValid(partcoll) && partcoll != keycolcollation) |
| appendStringInfo(&buf, " COLLATE %s", |
| generate_collation_name((partcoll))); |
| |
| /* Add the operator class name, if not default */ |
| if (!attrsOnly) |
| get_opclass_name(partclass->values[keyno], keycoltype, &buf); |
| } |
| |
| if (!attrsOnly) |
| appendStringInfoChar(&buf, ')'); |
| |
| /* Clean up */ |
| ReleaseSysCache(tuple); |
| |
| return buf.data; |
| } |
| |
| /* |
| * pg_get_partition_constraintdef |
| * |
| * Returns partition constraint expression as a string for the input relation |
| */ |
| Datum |
| pg_get_partition_constraintdef(PG_FUNCTION_ARGS) |
| { |
| Oid relationId = PG_GETARG_OID(0); |
| Expr *constr_expr; |
| int prettyFlags; |
| List *context; |
| char *consrc; |
| |
| constr_expr = get_partition_qual_relid(relationId); |
| |
| /* Quick exit if no partition constraint */ |
| if (constr_expr == NULL) |
| PG_RETURN_NULL(); |
| |
| /* |
| * Deparse and return the constraint expression. |
| */ |
| prettyFlags = PRETTYFLAG_INDENT; |
| context = deparse_context_for(get_relation_name(relationId), relationId); |
| consrc = deparse_expression_pretty((Node *) constr_expr, context, false, |
| false, prettyFlags, 0); |
| |
| PG_RETURN_TEXT_P(string_to_text(consrc)); |
| } |
| |
| /* |
| * pg_get_partconstrdef_string |
| * |
| * Returns the partition constraint as a C-string for the input relation, with |
| * the given alias. No pretty-printing. |
| */ |
| char * |
| pg_get_partconstrdef_string(Oid partitionId, char *aliasname) |
| { |
| Expr *constr_expr; |
| List *context; |
| |
| constr_expr = get_partition_qual_relid(partitionId); |
| context = deparse_context_for(aliasname, partitionId); |
| |
| return deparse_expression((Node *) constr_expr, context, true, false); |
| } |
| |
| /* |
| * pg_get_constraintdef |
| * |
| * Returns the definition for the constraint, ie, everything that needs to |
| * appear after "ALTER TABLE ... ADD CONSTRAINT <constraintname>". |
| */ |
| Datum |
| pg_get_constraintdef(PG_FUNCTION_ARGS) |
| { |
| Oid constraintId = PG_GETARG_OID(0); |
| int prettyFlags; |
| char *res; |
| |
| prettyFlags = PRETTYFLAG_INDENT; |
| |
| res = pg_get_constraintdef_worker(constraintId, false, prettyFlags, true); |
| |
| if (res == NULL) |
| PG_RETURN_NULL(); |
| |
| PG_RETURN_TEXT_P(string_to_text(res)); |
| } |
| |
| Datum |
| pg_get_constraintdef_ext(PG_FUNCTION_ARGS) |
| { |
| Oid constraintId = PG_GETARG_OID(0); |
| bool pretty = PG_GETARG_BOOL(1); |
| int prettyFlags; |
| char *res; |
| |
| prettyFlags = pretty ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT | PRETTYFLAG_SCHEMA) : PRETTYFLAG_INDENT; |
| |
| res = pg_get_constraintdef_worker(constraintId, false, prettyFlags, true); |
| |
| if (res == NULL) |
| PG_RETURN_NULL(); |
| |
| PG_RETURN_TEXT_P(string_to_text(res)); |
| } |
| |
| /* |
| * Internal version that returns a full ALTER TABLE ... ADD CONSTRAINT command |
| */ |
| char * |
| pg_get_constraintdef_command(Oid constraintId) |
| { |
| return pg_get_constraintdef_worker(constraintId, true, 0, false); |
| } |
| |
| /* Internal version that returns a palloc'd C string */ |
| char * |
| pg_get_constraintexpr_string(Oid constraintId) |
| { |
| return pg_get_constraintdef_worker(constraintId, false, 0, false); |
| } |
| |
| /* |
| * As of 9.4, we now use an MVCC snapshot for this. |
| */ |
| static char * |
| pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, |
| int prettyFlags, bool missing_ok) |
| { |
| HeapTuple tup; |
| Form_pg_constraint conForm; |
| StringInfoData buf; |
| SysScanDesc scandesc; |
| ScanKeyData scankey[1]; |
| Snapshot snapshot = RegisterSnapshot(GetTransactionSnapshot()); |
| Relation relation = table_open(ConstraintRelationId, AccessShareLock); |
| |
| ScanKeyInit(&scankey[0], |
| Anum_pg_constraint_oid, |
| BTEqualStrategyNumber, F_OIDEQ, |
| ObjectIdGetDatum(constraintId)); |
| |
| scandesc = systable_beginscan(relation, |
| ConstraintOidIndexId, |
| true, |
| snapshot, |
| 1, |
| scankey); |
| |
| /* |
| * We later use the tuple with SysCacheGetAttr() as if we had obtained it |
| * via SearchSysCache, which works fine. |
| */ |
| tup = systable_getnext(scandesc); |
| |
| UnregisterSnapshot(snapshot); |
| |
| if (!HeapTupleIsValid(tup)) |
| { |
| if (missing_ok) |
| { |
| systable_endscan(scandesc); |
| table_close(relation, AccessShareLock); |
| return NULL; |
| } |
| elog(ERROR, "could not find tuple for constraint %u", constraintId); |
| } |
| |
| conForm = (Form_pg_constraint) GETSTRUCT(tup); |
| |
| initStringInfo(&buf); |
| |
| if (fullCommand) |
| { |
| if (OidIsValid(conForm->conrelid)) |
| { |
| /* |
| * Currently, callers want ALTER TABLE (without ONLY) for CHECK |
| * constraints, and other types of constraints don't inherit |
| * anyway so it doesn't matter whether we say ONLY or not. Someday |
| * we might need to let callers specify whether to put ONLY in the |
| * command. |
| */ |
| appendStringInfo(&buf, "ALTER TABLE %s ADD CONSTRAINT %s ", |
| generate_qualified_relation_name(conForm->conrelid), |
| quote_identifier(NameStr(conForm->conname))); |
| } |
| else |
| { |
| /* Must be a domain constraint */ |
| Assert(OidIsValid(conForm->contypid)); |
| appendStringInfo(&buf, "ALTER DOMAIN %s ADD CONSTRAINT %s ", |
| generate_qualified_type_name(conForm->contypid), |
| quote_identifier(NameStr(conForm->conname))); |
| } |
| } |
| |
| switch (conForm->contype) |
| { |
| case CONSTRAINT_FOREIGN: |
| { |
| Datum val; |
| bool isnull; |
| const char *string; |
| |
| /* Start off the constraint definition */ |
| appendStringInfoString(&buf, "FOREIGN KEY ("); |
| |
| /* Fetch and build referencing-column list */ |
| val = SysCacheGetAttr(CONSTROID, tup, |
| Anum_pg_constraint_conkey, &isnull); |
| if (isnull) |
| elog(ERROR, "null conkey for constraint %u", |
| constraintId); |
| |
| decompile_column_index_array(val, conForm->conrelid, &buf); |
| |
| /* add foreign relation name */ |
| appendStringInfo(&buf, ") REFERENCES %s(", |
| generate_relation_name(conForm->confrelid, |
| NIL)); |
| |
| /* Fetch and build referenced-column list */ |
| val = SysCacheGetAttr(CONSTROID, tup, |
| Anum_pg_constraint_confkey, &isnull); |
| if (isnull) |
| elog(ERROR, "null confkey for constraint %u", |
| constraintId); |
| |
| decompile_column_index_array(val, conForm->confrelid, &buf); |
| |
| appendStringInfoChar(&buf, ')'); |
| |
| /* Add match type */ |
| switch (conForm->confmatchtype) |
| { |
| case FKCONSTR_MATCH_FULL: |
| string = " MATCH FULL"; |
| break; |
| case FKCONSTR_MATCH_PARTIAL: |
| string = " MATCH PARTIAL"; |
| break; |
| case FKCONSTR_MATCH_SIMPLE: |
| string = ""; |
| break; |
| default: |
| elog(ERROR, "unrecognized confmatchtype: %d", |
| conForm->confmatchtype); |
| string = ""; /* keep compiler quiet */ |
| break; |
| } |
| appendStringInfoString(&buf, string); |
| |
| /* Add ON UPDATE and ON DELETE clauses, if needed */ |
| switch (conForm->confupdtype) |
| { |
| case FKCONSTR_ACTION_NOACTION: |
| string = NULL; /* suppress default */ |
| break; |
| case FKCONSTR_ACTION_RESTRICT: |
| string = "RESTRICT"; |
| break; |
| case FKCONSTR_ACTION_CASCADE: |
| string = "CASCADE"; |
| break; |
| case FKCONSTR_ACTION_SETNULL: |
| string = "SET NULL"; |
| break; |
| case FKCONSTR_ACTION_SETDEFAULT: |
| string = "SET DEFAULT"; |
| break; |
| default: |
| elog(ERROR, "unrecognized confupdtype: %d", |
| conForm->confupdtype); |
| string = NULL; /* keep compiler quiet */ |
| break; |
| } |
| if (string) |
| appendStringInfo(&buf, " ON UPDATE %s", string); |
| |
| switch (conForm->confdeltype) |
| { |
| case FKCONSTR_ACTION_NOACTION: |
| string = NULL; /* suppress default */ |
| break; |
| case FKCONSTR_ACTION_RESTRICT: |
| string = "RESTRICT"; |
| break; |
| case FKCONSTR_ACTION_CASCADE: |
| string = "CASCADE"; |
| break; |
| case FKCONSTR_ACTION_SETNULL: |
| string = "SET NULL"; |
| break; |
| case FKCONSTR_ACTION_SETDEFAULT: |
| string = "SET DEFAULT"; |
| break; |
| default: |
| elog(ERROR, "unrecognized confdeltype: %d", |
| conForm->confdeltype); |
| string = NULL; /* keep compiler quiet */ |
| break; |
| } |
| if (string) |
| appendStringInfo(&buf, " ON DELETE %s", string); |
| |
| break; |
| } |
| case CONSTRAINT_PRIMARY: |
| case CONSTRAINT_UNIQUE: |
| { |
| Datum val; |
| bool isnull; |
| Oid indexId; |
| int keyatts; |
| HeapTuple indtup; |
| |
| /* Start off the constraint definition */ |
| if (conForm->contype == CONSTRAINT_PRIMARY) |
| appendStringInfoString(&buf, "PRIMARY KEY ("); |
| else |
| appendStringInfoString(&buf, "UNIQUE ("); |
| |
| /* Fetch and build target column list */ |
| val = SysCacheGetAttr(CONSTROID, tup, |
| Anum_pg_constraint_conkey, &isnull); |
| if (isnull) |
| elog(ERROR, "null conkey for constraint %u", |
| constraintId); |
| |
| keyatts = decompile_column_index_array(val, conForm->conrelid, &buf); |
| |
| appendStringInfoChar(&buf, ')'); |
| |
| indexId = conForm->conindid; |
| |
| /* Build including column list (from pg_index.indkeys) */ |
| indtup = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexId)); |
| if (!HeapTupleIsValid(indtup)) |
| elog(ERROR, "cache lookup failed for index %u", indexId); |
| val = SysCacheGetAttr(INDEXRELID, indtup, |
| Anum_pg_index_indnatts, &isnull); |
| if (isnull) |
| elog(ERROR, "null indnatts for index %u", indexId); |
| if (DatumGetInt32(val) > keyatts) |
| { |
| Datum cols; |
| Datum *keys; |
| int nKeys; |
| int j; |
| |
| appendStringInfoString(&buf, " INCLUDE ("); |
| |
| cols = SysCacheGetAttr(INDEXRELID, indtup, |
| Anum_pg_index_indkey, &isnull); |
| if (isnull) |
| elog(ERROR, "null indkey for index %u", indexId); |
| |
| deconstruct_array(DatumGetArrayTypeP(cols), |
| INT2OID, 2, true, TYPALIGN_SHORT, |
| &keys, NULL, &nKeys); |
| |
| for (j = keyatts; j < nKeys; j++) |
| { |
| char *colName; |
| |
| colName = get_attname(conForm->conrelid, |
| DatumGetInt16(keys[j]), false); |
| if (j > keyatts) |
| appendStringInfoString(&buf, ", "); |
| appendStringInfoString(&buf, quote_identifier(colName)); |
| } |
| |
| appendStringInfoChar(&buf, ')'); |
| } |
| ReleaseSysCache(indtup); |
| |
| /* XXX why do we only print these bits if fullCommand? */ |
| if (fullCommand && OidIsValid(indexId)) |
| { |
| char *options = flatten_reloptions(indexId); |
| Oid tblspc; |
| |
| if (options) |
| { |
| appendStringInfo(&buf, " WITH (%s)", options); |
| pfree(options); |
| } |
| |
| /* |
| * Print the tablespace, unless it's the database default. |
| * This is to help ALTER TABLE usage of this facility, |
| * which needs this behavior to recreate exact catalog |
| * state. |
| */ |
| tblspc = get_rel_tablespace(indexId); |
| if (OidIsValid(tblspc)) |
| appendStringInfo(&buf, " USING INDEX TABLESPACE %s", |
| quote_identifier(get_tablespace_name(tblspc))); |
| } |
| |
| break; |
| } |
| case CONSTRAINT_CHECK: |
| { |
| Datum val; |
| bool isnull; |
| char *conbin; |
| char *consrc; |
| Node *expr; |
| List *context; |
| |
| /* Fetch constraint expression in parsetree form */ |
| val = SysCacheGetAttr(CONSTROID, tup, |
| Anum_pg_constraint_conbin, &isnull); |
| if (isnull) |
| elog(ERROR, "null conbin for constraint %u", |
| constraintId); |
| |
| conbin = TextDatumGetCString(val); |
| expr = stringToNode(conbin); |
| |
| /* Set up deparsing context for Var nodes in constraint */ |
| if (conForm->conrelid != InvalidOid) |
| { |
| /* relation constraint */ |
| context = deparse_context_for(get_relation_name(conForm->conrelid), |
| conForm->conrelid); |
| } |
| else |
| { |
| /* domain constraint --- can't have Vars */ |
| context = NIL; |
| } |
| |
| consrc = deparse_expression_pretty(expr, context, false, false, |
| prettyFlags, 0); |
| |
| /* |
| * Now emit the constraint definition, adding NO INHERIT if |
| * necessary. |
| * |
| * There are cases where the constraint expression will be |
| * fully parenthesized and we don't need the outer parens ... |
| * but there are other cases where we do need 'em. Be |
| * conservative for now. |
| * |
| * Note that simply checking for leading '(' and trailing ')' |
| * would NOT be good enough, consider "(x > 0) AND (y > 0)". |
| */ |
| appendStringInfo(&buf, "CHECK (%s)%s", |
| consrc, |
| conForm->connoinherit ? " NO INHERIT" : ""); |
| break; |
| } |
| case CONSTRAINT_TRIGGER: |
| |
| /* |
| * There isn't an ALTER TABLE syntax for creating a user-defined |
| * constraint trigger, but it seems better to print something than |
| * throw an error; if we throw error then this function couldn't |
| * safely be applied to all rows of pg_constraint. |
| */ |
| appendStringInfoString(&buf, "TRIGGER"); |
| break; |
| case CONSTRAINT_EXCLUSION: |
| { |
| Oid indexOid = conForm->conindid; |
| Datum val; |
| bool isnull; |
| Datum *elems; |
| int nElems; |
| int i; |
| Oid *operators; |
| |
| /* Extract operator OIDs from the pg_constraint tuple */ |
| val = SysCacheGetAttr(CONSTROID, tup, |
| Anum_pg_constraint_conexclop, |
| &isnull); |
| if (isnull) |
| elog(ERROR, "null conexclop for constraint %u", |
| constraintId); |
| |
| deconstruct_array(DatumGetArrayTypeP(val), |
| OIDOID, sizeof(Oid), true, TYPALIGN_INT, |
| &elems, NULL, &nElems); |
| |
| operators = (Oid *) palloc(nElems * sizeof(Oid)); |
| for (i = 0; i < nElems; i++) |
| operators[i] = DatumGetObjectId(elems[i]); |
| |
| /* pg_get_indexdef_worker does the rest */ |
| /* suppress tablespace because pg_dump wants it that way */ |
| appendStringInfoString(&buf, |
| pg_get_indexdef_worker(indexOid, |
| 0, |
| operators, |
| false, |
| false, |
| false, |
| false, |
| prettyFlags, |
| false)); |
| break; |
| } |
| default: |
| elog(ERROR, "invalid constraint type \"%c\"", conForm->contype); |
| break; |
| } |
| |
| if (conForm->condeferrable) |
| appendStringInfoString(&buf, " DEFERRABLE"); |
| if (conForm->condeferred) |
| appendStringInfoString(&buf, " INITIALLY DEFERRED"); |
| if (!conForm->convalidated) |
| appendStringInfoString(&buf, " NOT VALID"); |
| |
| /* Cleanup */ |
| systable_endscan(scandesc); |
| table_close(relation, AccessShareLock); |
| |
| return buf.data; |
| } |
| |
| |
| /* |
| * Convert an int16[] Datum into a comma-separated list of column names |
| * for the indicated relation; append the list to buf. Returns the number |
| * of keys. |
| */ |
| static int |
| decompile_column_index_array(Datum column_index_array, Oid relId, |
| StringInfo buf) |
| { |
| Datum *keys; |
| int nKeys; |
| int j; |
| |
| /* Extract data from array of int16 */ |
| deconstruct_array(DatumGetArrayTypeP(column_index_array), |
| INT2OID, 2, true, TYPALIGN_SHORT, |
| &keys, NULL, &nKeys); |
| |
| for (j = 0; j < nKeys; j++) |
| { |
| char *colName; |
| |
| colName = get_attname(relId, DatumGetInt16(keys[j]), false); |
| |
| if (j == 0) |
| appendStringInfoString(buf, quote_identifier(colName)); |
| else |
| appendStringInfo(buf, ", %s", quote_identifier(colName)); |
| } |
| |
| return nKeys; |
| } |
| |
| |
| /* ---------- |
| * pg_get_expr - Decompile an expression tree |
| * |
| * Input: an expression tree in nodeToString form, and a relation OID |
| * |
| * Output: reverse-listed expression |
| * |
| * Currently, the expression can only refer to a single relation, namely |
| * the one specified by the second parameter. This is sufficient for |
| * partial indexes, column default expressions, etc. We also support |
| * Var-free expressions, for which the OID can be InvalidOid. |
| * ---------- |
| */ |
| Datum |
| pg_get_expr(PG_FUNCTION_ARGS) |
| { |
| text *expr = PG_GETARG_TEXT_PP(0); |
| Oid relid = PG_GETARG_OID(1); |
| int prettyFlags; |
| char *relname; |
| |
| prettyFlags = PRETTYFLAG_INDENT; |
| |
| if (OidIsValid(relid)) |
| { |
| /* Get the name for the relation */ |
| relname = get_rel_name(relid); |
| |
| /* |
| * If the OID isn't actually valid, don't throw an error, just return |
| * NULL. This is a bit questionable, but it's what we've done |
| * historically, and it can help avoid unwanted failures when |
| * examining catalog entries for just-deleted relations. |
| */ |
| if (relname == NULL) |
| PG_RETURN_NULL(); |
| } |
| else |
| relname = NULL; |
| |
| PG_RETURN_TEXT_P(pg_get_expr_worker(expr, relid, relname, prettyFlags)); |
| } |
| |
| Datum |
| pg_get_expr_ext(PG_FUNCTION_ARGS) |
| { |
| text *expr = PG_GETARG_TEXT_PP(0); |
| Oid relid = PG_GETARG_OID(1); |
| bool pretty = PG_GETARG_BOOL(2); |
| int prettyFlags; |
| char *relname; |
| |
| prettyFlags = pretty ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT | PRETTYFLAG_SCHEMA) : PRETTYFLAG_INDENT; |
| |
| if (OidIsValid(relid)) |
| { |
| /* Get the name for the relation */ |
| relname = get_rel_name(relid); |
| /* See notes above */ |
| if (relname == NULL) |
| PG_RETURN_NULL(); |
| } |
| else |
| relname = NULL; |
| |
| PG_RETURN_TEXT_P(pg_get_expr_worker(expr, relid, relname, prettyFlags)); |
| } |
| |
| static text * |
| pg_get_expr_worker(text *expr, Oid relid, const char *relname, int prettyFlags) |
| { |
| Node *node; |
| List *context; |
| char *exprstr; |
| char *str; |
| |
| /* Convert input TEXT object to C string */ |
| exprstr = text_to_cstring(expr); |
| |
| /* Convert expression to node tree */ |
| node = (Node *) stringToNode(exprstr); |
| |
| pfree(exprstr); |
| |
| /* Prepare deparse context if needed */ |
| if (OidIsValid(relid)) |
| context = deparse_context_for(relname, relid); |
| else |
| context = NIL; |
| |
| /* Deparse */ |
| str = deparse_expression_pretty(node, context, false, false, |
| prettyFlags, 0); |
| |
| return string_to_text(str); |
| } |
| |
| |
| /* ---------- |
| * pg_get_userbyid - Get a user name by roleid and |
| * fallback to 'unknown (OID=n)' |
| * ---------- |
| */ |
| Datum |
| pg_get_userbyid(PG_FUNCTION_ARGS) |
| { |
| Oid roleid = PG_GETARG_OID(0); |
| Name result; |
| HeapTuple roletup; |
| Form_pg_authid role_rec; |
| |
| /* |
| * Allocate space for the result |
| */ |
| result = (Name) palloc(NAMEDATALEN); |
| memset(NameStr(*result), 0, NAMEDATALEN); |
| |
| /* |
| * Get the pg_authid entry and print the result |
| */ |
| roletup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); |
| if (HeapTupleIsValid(roletup)) |
| { |
| role_rec = (Form_pg_authid) GETSTRUCT(roletup); |
| *result = role_rec->rolname; |
| ReleaseSysCache(roletup); |
| } |
| else |
| sprintf(NameStr(*result), "unknown (OID=%u)", roleid); |
| |
| PG_RETURN_NAME(result); |
| } |
| |
| |
| /* |
| * pg_get_serial_sequence |
| * Get the name of the sequence used by an identity or serial column, |
| * formatted suitably for passing to setval, nextval or currval. |
| * First parameter is not treated as double-quoted, second parameter |
| * is --- see documentation for reason. |
| */ |
| Datum |
| pg_get_serial_sequence(PG_FUNCTION_ARGS) |
| { |
| text *tablename = PG_GETARG_TEXT_PP(0); |
| text *columnname = PG_GETARG_TEXT_PP(1); |
| RangeVar *tablerv; |
| Oid tableOid; |
| char *column; |
| AttrNumber attnum; |
| Oid sequenceId = InvalidOid; |
| Relation depRel; |
| ScanKeyData key[3]; |
| SysScanDesc scan; |
| HeapTuple tup; |
| |
| /* Look up table name. Can't lock it - we might not have privileges. */ |
| tablerv = makeRangeVarFromNameList(textToQualifiedNameList(tablename)); |
| tableOid = RangeVarGetRelid(tablerv, NoLock, false); |
| |
| /* Get the number of the column */ |
| column = text_to_cstring(columnname); |
| |
| attnum = get_attnum(tableOid, column); |
| if (attnum == InvalidAttrNumber) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_COLUMN), |
| errmsg("column \"%s\" of relation \"%s\" does not exist", |
| column, tablerv->relname))); |
| |
| /* Search the dependency table for the dependent sequence */ |
| depRel = table_open(DependRelationId, AccessShareLock); |
| |
| ScanKeyInit(&key[0], |
| Anum_pg_depend_refclassid, |
| BTEqualStrategyNumber, F_OIDEQ, |
| ObjectIdGetDatum(RelationRelationId)); |
| ScanKeyInit(&key[1], |
| Anum_pg_depend_refobjid, |
| BTEqualStrategyNumber, F_OIDEQ, |
| ObjectIdGetDatum(tableOid)); |
| ScanKeyInit(&key[2], |
| Anum_pg_depend_refobjsubid, |
| BTEqualStrategyNumber, F_INT4EQ, |
| Int32GetDatum(attnum)); |
| |
| scan = systable_beginscan(depRel, DependReferenceIndexId, true, |
| NULL, 3, key); |
| |
| while (HeapTupleIsValid(tup = systable_getnext(scan))) |
| { |
| Form_pg_depend deprec = (Form_pg_depend) GETSTRUCT(tup); |
| |
| /* |
| * Look for an auto dependency (serial column) or internal dependency |
| * (identity column) of a sequence on a column. (We need the relkind |
| * test because indexes can also have auto dependencies on columns.) |
| */ |
| if (deprec->classid == RelationRelationId && |
| deprec->objsubid == 0 && |
| (deprec->deptype == DEPENDENCY_AUTO || |
| deprec->deptype == DEPENDENCY_INTERNAL) && |
| get_rel_relkind(deprec->objid) == RELKIND_SEQUENCE) |
| { |
| sequenceId = deprec->objid; |
| break; |
| } |
| } |
| |
| systable_endscan(scan); |
| table_close(depRel, AccessShareLock); |
| |
| if (OidIsValid(sequenceId)) |
| { |
| char *result; |
| |
| result = generate_qualified_relation_name(sequenceId); |
| |
| PG_RETURN_TEXT_P(string_to_text(result)); |
| } |
| |
| PG_RETURN_NULL(); |
| } |
| |
| |
| /* |
| * pg_get_functiondef |
| * Returns the complete "CREATE OR REPLACE FUNCTION ..." statement for |
| * the specified function. |
| * |
| * Note: if you change the output format of this function, be careful not |
| * to break psql's rules (in \ef and \sf) for identifying the start of the |
| * function body. To wit: the function body starts on a line that begins |
| * with "AS ", and no preceding line will look like that. |
| */ |
| Datum |
| pg_get_functiondef(PG_FUNCTION_ARGS) |
| { |
| Oid funcid = PG_GETARG_OID(0); |
| StringInfoData buf; |
| StringInfoData dq; |
| HeapTuple proctup; |
| Form_pg_proc proc; |
| bool isfunction; |
| Datum tmp; |
| bool isnull; |
| const char *prosrc; |
| const char *name; |
| const char *nsp; |
| float4 procost; |
| int oldlen; |
| |
| initStringInfo(&buf); |
| |
| /* Look up the function */ |
| proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); |
| if (!HeapTupleIsValid(proctup)) |
| PG_RETURN_NULL(); |
| |
| proc = (Form_pg_proc) GETSTRUCT(proctup); |
| name = NameStr(proc->proname); |
| |
| if (proc->prokind == PROKIND_AGGREGATE) |
| ereport(ERROR, |
| (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| errmsg("\"%s\" is an aggregate function", name))); |
| |
| isfunction = (proc->prokind != PROKIND_PROCEDURE); |
| |
| /* |
| * We always qualify the function name, to ensure the right function gets |
| * replaced. |
| */ |
| nsp = get_namespace_name(proc->pronamespace); |
| appendStringInfo(&buf, "CREATE OR REPLACE %s %s(", |
| isfunction ? "FUNCTION" : "PROCEDURE", |
| quote_qualified_identifier(nsp, name)); |
| (void) print_function_arguments(&buf, proctup, false, true); |
| appendStringInfoString(&buf, ")\n"); |
| if (isfunction) |
| { |
| appendStringInfoString(&buf, " RETURNS "); |
| print_function_rettype(&buf, proctup); |
| appendStringInfoChar(&buf, '\n'); |
| } |
| |
| print_function_trftypes(&buf, proctup); |
| |
| appendStringInfo(&buf, " LANGUAGE %s\n", |
| quote_identifier(get_language_name(proc->prolang, false))); |
| |
| /* Emit some miscellaneous options on one line */ |
| oldlen = buf.len; |
| |
| if (proc->prokind == PROKIND_WINDOW) |
| appendStringInfoString(&buf, " WINDOW"); |
| switch (proc->provolatile) |
| { |
| case PROVOLATILE_IMMUTABLE: |
| appendStringInfoString(&buf, " IMMUTABLE"); |
| break; |
| case PROVOLATILE_STABLE: |
| appendStringInfoString(&buf, " STABLE"); |
| break; |
| case PROVOLATILE_VOLATILE: |
| break; |
| } |
| |
| switch (proc->proparallel) |
| { |
| case PROPARALLEL_SAFE: |
| appendStringInfoString(&buf, " PARALLEL SAFE"); |
| break; |
| case PROPARALLEL_RESTRICTED: |
| appendStringInfoString(&buf, " PARALLEL RESTRICTED"); |
| break; |
| case PROPARALLEL_UNSAFE: |
| break; |
| } |
| |
| if (proc->proisstrict) |
| appendStringInfoString(&buf, " STRICT"); |
| if (proc->prosecdef) |
| appendStringInfoString(&buf, " SECURITY DEFINER"); |
| if (proc->proleakproof) |
| appendStringInfoString(&buf, " LEAKPROOF"); |
| |
| /* This code for the default cost and rows should match functioncmds.c */ |
| if (proc->prolang == INTERNALlanguageId || |
| proc->prolang == ClanguageId) |
| procost = 1; |
| else |
| procost = 100; |
| if (proc->procost != procost) |
| appendStringInfo(&buf, " COST %g", proc->procost); |
| |
| if (proc->prorows > 0 && proc->prorows != 1000) |
| appendStringInfo(&buf, " ROWS %g", proc->prorows); |
| |
| if (proc->prosupport) |
| { |
| Oid argtypes[1]; |
| |
| /* |
| * We should qualify the support function's name if it wouldn't be |
| * resolved by lookup in the current search path. |
| */ |
| argtypes[0] = INTERNALOID; |
| appendStringInfo(&buf, " SUPPORT %s", |
| generate_function_name(proc->prosupport, 1, |
| NIL, argtypes, |
| false, NULL, EXPR_KIND_NONE)); |
| } |
| |
| if (oldlen != buf.len) |
| appendStringInfoChar(&buf, '\n'); |
| |
| /* Emit any proconfig options, one per line */ |
| tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_proconfig, &isnull); |
| if (!isnull) |
| { |
| ArrayType *a = DatumGetArrayTypeP(tmp); |
| int i; |
| |
| Assert(ARR_ELEMTYPE(a) == TEXTOID); |
| Assert(ARR_NDIM(a) == 1); |
| Assert(ARR_LBOUND(a)[0] == 1); |
| |
| for (i = 1; i <= ARR_DIMS(a)[0]; i++) |
| { |
| Datum d; |
| |
| d = array_ref(a, 1, &i, |
| -1 /* varlenarray */ , |
| -1 /* TEXT's typlen */ , |
| false /* TEXT's typbyval */ , |
| TYPALIGN_INT /* TEXT's typalign */ , |
| &isnull); |
| if (!isnull) |
| { |
| char *configitem = TextDatumGetCString(d); |
| char *pos; |
| |
| pos = strchr(configitem, '='); |
| if (pos == NULL) |
| continue; |
| *pos++ = '\0'; |
| |
| appendStringInfo(&buf, " SET %s TO ", |
| quote_identifier(configitem)); |
| |
| /* |
| * Variables that are marked GUC_LIST_QUOTE were already fully |
| * quoted by flatten_set_variable_args() before they were put |
| * into the proconfig array. However, because the quoting |
| * rules used there aren't exactly like SQL's, we have to |
| * break the list value apart and then quote the elements as |
| * string literals. (The elements may be double-quoted as-is, |
| * but we can't just feed them to the SQL parser; it would do |
| * the wrong thing with elements that are zero-length or |
| * longer than NAMEDATALEN.) |
| * |
| * Variables that are not so marked should just be emitted as |
| * simple string literals. If the variable is not known to |
| * guc.c, we'll do that; this makes it unsafe to use |
| * GUC_LIST_QUOTE for extension variables. |
| */ |
| if (GetConfigOptionFlags(configitem, true) & GUC_LIST_QUOTE) |
| { |
| List *namelist; |
| ListCell *lc; |
| |
| /* Parse string into list of identifiers */ |
| if (!SplitGUCList(pos, ',', &namelist)) |
| { |
| /* this shouldn't fail really */ |
| elog(ERROR, "invalid list syntax in proconfig item"); |
| } |
| foreach(lc, namelist) |
| { |
| char *curname = (char *) lfirst(lc); |
| |
| simple_quote_literal(&buf, curname); |
| if (lnext(namelist, lc)) |
| appendStringInfoString(&buf, ", "); |
| } |
| } |
| else |
| simple_quote_literal(&buf, pos); |
| appendStringInfoChar(&buf, '\n'); |
| } |
| } |
| } |
| |
| /* And finally the function definition ... */ |
| (void) SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull); |
| if (proc->prolang == SQLlanguageId && !isnull) |
| { |
| print_function_sqlbody(&buf, proctup); |
| } |
| else |
| { |
| appendStringInfoString(&buf, "AS "); |
| |
| tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull); |
| if (!isnull) |
| { |
| simple_quote_literal(&buf, TextDatumGetCString(tmp)); |
| appendStringInfoString(&buf, ", "); /* assume prosrc isn't null */ |
| } |
| |
| tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosrc, &isnull); |
| if (isnull) |
| elog(ERROR, "null prosrc"); |
| prosrc = TextDatumGetCString(tmp); |
| |
| /* |
| * We always use dollar quoting. Figure out a suitable delimiter. |
| * |
| * Since the user is likely to be editing the function body string, we |
| * shouldn't use a short delimiter that he might easily create a |
| * conflict with. Hence prefer "$function$"/"$procedure$", but extend |
| * if needed. |
| */ |
| initStringInfo(&dq); |
| appendStringInfoChar(&dq, '$'); |
| appendStringInfoString(&dq, (isfunction ? "function" : "procedure")); |
| while (strstr(prosrc, dq.data) != NULL) |
| appendStringInfoChar(&dq, 'x'); |
| appendStringInfoChar(&dq, '$'); |
| |
| appendBinaryStringInfo(&buf, dq.data, dq.len); |
| appendStringInfoString(&buf, prosrc); |
| appendBinaryStringInfo(&buf, dq.data, dq.len); |
| } |
| |
| appendStringInfoChar(&buf, '\n'); |
| |
| ReleaseSysCache(proctup); |
| |
| PG_RETURN_TEXT_P(string_to_text(buf.data)); |
| } |
| |
| /* |
| * pg_get_function_arguments |
| * Get a nicely-formatted list of arguments for a function. |
| * This is everything that would go between the parentheses in |
| * CREATE FUNCTION. |
| */ |
| Datum |
| pg_get_function_arguments(PG_FUNCTION_ARGS) |
| { |
| Oid funcid = PG_GETARG_OID(0); |
| StringInfoData buf; |
| HeapTuple proctup; |
| |
| proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); |
| if (!HeapTupleIsValid(proctup)) |
| PG_RETURN_NULL(); |
| |
| initStringInfo(&buf); |
| |
| (void) print_function_arguments(&buf, proctup, false, true); |
| |
| ReleaseSysCache(proctup); |
| |
| PG_RETURN_TEXT_P(string_to_text(buf.data)); |
| } |
| |
| /* |
| * pg_get_function_identity_arguments |
| * Get a formatted list of arguments for a function. |
| * This is everything that would go between the parentheses in |
| * ALTER FUNCTION, etc. In particular, don't print defaults. |
| */ |
| Datum |
| pg_get_function_identity_arguments(PG_FUNCTION_ARGS) |
| { |
| Oid funcid = PG_GETARG_OID(0); |
| StringInfoData buf; |
| HeapTuple proctup; |
| |
| proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); |
| if (!HeapTupleIsValid(proctup)) |
| PG_RETURN_NULL(); |
| |
| initStringInfo(&buf); |
| |
| (void) print_function_arguments(&buf, proctup, false, false); |
| |
| ReleaseSysCache(proctup); |
| |
| PG_RETURN_TEXT_P(string_to_text(buf.data)); |
| } |
| |
| /* |
| * pg_get_function_result |
| * Get a nicely-formatted version of the result type of a function. |
| * This is what would appear after RETURNS in CREATE FUNCTION. |
| */ |
| Datum |
| pg_get_function_result(PG_FUNCTION_ARGS) |
| { |
| Oid funcid = PG_GETARG_OID(0); |
| StringInfoData buf; |
| HeapTuple proctup; |
| |
| proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); |
| if (!HeapTupleIsValid(proctup)) |
| PG_RETURN_NULL(); |
| |
| if (((Form_pg_proc) GETSTRUCT(proctup))->prokind == PROKIND_PROCEDURE) |
| { |
| ReleaseSysCache(proctup); |
| PG_RETURN_NULL(); |
| } |
| |
| initStringInfo(&buf); |
| |
| print_function_rettype(&buf, proctup); |
| |
| ReleaseSysCache(proctup); |
| |
| PG_RETURN_TEXT_P(string_to_text(buf.data)); |
| } |
| |
| /* |
| * Guts of pg_get_function_result: append the function's return type |
| * to the specified buffer. |
| */ |
| static void |
| print_function_rettype(StringInfo buf, HeapTuple proctup) |
| { |
| Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(proctup); |
| int ntabargs = 0; |
| StringInfoData rbuf; |
| |
| initStringInfo(&rbuf); |
| |
| if (proc->proretset) |
| { |
| /* It might be a table function; try to print the arguments */ |
| appendStringInfoString(&rbuf, "TABLE("); |
| ntabargs = print_function_arguments(&rbuf, proctup, true, false); |
| if (ntabargs > 0) |
| appendStringInfoChar(&rbuf, ')'); |
| else |
| resetStringInfo(&rbuf); |
| } |
| |
| if (ntabargs == 0) |
| { |
| /* Not a table function, so do the normal thing */ |
| if (proc->proretset) |
| appendStringInfoString(&rbuf, "SETOF "); |
| appendStringInfoString(&rbuf, format_type_be(proc->prorettype)); |
| } |
| |
| appendBinaryStringInfo(buf, rbuf.data, rbuf.len); |
| } |
| |
| /* |
| * Common code for pg_get_function_arguments and pg_get_function_result: |
| * append the desired subset of arguments to buf. We print only TABLE |
| * arguments when print_table_args is true, and all the others when it's false. |
| * We print argument defaults only if print_defaults is true. |
| * Function return value is the number of arguments printed. |
| */ |
| static int |
| print_function_arguments(StringInfo buf, HeapTuple proctup, |
| bool print_table_args, bool print_defaults) |
| { |
| Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(proctup); |
| int numargs; |
| Oid *argtypes; |
| char **argnames; |
| char *argmodes; |
| int insertorderbyat = -1; |
| int argsprinted; |
| int inputargno; |
| int nlackdefaults; |
| List *argdefaults = NIL; |
| ListCell *nextargdefault = NULL; |
| int i; |
| |
| numargs = get_func_arg_info(proctup, |
| &argtypes, &argnames, &argmodes); |
| |
| nlackdefaults = numargs; |
| if (print_defaults && proc->pronargdefaults > 0) |
| { |
| Datum proargdefaults; |
| bool isnull; |
| |
| proargdefaults = SysCacheGetAttr(PROCOID, proctup, |
| Anum_pg_proc_proargdefaults, |
| &isnull); |
| if (!isnull) |
| { |
| char *str; |
| |
| str = TextDatumGetCString(proargdefaults); |
| argdefaults = castNode(List, stringToNode(str)); |
| pfree(str); |
| nextargdefault = list_head(argdefaults); |
| /* nlackdefaults counts only *input* arguments lacking defaults */ |
| nlackdefaults = proc->pronargs - list_length(argdefaults); |
| } |
| } |
| |
| /* Check for special treatment of ordered-set aggregates */ |
| if (proc->prokind == PROKIND_AGGREGATE) |
| { |
| HeapTuple aggtup; |
| Form_pg_aggregate agg; |
| |
| aggtup = SearchSysCache1(AGGFNOID, proc->oid); |
| if (!HeapTupleIsValid(aggtup)) |
| elog(ERROR, "cache lookup failed for aggregate %u", |
| proc->oid); |
| agg = (Form_pg_aggregate) GETSTRUCT(aggtup); |
| if (AGGKIND_IS_ORDERED_SET(agg->aggkind)) |
| insertorderbyat = agg->aggnumdirectargs; |
| ReleaseSysCache(aggtup); |
| } |
| |
| argsprinted = 0; |
| inputargno = 0; |
| for (i = 0; i < numargs; i++) |
| { |
| Oid argtype = argtypes[i]; |
| char *argname = argnames ? argnames[i] : NULL; |
| char argmode = argmodes ? argmodes[i] : PROARGMODE_IN; |
| const char *modename; |
| bool isinput; |
| |
| switch (argmode) |
| { |
| case PROARGMODE_IN: |
| |
| /* |
| * For procedures, explicitly mark all argument modes, so as |
| * to avoid ambiguity with the SQL syntax for DROP PROCEDURE. |
| */ |
| if (proc->prokind == PROKIND_PROCEDURE) |
| modename = "IN "; |
| else |
| modename = ""; |
| isinput = true; |
| break; |
| case PROARGMODE_INOUT: |
| modename = "INOUT "; |
| isinput = true; |
| break; |
| case PROARGMODE_OUT: |
| modename = "OUT "; |
| isinput = false; |
| break; |
| case PROARGMODE_VARIADIC: |
| modename = "VARIADIC "; |
| isinput = true; |
| break; |
| case PROARGMODE_TABLE: |
| modename = ""; |
| isinput = false; |
| break; |
| default: |
| elog(ERROR, "invalid parameter mode '%c'", argmode); |
| modename = NULL; /* keep compiler quiet */ |
| isinput = false; |
| break; |
| } |
| if (isinput) |
| inputargno++; /* this is a 1-based counter */ |
| |
| if (print_table_args != (argmode == PROARGMODE_TABLE)) |
| continue; |
| |
| if (argsprinted == insertorderbyat) |
| { |
| if (argsprinted) |
| appendStringInfoChar(buf, ' '); |
| appendStringInfoString(buf, "ORDER BY "); |
| } |
| else if (argsprinted) |
| appendStringInfoString(buf, ", "); |
| |
| appendStringInfoString(buf, modename); |
| if (argname && argname[0]) |
| appendStringInfo(buf, "%s ", quote_identifier(argname)); |
| appendStringInfoString(buf, format_type_be(argtype)); |
| if (print_defaults && isinput && inputargno > nlackdefaults) |
| { |
| Node *expr; |
| |
| Assert(nextargdefault != NULL); |
| expr = (Node *) lfirst(nextargdefault); |
| nextargdefault = lnext(argdefaults, nextargdefault); |
| |
| appendStringInfo(buf, " DEFAULT %s", |
| deparse_expression(expr, NIL, false, false)); |
| } |
| argsprinted++; |
| |
| /* nasty hack: print the last arg twice for variadic ordered-set agg */ |
| if (argsprinted == insertorderbyat && i == numargs - 1) |
| { |
| i--; |
| /* aggs shouldn't have defaults anyway, but just to be sure ... */ |
| print_defaults = false; |
| } |
| } |
| |
| return argsprinted; |
| } |
| |
| static bool |
| is_input_argument(int nth, const char *argmodes) |
| { |
| return (!argmodes |
| || argmodes[nth] == PROARGMODE_IN |
| || argmodes[nth] == PROARGMODE_INOUT |
| || argmodes[nth] == PROARGMODE_VARIADIC); |
| } |
| |
| /* |
| * Append used transformed types to specified buffer |
| */ |
| static void |
| print_function_trftypes(StringInfo buf, HeapTuple proctup) |
| { |
| Oid *trftypes; |
| int ntypes; |
| |
| ntypes = get_func_trftypes(proctup, &trftypes); |
| if (ntypes > 0) |
| { |
| int i; |
| |
| appendStringInfoString(buf, " TRANSFORM "); |
| for (i = 0; i < ntypes; i++) |
| { |
| if (i != 0) |
| appendStringInfoString(buf, ", "); |
| appendStringInfo(buf, "FOR TYPE %s", format_type_be(trftypes[i])); |
| } |
| appendStringInfoChar(buf, '\n'); |
| } |
| } |
| |
| /* |
| * Get textual representation of a function argument's default value. The |
| * second argument of this function is the argument number among all arguments |
| * (i.e. proallargtypes, *not* proargtypes), starting with 1, because that's |
| * how information_schema.sql uses it. |
| */ |
| Datum |
| pg_get_function_arg_default(PG_FUNCTION_ARGS) |
| { |
| Oid funcid = PG_GETARG_OID(0); |
| int32 nth_arg = PG_GETARG_INT32(1); |
| HeapTuple proctup; |
| Form_pg_proc proc; |
| int numargs; |
| Oid *argtypes; |
| char **argnames; |
| char *argmodes; |
| int i; |
| List *argdefaults; |
| Node *node; |
| char *str; |
| int nth_inputarg; |
| Datum proargdefaults; |
| bool isnull; |
| int nth_default; |
| |
| proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); |
| if (!HeapTupleIsValid(proctup)) |
| PG_RETURN_NULL(); |
| |
| numargs = get_func_arg_info(proctup, &argtypes, &argnames, &argmodes); |
| if (nth_arg < 1 || nth_arg > numargs || !is_input_argument(nth_arg - 1, argmodes)) |
| { |
| ReleaseSysCache(proctup); |
| PG_RETURN_NULL(); |
| } |
| |
| nth_inputarg = 0; |
| for (i = 0; i < nth_arg; i++) |
| if (is_input_argument(i, argmodes)) |
| nth_inputarg++; |
| |
| proargdefaults = SysCacheGetAttr(PROCOID, proctup, |
| Anum_pg_proc_proargdefaults, |
| &isnull); |
| if (isnull) |
| { |
| ReleaseSysCache(proctup); |
| PG_RETURN_NULL(); |
| } |
| |
| str = TextDatumGetCString(proargdefaults); |
| argdefaults = castNode(List, stringToNode(str)); |
| pfree(str); |
| |
| proc = (Form_pg_proc) GETSTRUCT(proctup); |
| |
| /* |
| * Calculate index into proargdefaults: proargdefaults corresponds to the |
| * last N input arguments, where N = pronargdefaults. |
| */ |
| nth_default = nth_inputarg - 1 - (proc->pronargs - proc->pronargdefaults); |
| |
| if (nth_default < 0 || nth_default >= list_length(argdefaults)) |
| { |
| ReleaseSysCache(proctup); |
| PG_RETURN_NULL(); |
| } |
| node = list_nth(argdefaults, nth_default); |
| str = deparse_expression(node, NIL, false, false); |
| |
| ReleaseSysCache(proctup); |
| |
| PG_RETURN_TEXT_P(string_to_text(str)); |
| } |
| |
| static void |
| print_function_sqlbody(StringInfo buf, HeapTuple proctup) |
| { |
| int numargs; |
| Oid *argtypes; |
| char **argnames; |
| char *argmodes; |
| deparse_namespace dpns = {0}; |
| Datum tmp; |
| bool isnull; |
| Node *n; |
| |
| dpns.funcname = pstrdup(NameStr(((Form_pg_proc) GETSTRUCT(proctup))->proname)); |
| numargs = get_func_arg_info(proctup, |
| &argtypes, &argnames, &argmodes); |
| dpns.numargs = numargs; |
| dpns.argnames = argnames; |
| |
| tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull); |
| Assert(!isnull); |
| n = stringToNode(TextDatumGetCString(tmp)); |
| |
| if (IsA(n, List)) |
| { |
| List *stmts; |
| ListCell *lc; |
| |
| stmts = linitial(castNode(List, n)); |
| |
| appendStringInfoString(buf, "BEGIN ATOMIC\n"); |
| |
| foreach(lc, stmts) |
| { |
| Query *query = lfirst_node(Query, lc); |
| |
| /* It seems advisable to get at least AccessShareLock on rels */ |
| AcquireRewriteLocks(query, false, false); |
| get_query_def(query, buf, list_make1(&dpns), NULL, false, |
| PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1); |
| appendStringInfoChar(buf, ';'); |
| appendStringInfoChar(buf, '\n'); |
| } |
| |
| appendStringInfoString(buf, "END"); |
| } |
| else |
| { |
| Query *query = castNode(Query, n); |
| |
| /* It seems advisable to get at least AccessShareLock on rels */ |
| AcquireRewriteLocks(query, false, false); |
| get_query_def(query, buf, list_make1(&dpns), NULL, false, |
| 0, WRAP_COLUMN_DEFAULT, 0); |
| } |
| } |
| |
| Datum |
| pg_get_function_sqlbody(PG_FUNCTION_ARGS) |
| { |
| Oid funcid = PG_GETARG_OID(0); |
| StringInfoData buf; |
| HeapTuple proctup; |
| bool isnull; |
| |
| initStringInfo(&buf); |
| |
| /* Look up the function */ |
| proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); |
| if (!HeapTupleIsValid(proctup)) |
| PG_RETURN_NULL(); |
| |
| (void) SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull); |
| if (isnull) |
| { |
| ReleaseSysCache(proctup); |
| PG_RETURN_NULL(); |
| } |
| |
| print_function_sqlbody(&buf, proctup); |
| |
| ReleaseSysCache(proctup); |
| |
| PG_RETURN_TEXT_P(cstring_to_text(buf.data)); |
| } |
| |
| |
| /* |
| * deparse_expression - General utility for deparsing expressions |
| * |
| * calls deparse_expression_pretty with all prettyPrinting disabled |
| */ |
| char * |
| deparse_expression(Node *expr, List *dpcontext, |
| bool forceprefix, bool showimplicit) |
| { |
| return deparse_expression_pretty(expr, dpcontext, forceprefix, |
| showimplicit, 0, 0); |
| } |
| |
| /* ---------- |
| * deparse_expression_pretty - General utility for deparsing expressions |
| * |
| * expr is the node tree to be deparsed. It must be a transformed expression |
| * tree (ie, not the raw output of gram.y). |
| * |
| * dpcontext is a list of deparse_namespace nodes representing the context |
| * for interpreting Vars in the node tree. It can be NIL if no Vars are |
| * expected. |
| * |
| * forceprefix is true to force all Vars to be prefixed with their table names. |
| * |
| * showimplicit is true to force all implicit casts to be shown explicitly. |
| * |
| * Tries to pretty up the output according to prettyFlags and startIndent. |
| * |
| * The result is a palloc'd string. |
| * ---------- |
| */ |
| static char * |
| deparse_expression_pretty(Node *expr, List *dpcontext, |
| bool forceprefix, bool showimplicit, |
| int prettyFlags, int startIndent) |
| { |
| StringInfoData buf; |
| deparse_context context; |
| |
| initStringInfo(&buf); |
| context.buf = &buf; |
| context.namespaces = dpcontext; |
| context.groupClause = NIL; |
| context.windowClause = NIL; |
| context.windowTList = NIL; |
| context.varprefix = forceprefix; |
| context.prettyFlags = prettyFlags; |
| context.wrapColumn = WRAP_COLUMN_DEFAULT; |
| context.indentLevel = startIndent; |
| context.special_exprkind = EXPR_KIND_NONE; |
| context.appendparents = NULL; |
| |
| get_rule_expr(expr, &context, showimplicit); |
| |
| return buf.data; |
| } |
| |
| /* ---------- |
| * deparse_context_for - Build deparse context for a single relation |
| * |
| * Given the reference name (alias) and OID of a relation, build deparsing |
| * context for an expression referencing only that relation (as varno 1, |
| * varlevelsup 0). This is sufficient for many uses of deparse_expression. |
| * ---------- |
| */ |
| List * |
| deparse_context_for(const char *aliasname, Oid relid) |
| { |
| deparse_namespace *dpns; |
| RangeTblEntry *rte; |
| |
| dpns = (deparse_namespace *) palloc0(sizeof(deparse_namespace)); |
| |
| /* Build a minimal RTE for the rel */ |
| rte = makeNode(RangeTblEntry); |
| rte->rtekind = RTE_RELATION; |
| rte->relid = relid; |
| rte->relkind = RELKIND_RELATION; /* no need for exactness here */ |
| rte->rellockmode = AccessShareLock; |
| rte->alias = makeAlias(aliasname, NIL); |
| rte->eref = rte->alias; |
| rte->lateral = false; |
| rte->inh = false; |
| rte->inFromCl = true; |
| |
| /* Build one-element rtable */ |
| dpns->rtable = list_make1(rte); |
| dpns->subplans = NIL; |
| dpns->ctes = NIL; |
| dpns->appendrels = NULL; |
| set_rtable_names(dpns, NIL, NULL); |
| set_simple_column_names(dpns); |
| |
| /* Return a one-deep namespace stack */ |
| return list_make1(dpns); |
| } |
| |
| /* |
| * deparse_context_for_plan_tree - Build deparse context for a Plan tree |
| * |
| * When deparsing an expression in a Plan tree, we use the plan's rangetable |
| * to resolve names of simple Vars. The initialization of column names for |
| * this is rather expensive if the rangetable is large, and it'll be the same |
| * for every expression in the Plan tree; so we do it just once and re-use |
| * the result of this function for each expression. (Note that the result |
| * is not usable until set_deparse_context_plan() is applied to it.) |
| * |
| * In addition to the PlannedStmt, pass the per-RTE alias names |
| * assigned by a previous call to select_rtable_names_for_explain. |
| */ |
| List * |
| deparse_context_for_plan_tree(PlannedStmt *pstmt, List *rtable_names) |
| { |
| deparse_namespace *dpns; |
| |
| dpns = (deparse_namespace *) palloc0(sizeof(deparse_namespace)); |
| |
| /* Initialize fields that stay the same across the whole plan tree */ |
| dpns->rtable = pstmt->rtable; |
| dpns->rtable_names = rtable_names; |
| dpns->subplans = pstmt->subplans; |
| dpns->ctes = NIL; |
| if (pstmt->appendRelations) |
| { |
| /* Set up the array, indexed by child relid */ |
| int ntables = list_length(dpns->rtable); |
| ListCell *lc; |
| |
| dpns->appendrels = (AppendRelInfo **) |
| palloc0((ntables + 1) * sizeof(AppendRelInfo *)); |
| foreach(lc, pstmt->appendRelations) |
| { |
| AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); |
| Index crelid = appinfo->child_relid; |
| |
| Assert(crelid > 0 && crelid <= ntables); |
| Assert(dpns->appendrels[crelid] == NULL); |
| dpns->appendrels[crelid] = appinfo; |
| } |
| } |
| else |
| dpns->appendrels = NULL; /* don't need it */ |
| |
| /* |
| * Set up column name aliases. We will get rather bogus results for join |
| * RTEs, but that doesn't matter because plan trees don't contain any join |
| * alias Vars. |
| */ |
| set_simple_column_names(dpns); |
| |
| /* Return a one-deep namespace stack */ |
| return list_make1(dpns); |
| } |
| |
| /* |
| * set_deparse_context_plan - Specify Plan node containing expression |
| * |
| * When deparsing an expression in a Plan tree, we might have to resolve |
| * OUTER_VAR, INNER_VAR, or INDEX_VAR references. To do this, the caller must |
| * provide the parent Plan node. Then OUTER_VAR and INNER_VAR references |
| * can be resolved by drilling down into the left and right child plans. |
| * Similarly, INDEX_VAR references can be resolved by reference to the |
| * indextlist given in a parent IndexOnlyScan node, or to the scan tlist in |
| * ForeignScan and CustomScan nodes. (Note that we don't currently support |
| * deparsing of indexquals in regular IndexScan or BitmapIndexScan nodes; |
| * for those, we can only deparse the indexqualorig fields, which won't |
| * contain INDEX_VAR Vars.) |
| * |
| * The ancestors list is a list of the Plan's parent Plan and SubPlan nodes, |
| * the most-closely-nested first. This is needed to resolve PARAM_EXEC |
| * Params. Note we assume that all the Plan nodes share the same rtable. |
| * |
| * Once this function has been called, deparse_expression() can be called on |
| * subsidiary expression(s) of the specified Plan node. To deparse |
| * expressions of a different Plan node in the same Plan tree, re-call this |
| * function to identify the new parent Plan node. |
| * |
| * The result is the same List passed in; this is a notational convenience. |
| */ |
| List * |
| set_deparse_context_plan(List *dpcontext, Plan *plan, List *ancestors) |
| { |
| deparse_namespace *dpns; |
| |
| /* Should always have one-entry namespace list for Plan deparsing */ |
| Assert(list_length(dpcontext) == 1); |
| dpns = (deparse_namespace *) linitial(dpcontext); |
| |
| /* Set our attention on the specific plan node passed in */ |
| dpns->ancestors = ancestors; |
| set_deparse_plan(dpns, plan); |
| |
| return dpcontext; |
| } |
| |
| /* |
| * select_rtable_names_for_explain - Select RTE aliases for EXPLAIN |
| * |
| * Determine the relation aliases we'll use during an EXPLAIN operation. |
| * This is just a frontend to set_rtable_names. We have to expose the aliases |
| * to EXPLAIN because EXPLAIN needs to know the right alias names to print. |
| */ |
| List * |
| select_rtable_names_for_explain(List *rtable, Bitmapset *rels_used) |
| { |
| deparse_namespace dpns; |
| |
| memset(&dpns, 0, sizeof(dpns)); |
| dpns.rtable = rtable; |
| dpns.subplans = NIL; |
| dpns.ctes = NIL; |
| dpns.appendrels = NULL; |
| set_rtable_names(&dpns, NIL, rels_used); |
| /* We needn't bother computing column aliases yet */ |
| |
| return dpns.rtable_names; |
| } |
| |
| /* |
| * set_rtable_names: select RTE aliases to be used in printing a query |
| * |
| * We fill in dpns->rtable_names with a list of names that is one-for-one with |
| * the already-filled dpns->rtable list. Each RTE name is unique among those |
| * in the new namespace plus any ancestor namespaces listed in |
| * parent_namespaces. |
| * |
| * If rels_used isn't NULL, only RTE indexes listed in it are given aliases. |
| * |
| * Note that this function is only concerned with relation names, not column |
| * names. |
| */ |
| static void |
| set_rtable_names(deparse_namespace *dpns, List *parent_namespaces, |
| Bitmapset *rels_used) |
| { |
| HASHCTL hash_ctl; |
| HTAB *names_hash; |
| NameHashEntry *hentry; |
| bool found; |
| int rtindex; |
| ListCell *lc; |
| |
| dpns->rtable_names = NIL; |
| /* nothing more to do if empty rtable */ |
| if (dpns->rtable == NIL) |
| return; |
| |
| /* |
| * We use a hash table to hold known names, so that this process is O(N) |
| * not O(N^2) for N names. |
| */ |
| hash_ctl.keysize = NAMEDATALEN; |
| hash_ctl.entrysize = sizeof(NameHashEntry); |
| hash_ctl.hcxt = CurrentMemoryContext; |
| names_hash = hash_create("set_rtable_names names", |
| list_length(dpns->rtable), |
| &hash_ctl, |
| HASH_ELEM | HASH_STRINGS | HASH_CONTEXT); |
| |
| /* Preload the hash table with names appearing in parent_namespaces */ |
| foreach(lc, parent_namespaces) |
| { |
| deparse_namespace *olddpns = (deparse_namespace *) lfirst(lc); |
| ListCell *lc2; |
| |
| foreach(lc2, olddpns->rtable_names) |
| { |
| char *oldname = (char *) lfirst(lc2); |
| |
| if (oldname == NULL) |
| continue; |
| hentry = (NameHashEntry *) hash_search(names_hash, |
| oldname, |
| HASH_ENTER, |
| &found); |
| /* we do not complain about duplicate names in parent namespaces */ |
| hentry->counter = 0; |
| } |
| } |
| |
| /* Now we can scan the rtable */ |
| rtindex = 1; |
| foreach(lc, dpns->rtable) |
| { |
| RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); |
| char *refname; |
| |
| /* Just in case this takes an unreasonable amount of time ... */ |
| CHECK_FOR_INTERRUPTS(); |
| |
| if (rels_used && !bms_is_member(rtindex, rels_used)) |
| { |
| /* Ignore unreferenced RTE */ |
| refname = NULL; |
| } |
| else if (rte->alias) |
| { |
| /* If RTE has a user-defined alias, prefer that */ |
| refname = rte->alias->aliasname; |
| } |
| else if (rte->rtekind == RTE_RELATION) |
| { |
| /* Use the current actual name of the relation */ |
| refname = get_rel_name(rte->relid); |
| } |
| else if (rte->rtekind == RTE_JOIN) |
| { |
| /* Unnamed join has no refname */ |
| refname = NULL; |
| } |
| else |
| { |
| /* Otherwise use whatever the parser assigned */ |
| refname = rte->eref->aliasname; |
| } |
| |
| /* |
| * If the selected name isn't unique, append digits to make it so, and |
| * make a new hash entry for it once we've got a unique name. For a |
| * very long input name, we might have to truncate to stay within |
| * NAMEDATALEN. |
| */ |
| if (refname) |
| { |
| hentry = (NameHashEntry *) hash_search(names_hash, |
| refname, |
| HASH_ENTER, |
| &found); |
| if (found) |
| { |
| /* Name already in use, must choose a new one */ |
| int refnamelen = strlen(refname); |
| char *modname = (char *) palloc(refnamelen + 16); |
| NameHashEntry *hentry2; |
| |
| do |
| { |
| hentry->counter++; |
| for (;;) |
| { |
| memcpy(modname, refname, refnamelen); |
| sprintf(modname + refnamelen, "_%d", hentry->counter); |
| if (strlen(modname) < NAMEDATALEN) |
| break; |
| /* drop chars from refname to keep all the digits */ |
| refnamelen = pg_mbcliplen(refname, refnamelen, |
| refnamelen - 1); |
| } |
| hentry2 = (NameHashEntry *) hash_search(names_hash, |
| modname, |
| HASH_ENTER, |
| &found); |
| } while (found); |
| hentry2->counter = 0; /* init new hash entry */ |
| refname = modname; |
| } |
| else |
| { |
| /* Name not previously used, need only initialize hentry */ |
| hentry->counter = 0; |
| } |
| } |
| |
| dpns->rtable_names = lappend(dpns->rtable_names, refname); |
| rtindex++; |
| } |
| |
| hash_destroy(names_hash); |
| } |
| |
| /* |
| * set_deparse_for_query: set up deparse_namespace for deparsing a Query tree |
| * |
| * For convenience, this is defined to initialize the deparse_namespace struct |
| * from scratch. |
| */ |
| static void |
| set_deparse_for_query(deparse_namespace *dpns, Query *query, |
| List *parent_namespaces) |
| { |
| ListCell *lc; |
| ListCell *lc2; |
| |
| /* Initialize *dpns and fill rtable/ctes links */ |
| memset(dpns, 0, sizeof(deparse_namespace)); |
| dpns->rtable = query->rtable; |
| dpns->subplans = NIL; |
| dpns->ctes = query->cteList; |
| dpns->appendrels = NULL; |
| |
| /* Assign a unique relation alias to each RTE */ |
| set_rtable_names(dpns, parent_namespaces, NULL); |
| |
| /* Initialize dpns->rtable_columns to contain zeroed structs */ |
| dpns->rtable_columns = NIL; |
| while (list_length(dpns->rtable_columns) < list_length(dpns->rtable)) |
| dpns->rtable_columns = lappend(dpns->rtable_columns, |
| palloc0(sizeof(deparse_columns))); |
| |
| /* If it's a utility query, it won't have a jointree */ |
| if (query->jointree) |
| { |
| /* Detect whether global uniqueness of USING names is needed */ |
| dpns->unique_using = |
| has_dangerous_join_using(dpns, (Node *) query->jointree); |
| |
| /* |
| * Select names for columns merged by USING, via a recursive pass over |
| * the query jointree. |
| */ |
| set_using_names(dpns, (Node *) query->jointree, NIL); |
| } |
| |
| /* |
| * Now assign remaining column aliases for each RTE. We do this in a |
| * linear scan of the rtable, so as to process RTEs whether or not they |
| * are in the jointree (we mustn't miss NEW.*, INSERT target relations, |
| * etc). JOIN RTEs must be processed after their children, but this is |
| * okay because they appear later in the rtable list than their children |
| * (cf Asserts in identify_join_columns()). |
| */ |
| forboth(lc, dpns->rtable, lc2, dpns->rtable_columns) |
| { |
| RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); |
| deparse_columns *colinfo = (deparse_columns *) lfirst(lc2); |
| |
| if (rte->rtekind == RTE_JOIN) |
| set_join_column_names(dpns, rte, colinfo); |
| else |
| set_relation_column_names(dpns, rte, colinfo); |
| } |
| } |
| |
| /* |
| * set_simple_column_names: fill in column aliases for non-query situations |
| * |
| * This handles EXPLAIN and cases where we only have relation RTEs. Without |
| * a join tree, we can't do anything smart about join RTEs, but we don't |
| * need to (note that EXPLAIN should never see join alias Vars anyway). |
| * If we do hit a join RTE we'll just process it like a non-table base RTE. |
| */ |
| static void |
| set_simple_column_names(deparse_namespace *dpns) |
| { |
| ListCell *lc; |
| ListCell *lc2; |
| |
| /* Initialize dpns->rtable_columns to contain zeroed structs */ |
| dpns->rtable_columns = NIL; |
| while (list_length(dpns->rtable_columns) < list_length(dpns->rtable)) |
| dpns->rtable_columns = lappend(dpns->rtable_columns, |
| palloc0(sizeof(deparse_columns))); |
| |
| /* Assign unique column aliases within each RTE */ |
| forboth(lc, dpns->rtable, lc2, dpns->rtable_columns) |
| { |
| RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); |
| deparse_columns *colinfo = (deparse_columns *) lfirst(lc2); |
| |
| set_relation_column_names(dpns, rte, colinfo); |
| } |
| } |
| |
| /* |
| * has_dangerous_join_using: search jointree for unnamed JOIN USING |
| * |
| * Merged columns of a JOIN USING may act differently from either of the input |
| * columns, either because they are merged with COALESCE (in a FULL JOIN) or |
| * because an implicit coercion of the underlying input column is required. |
| * In such a case the column must be referenced as a column of the JOIN not as |
| * a column of either input. And this is problematic if the join is unnamed |
| * (alias-less): we cannot qualify the column's name with an RTE name, since |
| * there is none. (Forcibly assigning an alias to the join is not a solution, |
| * since that will prevent legal references to tables below the join.) |
| * To ensure that every column in the query is unambiguously referenceable, |
| * we must assign such merged columns names that are globally unique across |
| * the whole query, aliasing other columns out of the way as necessary. |
| * |
| * Because the ensuing re-aliasing is fairly damaging to the readability of |
| * the query, we don't do this unless we have to. So, we must pre-scan |
| * the join tree to see if we have to, before starting set_using_names(). |
| */ |
| static bool |
| has_dangerous_join_using(deparse_namespace *dpns, Node *jtnode) |
| { |
| if (IsA(jtnode, RangeTblRef)) |
| { |
| /* nothing to do here */ |
| } |
| else if (IsA(jtnode, FromExpr)) |
| { |
| FromExpr *f = (FromExpr *) jtnode; |
| ListCell *lc; |
| |
| foreach(lc, f->fromlist) |
| { |
| if (has_dangerous_join_using(dpns, (Node *) lfirst(lc))) |
| return true; |
| } |
| } |
| else if (IsA(jtnode, JoinExpr)) |
| { |
| JoinExpr *j = (JoinExpr *) jtnode; |
| |
| /* Is it an unnamed JOIN with USING? */ |
| if (j->alias == NULL && j->usingClause) |
| { |
| /* |
| * Yes, so check each join alias var to see if any of them are not |
| * simple references to underlying columns. If so, we have a |
| * dangerous situation and must pick unique aliases. |
| */ |
| RangeTblEntry *jrte = rt_fetch(j->rtindex, dpns->rtable); |
| |
| /* We need only examine the merged columns */ |
| for (int i = 0; i < jrte->joinmergedcols; i++) |
| { |
| Node *aliasvar = list_nth(jrte->joinaliasvars, i); |
| |
| if (!IsA(aliasvar, Var)) |
| return true; |
| } |
| } |
| |
| /* Nope, but inspect children */ |
| if (has_dangerous_join_using(dpns, j->larg)) |
| return true; |
| if (has_dangerous_join_using(dpns, j->rarg)) |
| return true; |
| } |
| else |
| elog(ERROR, "unrecognized node type: %d", |
| (int) nodeTag(jtnode)); |
| return false; |
| } |
| |
| /* |
| * set_using_names: select column aliases to be used for merged USING columns |
| * |
| * We do this during a recursive descent of the query jointree. |
| * dpns->unique_using must already be set to determine the global strategy. |
| * |
| * Column alias info is saved in the dpns->rtable_columns list, which is |
| * assumed to be filled with pre-zeroed deparse_columns structs. |
| * |
| * parentUsing is a list of all USING aliases assigned in parent joins of |
| * the current jointree node. (The passed-in list must not be modified.) |
| */ |
| static void |
| set_using_names(deparse_namespace *dpns, Node *jtnode, List *parentUsing) |
| { |
| if (IsA(jtnode, RangeTblRef)) |
| { |
| /* nothing to do now */ |
| } |
| else if (IsA(jtnode, FromExpr)) |
| { |
| FromExpr *f = (FromExpr *) jtnode; |
| ListCell *lc; |
| |
| foreach(lc, f->fromlist) |
| set_using_names(dpns, (Node *) lfirst(lc), parentUsing); |
| } |
| else if (IsA(jtnode, JoinExpr)) |
| { |
| JoinExpr *j = (JoinExpr *) jtnode; |
| RangeTblEntry *rte = rt_fetch(j->rtindex, dpns->rtable); |
| deparse_columns *colinfo = deparse_columns_fetch(j->rtindex, dpns); |
| int *leftattnos; |
| int *rightattnos; |
| deparse_columns *leftcolinfo; |
| deparse_columns *rightcolinfo; |
| int i; |
| ListCell *lc; |
| |
| /* Get info about the shape of the join */ |
| identify_join_columns(j, rte, colinfo); |
| leftattnos = colinfo->leftattnos; |
| rightattnos = colinfo->rightattnos; |
| |
| /* Look up the not-yet-filled-in child deparse_columns structs */ |
| leftcolinfo = deparse_columns_fetch(colinfo->leftrti, dpns); |
| rightcolinfo = deparse_columns_fetch(colinfo->rightrti, dpns); |
| |
| /* |
| * If this join is unnamed, then we cannot substitute new aliases at |
| * this level, so any name requirements pushed down to here must be |
| * pushed down again to the children. |
| */ |
| if (rte->alias == NULL) |
| { |
| for (i = 0; i < colinfo->num_cols; i++) |
| { |
| char *colname = colinfo->colnames[i]; |
| |
| if (colname == NULL) |
| continue; |
| |
| /* Push down to left column, unless it's a system column */ |
| if (leftattnos[i] > 0) |
| { |
| expand_colnames_array_to(leftcolinfo, leftattnos[i]); |
| leftcolinfo->colnames[leftattnos[i] - 1] = colname; |
| } |
| |
| /* Same on the righthand side */ |
| if (rightattnos[i] > 0) |
| { |
| expand_colnames_array_to(rightcolinfo, rightattnos[i]); |
| rightcolinfo->colnames[rightattnos[i] - 1] = colname; |
| } |
| } |
| } |
| |
| /* |
| * If there's a USING clause, select the USING column names and push |
| * those names down to the children. We have two strategies: |
| * |
| * If dpns->unique_using is true, we force all USING names to be |
| * unique across the whole query level. In principle we'd only need |
| * the names of dangerous USING columns to be globally unique, but to |
| * safely assign all USING names in a single pass, we have to enforce |
| * the same uniqueness rule for all of them. However, if a USING |
| * column's name has been pushed down from the parent, we should use |
| * it as-is rather than making a uniqueness adjustment. This is |
| * necessary when we're at an unnamed join, and it creates no risk of |
| * ambiguity. Also, if there's a user-written output alias for a |
| * merged column, we prefer to use that rather than the input name; |
| * this simplifies the logic and seems likely to lead to less aliasing |
| * overall. |
| * |
| * If dpns->unique_using is false, we only need USING names to be |
| * unique within their own join RTE. We still need to honor |
| * pushed-down names, though. |
| * |
| * Though significantly different in results, these two strategies are |
| * implemented by the same code, with only the difference of whether |
| * to put assigned names into dpns->using_names. |
| */ |
| if (j->usingClause) |
| { |
| /* Copy the input parentUsing list so we don't modify it */ |
| parentUsing = list_copy(parentUsing); |
| |
| /* USING names must correspond to the first join output columns */ |
| expand_colnames_array_to(colinfo, list_length(j->usingClause)); |
| i = 0; |
| foreach(lc, j->usingClause) |
| { |
| char *colname = strVal(lfirst(lc)); |
| |
| /* Assert it's a merged column */ |
| Assert(leftattnos[i] != 0 && rightattnos[i] != 0); |
| |
| /* Adopt passed-down name if any, else select unique name */ |
| if (colinfo->colnames[i] != NULL) |
| colname = colinfo->colnames[i]; |
| else |
| { |
| /* Prefer user-written output alias if any */ |
| if (rte->alias && i < list_length(rte->alias->colnames)) |
| colname = strVal(list_nth(rte->alias->colnames, i)); |
| /* Make it appropriately unique */ |
| colname = make_colname_unique(colname, dpns, colinfo); |
| if (dpns->unique_using) |
| dpns->using_names = lappend(dpns->using_names, |
| colname); |
| /* Save it as output column name, too */ |
| colinfo->colnames[i] = colname; |
| } |
| |
| /* Remember selected names for use later */ |
| colinfo->usingNames = lappend(colinfo->usingNames, colname); |
| parentUsing = lappend(parentUsing, colname); |
| |
| /* Push down to left column, unless it's a system column */ |
| if (leftattnos[i] > 0) |
| { |
| expand_colnames_array_to(leftcolinfo, leftattnos[i]); |
| leftcolinfo->colnames[leftattnos[i] - 1] = colname; |
| } |
| |
| /* Same on the righthand side */ |
| if (rightattnos[i] > 0) |
| { |
| expand_colnames_array_to(rightcolinfo, rightattnos[i]); |
| rightcolinfo->colnames[rightattnos[i] - 1] = colname; |
| } |
| |
| i++; |
| } |
| } |
| |
| /* Mark child deparse_columns structs with correct parentUsing info */ |
| leftcolinfo->parentUsing = parentUsing; |
| rightcolinfo->parentUsing = parentUsing; |
| |
| /* Now recursively assign USING column names in children */ |
| set_using_names(dpns, j->larg, parentUsing); |
| set_using_names(dpns, j->rarg, parentUsing); |
| } |
| else |
| elog(ERROR, "unrecognized node type: %d", |
| (int) nodeTag(jtnode)); |
| } |
| |
| /* |
| * set_relation_column_names: select column aliases for a non-join RTE |
| * |
| * Column alias info is saved in *colinfo, which is assumed to be pre-zeroed. |
| * If any colnames entries are already filled in, those override local |
| * choices. |
| */ |
| static void |
| set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte, |
| deparse_columns *colinfo) |
| { |
| int ncolumns; |
| char **real_colnames; |
| bool changed_any; |
| int noldcolumns; |
| int i; |
| int j; |
| |
| /* |
| * Construct an array of the current "real" column names of the RTE. |
| * real_colnames[] will be indexed by physical column number, with NULL |
| * entries for dropped columns. |
| */ |
| if (rte->rtekind == RTE_RELATION) |
| { |
| /* Relation --- look to the system catalogs for up-to-date info */ |
| Relation rel; |
| TupleDesc tupdesc; |
| |
| rel = relation_open(rte->relid, AccessShareLock); |
| tupdesc = RelationGetDescr(rel); |
| |
| ncolumns = tupdesc->natts; |
| real_colnames = (char **) palloc(ncolumns * sizeof(char *)); |
| |
| for (i = 0; i < ncolumns; i++) |
| { |
| Form_pg_attribute attr = TupleDescAttr(tupdesc, i); |
| |
| if (attr->attisdropped) |
| real_colnames[i] = NULL; |
| else |
| real_colnames[i] = pstrdup(NameStr(attr->attname)); |
| } |
| relation_close(rel, AccessShareLock); |
| } |
| else |
| { |
| /* Otherwise get the column names from eref or expandRTE() */ |
| List *colnames; |
| ListCell *lc; |
| |
| /* |
| * Functions returning composites have the annoying property that some |
| * of the composite type's columns might have been dropped since the |
| * query was parsed. If possible, use expandRTE() to handle that |
| * case, since it has the tedious logic needed to find out about |
| * dropped columns. However, if we're explaining a plan, then we |
| * don't have rte->functions because the planner thinks that won't be |
| * needed later, and that breaks expandRTE(). So in that case we have |
| * to rely on rte->eref, which may lead us to report a dropped |
| * column's old name; that seems close enough for EXPLAIN's purposes. |
| * |
| * For non-RELATION, non-FUNCTION RTEs, we can just look at rte->eref, |
| * which should be sufficiently up-to-date: no other RTE types can |
| * have columns get dropped from under them after parsing. |
| */ |
| if (rte->rtekind == RTE_FUNCTION && rte->functions != NIL) |
| { |
| /* Since we're not creating Vars, rtindex etc. don't matter */ |
| expandRTE(rte, 1, 0, -1, true /* include dropped */ , |
| &colnames, NULL); |
| } |
| else |
| colnames = rte->eref->colnames; |
| |
| ncolumns = list_length(colnames); |
| real_colnames = (char **) palloc(ncolumns * sizeof(char *)); |
| |
| i = 0; |
| foreach(lc, colnames) |
| { |
| /* |
| * If the column name we find here is an empty string, then it's a |
| * dropped column, so change to NULL. |
| */ |
| char *cname = strVal(lfirst(lc)); |
| |
| if (cname[0] == '\0') |
| cname = NULL; |
| real_colnames[i] = cname; |
| i++; |
| } |
| } |
| |
| /* |
| * Ensure colinfo->colnames has a slot for each column. (It could be long |
| * enough already, if we pushed down a name for the last column.) Note: |
| * it's possible that there are now more columns than there were when the |
| * query was parsed, ie colnames could be longer than rte->eref->colnames. |
| * We must assign unique aliases to the new columns too, else there could |
| * be unresolved conflicts when the view/rule is reloaded. |
| */ |
| expand_colnames_array_to(colinfo, ncolumns); |
| Assert(colinfo->num_cols == ncolumns); |
| |
| /* |
| * Make sufficiently large new_colnames and is_new_col arrays, too. |
| * |
| * Note: because we leave colinfo->num_new_cols zero until after the loop, |
| * colname_is_unique will not consult that array, which is fine because it |
| * would only be duplicate effort. |
| */ |
| colinfo->new_colnames = (char **) palloc(ncolumns * sizeof(char *)); |
| colinfo->is_new_col = (bool *) palloc(ncolumns * sizeof(bool)); |
| |
| /* |
| * Scan the columns, select a unique alias for each one, and store it in |
| * colinfo->colnames and colinfo->new_colnames. The former array has NULL |
| * entries for dropped columns, the latter omits them. Also mark |
| * new_colnames entries as to whether they are new since parse time; this |
| * is the case for entries beyond the length of rte->eref->colnames. |
| */ |
| noldcolumns = list_length(rte->eref->colnames); |
| changed_any = false; |
| j = 0; |
| for (i = 0; i < ncolumns; i++) |
| { |
| char *real_colname = real_colnames[i]; |
| char *colname = colinfo->colnames[i]; |
| |
| /* Skip dropped columns */ |
| if (real_colname == NULL) |
| { |
| Assert(colname == NULL); /* colnames[i] is already NULL */ |
| continue; |
| } |
| |
| /* If alias already assigned, that's what to use */ |
| if (colname == NULL) |
| { |
| /* If user wrote an alias, prefer that over real column name */ |
| if (rte->alias && i < list_length(rte->alias->colnames)) |
| colname = strVal(list_nth(rte->alias->colnames, i)); |
| else |
| colname = real_colname; |
| |
| /* Unique-ify and insert into colinfo */ |
| colname = make_colname_unique(colname, dpns, colinfo); |
| |
| colinfo->colnames[i] = colname; |
| } |
| |
| /* Put names of non-dropped columns in new_colnames[] too */ |
| colinfo->new_colnames[j] = colname; |
| /* And mark them as new or not */ |
| colinfo->is_new_col[j] = (i >= noldcolumns); |
| j++; |
| |
| /* Remember if any assigned aliases differ from "real" name */ |
| if (!changed_any && strcmp(colname, real_colname) != 0) |
| changed_any = true; |
| } |
| |
| /* |
| * Set correct length for new_colnames[] array. (Note: if columns have |
| * been added, colinfo->num_cols includes them, which is not really quite |
| * right but is harmless, since any new columns must be at the end where |
| * they won't affect varattnos of pre-existing columns.) |
| */ |
| colinfo->num_new_cols = j; |
| |
| /* |
| * For a relation RTE, we need only print the alias column names if any |
| * are different from the underlying "real" names. For a function RTE, |
| * always emit a complete column alias list; this is to protect against |
| * possible instability of the default column names (eg, from altering |
| * parameter names). For tablefunc RTEs, we never print aliases, because |
| * the column names are part of the clause itself. For other RTE types, |
| * print if we changed anything OR if there were user-written column |
| * aliases (since the latter would be part of the underlying "reality"). |
| */ |
| if (rte->rtekind == RTE_RELATION) |
| colinfo->printaliases = changed_any; |
| else if (rte->rtekind == RTE_FUNCTION) |
| colinfo->printaliases = true; |
| else if (rte->rtekind == RTE_TABLEFUNC) |
| colinfo->printaliases = false; |
| else if (rte->alias && rte->alias->colnames != NIL) |
| colinfo->printaliases = true; |
| else |
| colinfo->printaliases = changed_any; |
| } |
| |
| /* |
| * set_join_column_names: select column aliases for a join RTE |
| * |
| * Column alias info is saved in *colinfo, which is assumed to be pre-zeroed. |
| * If any colnames entries are already filled in, those override local |
| * choices. Also, names for USING columns were already chosen by |
| * set_using_names(). We further expect that column alias selection has been |
| * completed for both input RTEs. |
| */ |
| static void |
| set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte, |
| deparse_columns *colinfo) |
| { |
| deparse_columns *leftcolinfo; |
| deparse_columns *rightcolinfo; |
| bool changed_any; |
| int noldcolumns; |
| int nnewcolumns; |
| Bitmapset *leftmerged = NULL; |
| Bitmapset *rightmerged = NULL; |
| int i; |
| int j; |
| int ic; |
| int jc; |
| |
| /* Look up the previously-filled-in child deparse_columns structs */ |
| leftcolinfo = deparse_columns_fetch(colinfo->leftrti, dpns); |
| rightcolinfo = deparse_columns_fetch(colinfo->rightrti, dpns); |
| |
| /* |
| * Ensure colinfo->colnames has a slot for each column. (It could be long |
| * enough already, if we pushed down a name for the last column.) Note: |
| * it's possible that one or both inputs now have more columns than there |
| * were when the query was parsed, but we'll deal with that below. We |
| * only need entries in colnames for pre-existing columns. |
| */ |
| noldcolumns = list_length(rte->eref->colnames); |
| expand_colnames_array_to(colinfo, noldcolumns); |
| Assert(colinfo->num_cols == noldcolumns); |
| |
| /* |
| * Scan the join output columns, select an alias for each one, and store |
| * it in colinfo->colnames. If there are USING columns, set_using_names() |
| * already selected their names, so we can start the loop at the first |
| * non-merged column. |
| */ |
| changed_any = false; |
| for (i = list_length(colinfo->usingNames); i < noldcolumns; i++) |
| { |
| char *colname = colinfo->colnames[i]; |
| char *real_colname; |
| |
| /* Join column must refer to at least one input column */ |
| Assert(colinfo->leftattnos[i] != 0 || colinfo->rightattnos[i] != 0); |
| |
| /* Get the child column name */ |
| if (colinfo->leftattnos[i] > 0) |
| real_colname = leftcolinfo->colnames[colinfo->leftattnos[i] - 1]; |
| else if (colinfo->rightattnos[i] > 0) |
| real_colname = rightcolinfo->colnames[colinfo->rightattnos[i] - 1]; |
| else |
| { |
| /* We're joining system columns --- use eref name */ |
| real_colname = strVal(list_nth(rte->eref->colnames, i)); |
| } |
| |
| /* If child col has been dropped, no need to assign a join colname */ |
| if (real_colname == NULL) |
| { |
| colinfo->colnames[i] = NULL; |
| continue; |
| } |
| |
| /* In an unnamed join, just report child column names as-is */ |
| if (rte->alias == NULL) |
| { |
| colinfo->colnames[i] = real_colname; |
| continue; |
| } |
| |
| /* If alias already assigned, that's what to use */ |
| if (colname == NULL) |
| { |
| /* If user wrote an alias, prefer that over real column name */ |
| if (rte->alias && i < list_length(rte->alias->colnames)) |
| colname = strVal(list_nth(rte->alias->colnames, i)); |
| else |
| colname = real_colname; |
| |
| /* Unique-ify and insert into colinfo */ |
| colname = make_colname_unique(colname, dpns, colinfo); |
| |
| colinfo->colnames[i] = colname; |
| } |
| |
| /* Remember if any assigned aliases differ from "real" name */ |
| if (!changed_any && strcmp(colname, real_colname) != 0) |
| changed_any = true; |
| } |
| |
| /* |
| * Calculate number of columns the join would have if it were re-parsed |
| * now, and create storage for the new_colnames and is_new_col arrays. |
| * |
| * Note: colname_is_unique will be consulting new_colnames[] during the |
| * loops below, so its not-yet-filled entries must be zeroes. |
| */ |
| nnewcolumns = leftcolinfo->num_new_cols + rightcolinfo->num_new_cols - |
| list_length(colinfo->usingNames); |
| colinfo->num_new_cols = nnewcolumns; |
| colinfo->new_colnames = (char **) palloc0(nnewcolumns * sizeof(char *)); |
| colinfo->is_new_col = (bool *) palloc0(nnewcolumns * sizeof(bool)); |
| |
| /* |
| * Generating the new_colnames array is a bit tricky since any new columns |
| * added since parse time must be inserted in the right places. This code |
| * must match the parser, which will order a join's columns as merged |
| * columns first (in USING-clause order), then non-merged columns from the |
| * left input (in attnum order), then non-merged columns from the right |
| * input (ditto). If one of the inputs is itself a join, its columns will |
| * be ordered according to the same rule, which means newly-added columns |
| * might not be at the end. We can figure out what's what by consulting |
| * the leftattnos and rightattnos arrays plus the input is_new_col arrays. |
| * |
| * In these loops, i indexes leftattnos/rightattnos (so it's join varattno |
| * less one), j indexes new_colnames/is_new_col, and ic/jc have similar |
| * meanings for the current child RTE. |
| */ |
| |
| /* Handle merged columns; they are first and can't be new */ |
| i = j = 0; |
| while (i < noldcolumns && |
| colinfo->leftattnos[i] != 0 && |
| colinfo->rightattnos[i] != 0) |
| { |
| /* column name is already determined and known unique */ |
| colinfo->new_colnames[j] = colinfo->colnames[i]; |
| colinfo->is_new_col[j] = false; |
| |
| /* build bitmapsets of child attnums of merged columns */ |
| if (colinfo->leftattnos[i] > 0) |
| leftmerged = bms_add_member(leftmerged, colinfo->leftattnos[i]); |
| if (colinfo->rightattnos[i] > 0) |
| rightmerged = bms_add_member(rightmerged, colinfo->rightattnos[i]); |
| |
| i++, j++; |
| } |
| |
| /* Handle non-merged left-child columns */ |
| ic = 0; |
| for (jc = 0; jc < leftcolinfo->num_new_cols; jc++) |
| { |
| char *child_colname = leftcolinfo->new_colnames[jc]; |
| |
| if (!leftcolinfo->is_new_col[jc]) |
| { |
| /* Advance ic to next non-dropped old column of left child */ |
| while (ic < leftcolinfo->num_cols && |
| leftcolinfo->colnames[ic] == NULL) |
| ic++; |
| Assert(ic < leftcolinfo->num_cols); |
| ic++; |
| /* If it is a merged column, we already processed it */ |
| if (bms_is_member(ic, leftmerged)) |
| continue; |
| /* Else, advance i to the corresponding existing join column */ |
| while (i < colinfo->num_cols && |
| colinfo->colnames[i] == NULL) |
| i++; |
| Assert(i < colinfo->num_cols); |
| Assert(ic == colinfo->leftattnos[i]); |
| /* Use the already-assigned name of this column */ |
| colinfo->new_colnames[j] = colinfo->colnames[i]; |
| i++; |
| } |
| else |
| { |
| /* |
| * Unique-ify the new child column name and assign, unless we're |
| * in an unnamed join, in which case just copy |
| */ |
| if (rte->alias != NULL) |
| { |
| colinfo->new_colnames[j] = |
| make_colname_unique(child_colname, dpns, colinfo); |
| if (!changed_any && |
| strcmp(colinfo->new_colnames[j], child_colname) != 0) |
| changed_any = true; |
| } |
| else |
| colinfo->new_colnames[j] = child_colname; |
| } |
| |
| colinfo->is_new_col[j] = leftcolinfo->is_new_col[jc]; |
| j++; |
| } |
| |
| /* Handle non-merged right-child columns in exactly the same way */ |
| ic = 0; |
| for (jc = 0; jc < rightcolinfo->num_new_cols; jc++) |
| { |
| char *child_colname = rightcolinfo->new_colnames[jc]; |
| |
| if (!rightcolinfo->is_new_col[jc]) |
| { |
| /* Advance ic to next non-dropped old column of right child */ |
| while (ic < rightcolinfo->num_cols && |
| rightcolinfo->colnames[ic] == NULL) |
| ic++; |
| Assert(ic < rightcolinfo->num_cols); |
| ic++; |
| /* If it is a merged column, we already processed it */ |
| if (bms_is_member(ic, rightmerged)) |
| continue; |
| /* Else, advance i to the corresponding existing join column */ |
| while (i < colinfo->num_cols && |
| colinfo->colnames[i] == NULL) |
| i++; |
| Assert(i < colinfo->num_cols); |
| Assert(ic == colinfo->rightattnos[i]); |
| /* Use the already-assigned name of this column */ |
| colinfo->new_colnames[j] = colinfo->colnames[i]; |
| i++; |
| } |
| else |
| { |
| /* |
| * Unique-ify the new child column name and assign, unless we're |
| * in an unnamed join, in which case just copy |
| */ |
| if (rte->alias != NULL) |
| { |
| colinfo->new_colnames[j] = |
| make_colname_unique(child_colname, dpns, colinfo); |
| if (!changed_any && |
| strcmp(colinfo->new_colnames[j], child_colname) != 0) |
| changed_any = true; |
| } |
| else |
| colinfo->new_colnames[j] = child_colname; |
| } |
| |
| colinfo->is_new_col[j] = rightcolinfo->is_new_col[jc]; |
| j++; |
| } |
| |
| /* Assert we processed the right number of columns */ |
| #ifdef USE_ASSERT_CHECKING |
| while (i < colinfo->num_cols && colinfo->colnames[i] == NULL) |
| i++; |
| Assert(i == colinfo->num_cols); |
| Assert(j == nnewcolumns); |
| #endif |
| |
| /* |
| * For a named join, print column aliases if we changed any from the child |
| * names. Unnamed joins cannot print aliases. |
| */ |
| if (rte->alias != NULL) |
| colinfo->printaliases = changed_any; |
| else |
| colinfo->printaliases = false; |
| } |
| |
| /* |
| * colname_is_unique: is colname distinct from already-chosen column names? |
| * |
| * dpns is query-wide info, colinfo is for the column's RTE |
| */ |
| static bool |
| colname_is_unique(const char *colname, deparse_namespace *dpns, |
| deparse_columns *colinfo) |
| { |
| int i; |
| ListCell *lc; |
| |
| /* Check against already-assigned column aliases within RTE */ |
| for (i = 0; i < colinfo->num_cols; i++) |
| { |
| char *oldname = colinfo->colnames[i]; |
| |
| if (oldname && strcmp(oldname, colname) == 0) |
| return false; |
| } |
| |
| /* |
| * If we're building a new_colnames array, check that too (this will be |
| * partially but not completely redundant with the previous checks) |
| */ |
| for (i = 0; i < colinfo->num_new_cols; i++) |
| { |
| char *oldname = colinfo->new_colnames[i]; |
| |
| if (oldname && strcmp(oldname, colname) == 0) |
| return false; |
| } |
| |
| /* Also check against USING-column names that must be globally unique */ |
| foreach(lc, dpns->using_names) |
| { |
| char *oldname = (char *) lfirst(lc); |
| |
| if (strcmp(oldname, colname) == 0) |
| return false; |
| } |
| |
| /* Also check against names already assigned for parent-join USING cols */ |
| foreach(lc, colinfo->parentUsing) |
| { |
| char *oldname = (char *) lfirst(lc); |
| |
| if (strcmp(oldname, colname) == 0) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * make_colname_unique: modify colname if necessary to make it unique |
| * |
| * dpns is query-wide info, colinfo is for the column's RTE |
| */ |
| static char * |
| make_colname_unique(char *colname, deparse_namespace *dpns, |
| deparse_columns *colinfo) |
| { |
| /* |
| * If the selected name isn't unique, append digits to make it so. For a |
| * very long input name, we might have to truncate to stay within |
| * NAMEDATALEN. |
| */ |
| if (!colname_is_unique(colname, dpns, colinfo)) |
| { |
| int colnamelen = strlen(colname); |
| char *modname = (char *) palloc(colnamelen + 16); |
| int i = 0; |
| |
| do |
| { |
| i++; |
| for (;;) |
| { |
| memcpy(modname, colname, colnamelen); |
| sprintf(modname + colnamelen, "_%d", i); |
| if (strlen(modname) < NAMEDATALEN) |
| break; |
| /* drop chars from colname to keep all the digits */ |
| colnamelen = pg_mbcliplen(colname, colnamelen, |
| colnamelen - 1); |
| } |
| } while (!colname_is_unique(modname, dpns, colinfo)); |
| colname = modname; |
| } |
| return colname; |
| } |
| |
| /* |
| * expand_colnames_array_to: make colinfo->colnames at least n items long |
| * |
| * Any added array entries are initialized to zero. |
| */ |
| static void |
| expand_colnames_array_to(deparse_columns *colinfo, int n) |
| { |
| if (n > colinfo->num_cols) |
| { |
| if (colinfo->colnames == NULL) |
| colinfo->colnames = (char **) palloc0(n * sizeof(char *)); |
| else |
| { |
| colinfo->colnames = (char **) repalloc(colinfo->colnames, |
| n * sizeof(char *)); |
| memset(colinfo->colnames + colinfo->num_cols, 0, |
| (n - colinfo->num_cols) * sizeof(char *)); |
| } |
| colinfo->num_cols = n; |
| } |
| } |
| |
| /* |
| * identify_join_columns: figure out where columns of a join come from |
| * |
| * Fills the join-specific fields of the colinfo struct, except for |
| * usingNames which is filled later. |
| */ |
| static void |
| identify_join_columns(JoinExpr *j, RangeTblEntry *jrte, |
| deparse_columns *colinfo) |
| { |
| int numjoincols; |
| int jcolno; |
| int rcolno; |
| ListCell *lc; |
| |
| /* Extract left/right child RT indexes */ |
| if (IsA(j->larg, RangeTblRef)) |
| colinfo->leftrti = ((RangeTblRef *) j->larg)->rtindex; |
| else if (IsA(j->larg, JoinExpr)) |
| colinfo->leftrti = ((JoinExpr *) j->larg)->rtindex; |
| else |
| elog(ERROR, "unrecognized node type in jointree: %d", |
| (int) nodeTag(j->larg)); |
| if (IsA(j->rarg, RangeTblRef)) |
| colinfo->rightrti = ((RangeTblRef *) j->rarg)->rtindex; |
| else if (IsA(j->rarg, JoinExpr)) |
| colinfo->rightrti = ((JoinExpr *) j->rarg)->rtindex; |
| else |
| elog(ERROR, "unrecognized node type in jointree: %d", |
| (int) nodeTag(j->rarg)); |
| |
| /* Assert children will be processed earlier than join in second pass */ |
| Assert(colinfo->leftrti < j->rtindex); |
| Assert(colinfo->rightrti < j->rtindex); |
| |
| /* Initialize result arrays with zeroes */ |
| numjoincols = list_length(jrte->joinaliasvars); |
| Assert(numjoincols == list_length(jrte->eref->colnames)); |
| colinfo->leftattnos = (int *) palloc0(numjoincols * sizeof(int)); |
| colinfo->rightattnos = (int *) palloc0(numjoincols * sizeof(int)); |
| |
| /* |
| * Deconstruct RTE's joinleftcols/joinrightcols into desired format. |
| * Recall that the column(s) merged due to USING are the first column(s) |
| * of the join output. We need not do anything special while scanning |
| * joinleftcols, but while scanning joinrightcols we must distinguish |
| * merged from unmerged columns. |
| */ |
| jcolno = 0; |
| foreach(lc, jrte->joinleftcols) |
| { |
| int leftattno = lfirst_int(lc); |
| |
| colinfo->leftattnos[jcolno++] = leftattno; |
| } |
| rcolno = 0; |
| foreach(lc, jrte->joinrightcols) |
| { |
| int rightattno = lfirst_int(lc); |
| |
| if (rcolno < jrte->joinmergedcols) /* merged column? */ |
| colinfo->rightattnos[rcolno] = rightattno; |
| else |
| colinfo->rightattnos[jcolno++] = rightattno; |
| rcolno++; |
| } |
| Assert(jcolno == numjoincols); |
| } |
| |
| /* |
| * get_rtable_name: convenience function to get a previously assigned RTE alias |
| * |
| * The RTE must belong to the topmost namespace level in "context". |
| */ |
| static char * |
| get_rtable_name(int rtindex, deparse_context *context) |
| { |
| deparse_namespace *dpns = (deparse_namespace *) linitial(context->namespaces); |
| |
| Assert(rtindex > 0 && rtindex <= list_length(dpns->rtable_names)); |
| return (char *) list_nth(dpns->rtable_names, rtindex - 1); |
| } |
| |
| /* |
| * set_deparse_plan: set up deparse_namespace to parse subexpressions |
| * of a given Plan node |
| * |
| * This sets the plan, outer_plan, inner_plan, outer_tlist, inner_tlist, |
| * and index_tlist fields. Caller must already have adjusted the ancestors |
| * list if necessary. Note that the rtable, subplans, and ctes fields do |
| * not need to change when shifting attention to different plan nodes in a |
| * single plan tree. |
| */ |
| static void |
| set_deparse_plan(deparse_namespace *dpns, Plan *plan) |
| { |
| dpns->plan = plan; |
| |
| /* |
| * We special-case Append and MergeAppend to pretend that the first child |
| * plan is the OUTER referent; we have to interpret OUTER Vars in their |
| * tlists according to one of the children, and the first one is the most |
| * natural choice. |
| */ |
| if (IsA(plan, Append)) |
| dpns->outer_plan = linitial(((Append *) plan)->appendplans); |
| else if (IsA(plan, Sequence)) |
| /* |
| * A Sequence node returns tuples from the *last* child node only. |
| * The other subplans can even have a different, incompatible tuple |
| * descriptor. A typical case is to have a PartitionSelector node |
| * as the first subplan, and the Dynamic Table Scan as the second |
| * subplan. |
| */ |
| /* |
| * GPDB_13_MERGE_FIXME: |
| * The above comment says "the *last* child node only", but |
| * it assigns the outer plan by its second child subplan. |
| */ |
| dpns->outer_plan = list_nth(((Sequence *) plan)->subplans, 1); |
| else if (IsA(plan, MergeAppend)) |
| dpns->outer_plan = linitial(((MergeAppend *) plan)->mergeplans); |
| else |
| dpns->outer_plan = outerPlan(plan); |
| |
| if (dpns->outer_plan) |
| dpns->outer_tlist = dpns->outer_plan->targetlist; |
| else |
| dpns->outer_tlist = NIL; |
| |
| /* |
| * For a SubqueryScan, pretend the subplan is INNER referent. (We don't |
| * use OUTER because that could someday conflict with the normal meaning.) |
| * Likewise, for a CteScan, pretend the subquery's plan is INNER referent. |
| * For a WorkTableScan, locate the parent RecursiveUnion plan node and use |
| * that as INNER referent. |
| * |
| * For ON CONFLICT .. UPDATE we just need the inner tlist to point to the |
| * excluded expression's tlist. (Similar to the SubqueryScan we don't want |
| * to reuse OUTER, it's used for RETURNING in some modify table cases, |
| * although not INSERT .. CONFLICT). |
| */ |
| if (IsA(plan, SubqueryScan)) |
| dpns->inner_plan = ((SubqueryScan *) plan)->subplan; |
| else if (IsA(plan, CteScan)) |
| dpns->inner_plan = list_nth(dpns->subplans, |
| ((CteScan *) plan)->ctePlanId - 1); |
| else if (IsA(plan, Sequence)) |
| /* |
| * Set the inner_plan to a sequences first child only if it is a |
| * partition selector. This is a specific fix to enable Explain’s of |
| * query plans that have a Partition Selector |
| */ |
| dpns->inner_plan = linitial(((Sequence *) plan)->subplans); |
| else if (IsA(plan, WorkTableScan)) |
| dpns->inner_plan = find_recursive_union(dpns, |
| (WorkTableScan *) plan); |
| else if (IsA(plan, ModifyTable)) |
| dpns->inner_plan = plan; |
| /* Pretend subplan is INNER referent. See logics in function replace_shareinput_targetlists */ |
| else if (IsA(plan, ShareInputScan)) |
| dpns->inner_plan = outerPlan(plan); |
| else |
| dpns->inner_plan = innerPlan(plan); |
| |
| if (IsA(plan, ModifyTable)) |
| dpns->inner_tlist = ((ModifyTable *) plan)->exclRelTlist; |
| else if (dpns->inner_plan) |
| dpns->inner_tlist = dpns->inner_plan->targetlist; |
| else |
| dpns->inner_tlist = NIL; |
| |
| /* Set up referent for INDEX_VAR Vars, if needed */ |
| if (IsA(plan, IndexOnlyScan)) |
| dpns->index_tlist = ((IndexOnlyScan *) plan)->indextlist; |
| else if (IsA(plan, ForeignScan)) |
| dpns->index_tlist = ((ForeignScan *) plan)->fdw_scan_tlist; |
| else if (IsA(plan, CustomScan)) |
| dpns->index_tlist = ((CustomScan *) plan)->custom_scan_tlist; |
| else if (IsA(plan, DynamicIndexOnlyScan)) |
| dpns->index_tlist = ((DynamicIndexOnlyScan *) plan)->indexscan.indextlist; |
| else |
| dpns->index_tlist = NIL; |
| } |
| |
| /* |
| * Locate the ancestor plan node that is the RecursiveUnion generating |
| * the WorkTableScan's work table. We can match on wtParam, since that |
| * should be unique within the plan tree. |
| */ |
| static Plan * |
| find_recursive_union(deparse_namespace *dpns, WorkTableScan *wtscan) |
| { |
| ListCell *lc; |
| |
| foreach(lc, dpns->ancestors) |
| { |
| Plan *ancestor = (Plan *) lfirst(lc); |
| |
| if (IsA(ancestor, RecursiveUnion) && |
| ((RecursiveUnion *) ancestor)->wtParam == wtscan->wtParam) |
| return ancestor; |
| } |
| elog(ERROR, "could not find RecursiveUnion for WorkTableScan with wtParam %d", |
| wtscan->wtParam); |
| return NULL; |
| } |
| |
| /* |
| * push_child_plan: temporarily transfer deparsing attention to a child plan |
| * |
| * When expanding an OUTER_VAR or INNER_VAR reference, we must adjust the |
| * deparse context in case the referenced expression itself uses |
| * OUTER_VAR/INNER_VAR. We modify the top stack entry in-place to avoid |
| * affecting levelsup issues (although in a Plan tree there really shouldn't |
| * be any). |
| * |
| * Caller must provide a local deparse_namespace variable to save the |
| * previous state for pop_child_plan. |
| */ |
| static void |
| push_child_plan(deparse_namespace *dpns, Plan *plan, |
| deparse_namespace *save_dpns) |
| { |
| /* Save state for restoration later */ |
| *save_dpns = *dpns; |
| |
| /* Link current plan node into ancestors list */ |
| dpns->ancestors = lcons(dpns->plan, dpns->ancestors); |
| |
| /* Set attention on selected child */ |
| set_deparse_plan(dpns, plan); |
| } |
| |
| /* |
| * pop_child_plan: undo the effects of push_child_plan |
| */ |
| static void |
| pop_child_plan(deparse_namespace *dpns, deparse_namespace *save_dpns) |
| { |
| List *ancestors; |
| |
| /* Get rid of ancestors list cell added by push_child_plan */ |
| ancestors = list_delete_first(dpns->ancestors); |
| |
| /* Restore fields changed by push_child_plan */ |
| *dpns = *save_dpns; |
| |
| /* Make sure dpns->ancestors is right (may be unnecessary) */ |
| dpns->ancestors = ancestors; |
| } |
| |
| /* |
| * push_ancestor_plan: temporarily transfer deparsing attention to an |
| * ancestor plan |
| * |
| * When expanding a Param reference, we must adjust the deparse context |
| * to match the plan node that contains the expression being printed; |
| * otherwise we'd fail if that expression itself contains a Param or |
| * OUTER_VAR/INNER_VAR/INDEX_VAR variable. |
| * |
| * The target ancestor is conveniently identified by the ListCell holding it |
| * in dpns->ancestors. |
| * |
| * Caller must provide a local deparse_namespace variable to save the |
| * previous state for pop_ancestor_plan. |
| */ |
| static void |
| push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell, |
| deparse_namespace *save_dpns) |
| { |
| Plan *plan = (Plan *) lfirst(ancestor_cell); |
| |
| /* Save state for restoration later */ |
| *save_dpns = *dpns; |
| |
| /* Build a new ancestor list with just this node's ancestors */ |
| dpns->ancestors = |
| list_copy_tail(dpns->ancestors, |
| list_cell_number(dpns->ancestors, ancestor_cell) + 1); |
| |
| /* Set attention on selected ancestor */ |
| set_deparse_plan(dpns, plan); |
| } |
| |
| /* |
| * pop_ancestor_plan: undo the effects of push_ancestor_plan |
| */ |
| static void |
| pop_ancestor_plan(deparse_namespace *dpns, deparse_namespace *save_dpns) |
| { |
| /* Free the ancestor list made in push_ancestor_plan */ |
| list_free(dpns->ancestors); |
| |
| /* Restore fields changed by push_ancestor_plan */ |
| *dpns = *save_dpns; |
| } |
| |
| |
| /* ---------- |
| * make_ruledef - reconstruct the CREATE RULE command |
| * for a given pg_rewrite tuple |
| * ---------- |
| */ |
| static void |
| make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, |
| int prettyFlags) |
| { |
| char *rulename; |
| char ev_type; |
| Oid ev_class; |
| bool is_instead; |
| char *ev_qual; |
| char *ev_action; |
| List *actions; |
| Relation ev_relation; |
| TupleDesc viewResultDesc = NULL; |
| int fno; |
| Datum dat; |
| bool isnull; |
| |
| /* |
| * Get the attribute values from the rules tuple |
| */ |
| fno = SPI_fnumber(rulettc, "rulename"); |
| dat = SPI_getbinval(ruletup, rulettc, fno, &isnull); |
| Assert(!isnull); |
| rulename = NameStr(*(DatumGetName(dat))); |
| |
| fno = SPI_fnumber(rulettc, "ev_type"); |
| dat = SPI_getbinval(ruletup, rulettc, fno, &isnull); |
| Assert(!isnull); |
| ev_type = DatumGetChar(dat); |
| |
| fno = SPI_fnumber(rulettc, "ev_class"); |
| dat = SPI_getbinval(ruletup, rulettc, fno, &isnull); |
| Assert(!isnull); |
| ev_class = DatumGetObjectId(dat); |
| |
| fno = SPI_fnumber(rulettc, "is_instead"); |
| dat = SPI_getbinval(ruletup, rulettc, fno, &isnull); |
| Assert(!isnull); |
| is_instead = DatumGetBool(dat); |
| |
| fno = SPI_fnumber(rulettc, "ev_qual"); |
| ev_qual = SPI_getvalue(ruletup, rulettc, fno); |
| Assert(ev_qual != NULL); |
| |
| fno = SPI_fnumber(rulettc, "ev_action"); |
| ev_action = SPI_getvalue(ruletup, rulettc, fno); |
| Assert(ev_action != NULL); |
| actions = (List *) stringToNode(ev_action); |
| if (actions == NIL) |
| elog(ERROR, "invalid empty ev_action list"); |
| |
| ev_relation = table_open(ev_class, AccessShareLock); |
| |
| /* |
| * Build the rules definition text |
| */ |
| appendStringInfo(buf, "CREATE RULE %s AS", |
| quote_identifier(rulename)); |
| |
| if (prettyFlags & PRETTYFLAG_INDENT) |
| appendStringInfoString(buf, "\n ON "); |
| else |
| appendStringInfoString(buf, " ON "); |
| |
| /* The event the rule is fired for */ |
| switch (ev_type) |
| { |
| case '1': |
| appendStringInfoString(buf, "SELECT"); |
| viewResultDesc = RelationGetDescr(ev_relation); |
| break; |
| |
| case '2': |
| appendStringInfoString(buf, "UPDATE"); |
| break; |
| |
| case '3': |
| appendStringInfoString(buf, "INSERT"); |
| break; |
| |
| case '4': |
| appendStringInfoString(buf, "DELETE"); |
| break; |
| |
| default: |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("rule \"%s\" has unsupported event type %d", |
| rulename, ev_type))); |
| break; |
| } |
| |
| /* The relation the rule is fired on */ |
| appendStringInfo(buf, " TO %s", |
| (prettyFlags & PRETTYFLAG_SCHEMA) ? |
| generate_relation_name(ev_class, NIL) : |
| generate_qualified_relation_name(ev_class)); |
| |
| /* If the rule has an event qualification, add it */ |
| if (strcmp(ev_qual, "<>") != 0) |
| { |
| Node *qual; |
| Query *query; |
| deparse_context context; |
| deparse_namespace dpns; |
| |
| if (prettyFlags & PRETTYFLAG_INDENT) |
| appendStringInfoString(buf, "\n "); |
| appendStringInfoString(buf, " WHERE "); |
| |
| qual = stringToNode(ev_qual); |
| |
| /* |
| * We need to make a context for recognizing any Vars in the qual |
| * (which can only be references to OLD and NEW). Use the rtable of |
| * the first query in the action list for this purpose. |
| */ |
| query = (Query *) linitial(actions); |
| |
| /* |
| * If the action is INSERT...SELECT, OLD/NEW have been pushed down |
| * into the SELECT, and that's what we need to look at. (Ugly kluge |
| * ... try to fix this when we redesign querytrees.) |
| */ |
| query = getInsertSelectQuery(query, NULL); |
| |
| /* Must acquire locks right away; see notes in get_query_def() */ |
| AcquireRewriteLocks(query, false, false); |
| |
| context.buf = buf; |
| context.namespaces = list_make1(&dpns); |
| context.groupClause = NIL; |
| context.windowClause = NIL; |
| context.windowTList = NIL; |
| context.varprefix = (list_length(query->rtable) != 1); |
| context.prettyFlags = prettyFlags; |
| context.wrapColumn = WRAP_COLUMN_DEFAULT; |
| context.indentLevel = PRETTYINDENT_STD; |
| context.special_exprkind = EXPR_KIND_NONE; |
| context.appendparents = NULL; |
| |
| set_deparse_for_query(&dpns, query, NIL); |
| |
| get_rule_expr(qual, &context, false); |
| } |
| |
| appendStringInfoString(buf, " DO "); |
| |
| /* The INSTEAD keyword (if so) */ |
| if (is_instead) |
| appendStringInfoString(buf, "INSTEAD "); |
| |
| /* Finally the rules actions */ |
| if (list_length(actions) > 1) |
| { |
| ListCell *action; |
| Query *query; |
| |
| appendStringInfoChar(buf, '('); |
| foreach(action, actions) |
| { |
| query = (Query *) lfirst(action); |
| get_query_def(query, buf, NIL, viewResultDesc, true, |
| prettyFlags, WRAP_COLUMN_DEFAULT, 0); |
| if (prettyFlags) |
| appendStringInfoString(buf, ";\n"); |
| else |
| appendStringInfoString(buf, "; "); |
| } |
| appendStringInfoString(buf, ");"); |
| } |
| else |
| { |
| Query *query; |
| |
| query = (Query *) linitial(actions); |
| get_query_def(query, buf, NIL, viewResultDesc, true, |
| prettyFlags, WRAP_COLUMN_DEFAULT, 0); |
| appendStringInfoChar(buf, ';'); |
| } |
| |
| table_close(ev_relation, AccessShareLock); |
| } |
| |
| |
| /* ---------- |
| * make_viewdef - reconstruct the SELECT part of a |
| * view rewrite rule |
| * ---------- |
| */ |
| static void |
| make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, |
| int prettyFlags, int wrapColumn) |
| { |
| Query *query; |
| char ev_type; |
| Oid ev_class; |
| bool is_instead; |
| char *ev_qual; |
| char *ev_action; |
| List *actions; |
| Relation ev_relation; |
| int fno; |
| Datum dat; |
| bool isnull; |
| |
| /* |
| * Get the attribute values from the rules tuple |
| */ |
| fno = SPI_fnumber(rulettc, "ev_type"); |
| dat = SPI_getbinval(ruletup, rulettc, fno, &isnull); |
| Assert(!isnull); |
| ev_type = DatumGetChar(dat); |
| |
| fno = SPI_fnumber(rulettc, "ev_class"); |
| dat = SPI_getbinval(ruletup, rulettc, fno, &isnull); |
| Assert(!isnull); |
| ev_class = DatumGetObjectId(dat); |
| |
| fno = SPI_fnumber(rulettc, "is_instead"); |
| dat = SPI_getbinval(ruletup, rulettc, fno, &isnull); |
| Assert(!isnull); |
| is_instead = DatumGetBool(dat); |
| |
| fno = SPI_fnumber(rulettc, "ev_qual"); |
| ev_qual = SPI_getvalue(ruletup, rulettc, fno); |
| Assert(ev_qual != NULL); |
| |
| fno = SPI_fnumber(rulettc, "ev_action"); |
| ev_action = SPI_getvalue(ruletup, rulettc, fno); |
| Assert(ev_action != NULL); |
| actions = (List *) stringToNode(ev_action); |
| |
| if (list_length(actions) != 1) |
| { |
| /* keep output buffer empty and leave */ |
| return; |
| } |
| |
| query = (Query *) linitial(actions); |
| |
| if (ev_type != '1' || !is_instead || |
| strcmp(ev_qual, "<>") != 0 || query->commandType != CMD_SELECT) |
| { |
| /* keep output buffer empty and leave */ |
| return; |
| } |
| |
| /* |
| * MPP-25160: pg_rewrite was scanned using MVCC snapshot, someone |
| * else might drop a view that was visible then. We return nothing |
| * in buf in this case. |
| */ |
| ev_relation = try_relation_open(ev_class, AccessShareLock, false); |
| if (ev_relation == NULL) |
| { |
| return; |
| } |
| |
| get_query_def(query, buf, NIL, RelationGetDescr(ev_relation), true, |
| prettyFlags, wrapColumn, 0); |
| appendStringInfoChar(buf, ';'); |
| |
| table_close(ev_relation, AccessShareLock); |
| } |
| |
| |
| /* ---------- |
| * get_query_def - Parse back one query parsetree |
| * |
| * query: parsetree to be displayed |
| * buf: output text is appended to buf |
| * parentnamespace: list (initially empty) of outer-level deparse_namespace's |
| * resultDesc: if not NULL, the output tuple descriptor for the view |
| * represented by a SELECT query. We use the column names from it |
| * to label SELECT output columns, in preference to names in the query |
| * colNamesVisible: true if the surrounding context cares about the output |
| * column names at all (as, for example, an EXISTS() context does not); |
| * when false, we can suppress dummy column labels such as "?column?" |
| * prettyFlags: bitmask of PRETTYFLAG_XXX options |
| * wrapColumn: maximum line length, or -1 to disable wrapping |
| * startIndent: initial indentation amount |
| * ---------- |
| */ |
| static void |
| get_query_def(Query *query, StringInfo buf, List *parentnamespace, |
| TupleDesc resultDesc, bool colNamesVisible, |
| int prettyFlags, int wrapColumn, int startIndent) |
| { |
| deparse_context context; |
| deparse_namespace dpns; |
| |
| /* Guard against excessively long or deeply-nested queries */ |
| CHECK_FOR_INTERRUPTS(); |
| check_stack_depth(); |
| |
| /* |
| * Before we begin to examine the query, acquire locks on referenced |
| * relations, and fix up deleted columns in JOIN RTEs. This ensures |
| * consistent results. Note we assume it's OK to scribble on the passed |
| * querytree! |
| * |
| * We are only deparsing the query (we are not about to execute it), so we |
| * only need AccessShareLock on the relations it mentions. |
| */ |
| AcquireRewriteLocks(query, false, false); |
| |
| context.buf = buf; |
| context.namespaces = lcons(&dpns, list_copy(parentnamespace)); |
| context.groupClause = NIL; |
| context.windowClause = NIL; |
| context.windowTList = NIL; |
| context.varprefix = (parentnamespace != NIL || |
| list_length(query->rtable) != 1); |
| context.prettyFlags = prettyFlags; |
| context.wrapColumn = wrapColumn; |
| context.indentLevel = startIndent; |
| context.special_exprkind = EXPR_KIND_NONE; |
| context.appendparents = NULL; |
| |
| set_deparse_for_query(&dpns, query, parentnamespace); |
| |
| switch (query->commandType) |
| { |
| case CMD_SELECT: |
| get_select_query_def(query, &context, resultDesc, colNamesVisible); |
| break; |
| |
| case CMD_UPDATE: |
| get_update_query_def(query, &context, colNamesVisible); |
| break; |
| |
| case CMD_INSERT: |
| get_insert_query_def(query, &context, colNamesVisible); |
| break; |
| |
| case CMD_DELETE: |
| get_delete_query_def(query, &context, colNamesVisible); |
| break; |
| |
| case CMD_NOTHING: |
| appendStringInfoString(buf, "NOTHING"); |
| break; |
| |
| case CMD_UTILITY: |
| get_utility_query_def(query, &context); |
| break; |
| |
| default: |
| elog(ERROR, "unrecognized query command type: %d", |
| query->commandType); |
| break; |
| } |
| } |
| |
| /* ---------- |
| * get_values_def - Parse back a VALUES list |
| * ---------- |
| */ |
| static void |
| get_values_def(List *values_lists, deparse_context *context) |
| { |
| StringInfo buf = context->buf; |
| bool first_list = true; |
| ListCell *vtl; |
| |
| appendStringInfoString(buf, "VALUES "); |
| |
| foreach(vtl, values_lists) |
| { |
| List *sublist = (List *) lfirst(vtl); |
| bool first_col = true; |
| ListCell *lc; |
| |
| if (first_list) |
| first_list = false; |
| else |
| appendStringInfoString(buf, ", "); |
| |
| appendStringInfoChar(buf, '('); |
| foreach(lc, sublist) |
| { |
| Node *col = (Node *) lfirst(lc); |
| |
| if (first_col) |
| first_col = false; |
| else |
| appendStringInfoChar(buf, ','); |
| |
| /* |
| * Print the value. Whole-row Vars need special treatment. |
| */ |
| get_rule_expr_toplevel(col, context, false); |
| } |
| appendStringInfoChar(buf, ')'); |
| } |
| } |
| |
| /* ---------- |
| * get_with_clause - Parse back a WITH clause |
| * ---------- |
| */ |
| static void |
| get_with_clause(Query *query, deparse_context *context) |
| { |
| StringInfo buf = context->buf; |
| const char *sep; |
| ListCell *l; |
| |
| if (query->cteList == NIL) |
| return; |
| |
| if (PRETTY_INDENT(context)) |
| { |
| context->indentLevel += PRETTYINDENT_STD; |
| appendStringInfoChar(buf, ' '); |
| } |
| |
| if (query->hasRecursive) |
| sep = "WITH RECURSIVE "; |
| else |
| sep = "WITH "; |
| foreach(l, query->cteList) |
| { |
| CommonTableExpr *cte = (CommonTableExpr *) lfirst(l); |
| |
| appendStringInfoString(buf, sep); |
| appendStringInfoString(buf, quote_identifier(cte->ctename)); |
| if (cte->aliascolnames) |
| { |
| bool first = true; |
| ListCell *col; |
| |
| appendStringInfoChar(buf, '('); |
| foreach(col, cte->aliascolnames) |
| { |
| if (first) |
| first = false; |
| else |
| appendStringInfoString(buf, ", "); |
| appendStringInfoString(buf, |
| quote_identifier(strVal(lfirst(col)))); |
| } |
| appendStringInfoChar(buf, ')'); |
| } |
| appendStringInfoString(buf, " AS "); |
| switch (cte->ctematerialized) |
| { |
| case CTEMaterializeDefault: |
| break; |
| case CTEMaterializeAlways: |
| appendStringInfoString(buf, "MATERIALIZED "); |
| break; |
| case CTEMaterializeNever: |
| appendStringInfoString(buf, "NOT MATERIALIZED "); |
| break; |
| } |
| appendStringInfoChar(buf, '('); |
| if (PRETTY_INDENT(context)) |
| appendContextKeyword(context, "", 0, 0, 0); |
| get_query_def((Query *) cte->ctequery, buf, context->namespaces, NULL, |
| true, |
| context->prettyFlags, context->wrapColumn, |
| context->indentLevel); |
| if (PRETTY_INDENT(context)) |
| appendContextKeyword(context, "", 0, 0, 0); |
| appendStringInfoChar(buf, ')'); |
| |
| if (cte->search_clause) |
| { |
| bool first = true; |
| ListCell *lc; |
| |
| appendStringInfo(buf, " SEARCH %s FIRST BY ", |
| cte->search_clause->search_breadth_first ? "BREADTH" : "DEPTH"); |
| |
| foreach(lc, cte->search_clause->search_col_list) |
| { |
| if (first) |
| first = false; |
| else |
| appendStringInfoString(buf, ", "); |
| appendStringInfoString(buf, |
| quote_identifier(strVal(lfirst(lc)))); |
| } |
| |
| appendStringInfo(buf, " SET %s", quote_identifier(cte->search_clause->search_seq_column)); |
| } |
| |
| if (cte->cycle_clause) |
| { |
| bool first = true; |
| ListCell *lc; |
| |
| appendStringInfoString(buf, " CYCLE "); |
| |
| foreach(lc, cte->cycle_clause->cycle_col_list) |
| { |
| if (first) |
| first = false; |
| else |
| appendStringInfoString(buf, ", "); |
| appendStringInfoString(buf, |
| quote_identifier(strVal(lfirst(lc)))); |
| } |
| |
| appendStringInfo(buf, " SET %s", quote_identifier(cte->cycle_clause->cycle_mark_column)); |
| |
| { |
| Const *cmv = castNode(Const, cte->cycle_clause->cycle_mark_value); |
| Const *cmd = castNode(Const, cte->cycle_clause->cycle_mark_default); |
| |
| if (!(cmv->consttype == BOOLOID && !cmv->constisnull && DatumGetBool(cmv->constvalue) == true && |
| cmd->consttype == BOOLOID && !cmd->constisnull && DatumGetBool(cmd->constvalue) == false)) |
| { |
| appendStringInfoString(buf, " TO "); |
| get_rule_expr(cte->cycle_clause->cycle_mark_value, context, false); |
| appendStringInfoString(buf, " DEFAULT "); |
| get_rule_expr(cte->cycle_clause->cycle_mark_default, context, false); |
| } |
| } |
| |
| appendStringInfo(buf, " USING %s", quote_identifier(cte->cycle_clause->cycle_path_column)); |
| } |
| |
| sep = ", "; |
| } |
| |
| if (PRETTY_INDENT(context)) |
| { |
| context->indentLevel -= PRETTYINDENT_STD; |
| appendContextKeyword(context, "", 0, 0, 0); |
| } |
| else |
| appendStringInfoChar(buf, ' '); |
| } |
| |
| /* ---------- |
| * get_select_query_def - Parse back a SELECT parsetree |
| * ---------- |
| */ |
| static void |
| get_select_query_def(Query *query, deparse_context *context, |
| TupleDesc resultDesc, bool colNamesVisible) |
| { |
| StringInfo buf = context->buf; |
| List *save_windowclause; |
| List *save_windowtlist; |
| List *save_groupclause; |
| bool force_colno; |
| ListCell *l; |
| |
| /* Insert the WITH clause if given */ |
| get_with_clause(query, context); |
| |
| /* Set up context for possible window functions */ |
| save_windowclause = context->windowClause; |
| context->windowClause = query->windowClause; |
| save_windowtlist = context->windowTList; |
| context->windowTList = query->targetList; |
| save_groupclause = context->groupClause; |
| context->groupClause = query->groupClause; |
| |
| /* |
| * If the Query node has a setOperations tree, then it's the top level of |
| * a UNION/INTERSECT/EXCEPT query; only the WITH, ORDER BY and LIMIT |
| * fields are interesting in the top query itself. |
| */ |
| if (query->setOperations) |
| { |
| get_setop_query(query->setOperations, query, context, resultDesc, |
| colNamesVisible); |
| /* ORDER BY clauses must be simple in this case */ |
| force_colno = true; |
| } |
| else |
| { |
| get_basic_select_query(query, context, resultDesc, colNamesVisible); |
| force_colno = false; |
| } |
| |
| /* Add the ORDER BY clause if given */ |
| if (query->sortClause != NIL) |
| { |
| appendContextKeyword(context, " ORDER BY ", |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); |
| get_rule_orderby(query->sortClause, query->targetList, |
| force_colno, context); |
| } |
| |
| /* |
| * Add the LIMIT/OFFSET clauses if given. If non-default options, use the |
| * standard spelling of LIMIT. |
| */ |
| if (query->limitOffset != NULL) |
| { |
| appendContextKeyword(context, " OFFSET ", |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); |
| get_rule_expr(query->limitOffset, context, false); |
| } |
| if (query->limitCount != NULL) |
| { |
| if (query->limitOption == LIMIT_OPTION_WITH_TIES) |
| { |
| appendContextKeyword(context, " FETCH FIRST ", |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); |
| get_rule_expr(query->limitCount, context, false); |
| appendStringInfoString(buf, " ROWS WITH TIES"); |
| } |
| else |
| { |
| appendContextKeyword(context, " LIMIT ", |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); |
| if (IsA(query->limitCount, Const) && |
| ((Const *) query->limitCount)->constisnull) |
| appendStringInfoString(buf, "ALL"); |
| else |
| get_rule_expr(query->limitCount, context, false); |
| } |
| } |
| |
| /* Add the SCATTER BY clause, if given */ |
| if (query->scatterClause) |
| { |
| appendContextKeyword(context, " SCATTER ", |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); |
| |
| /* Distinguish between RANDOMLY and BY <expr-list> */ |
| if (list_length(query->scatterClause) == 1 && |
| linitial(query->scatterClause) == NULL) |
| { |
| appendStringInfo(buf, "RANDOMLY"); |
| } |
| else |
| { |
| ListCell *lc; |
| |
| appendStringInfo(buf, "BY "); |
| foreach(lc, query->scatterClause) |
| { |
| Node *expr = (Node *) lfirst(lc); |
| |
| get_rule_expr(expr, context, false); |
| if (lnext(query->scatterClause, lc)) |
| appendStringInfo(buf, ", "); |
| } |
| } |
| } |
| |
| /* Add FOR [KEY] UPDATE/SHARE clauses if present */ |
| if (query->hasForUpdate) |
| { |
| foreach(l, query->rowMarks) |
| { |
| RowMarkClause *rc = (RowMarkClause *) lfirst(l); |
| |
| /* don't print implicit clauses */ |
| if (rc->pushedDown) |
| continue; |
| |
| switch (rc->strength) |
| { |
| case LCS_NONE: |
| /* we intentionally throw an error for LCS_NONE */ |
| elog(ERROR, "unrecognized LockClauseStrength %d", |
| (int) rc->strength); |
| break; |
| case LCS_FORKEYSHARE: |
| appendContextKeyword(context, " FOR KEY SHARE", |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); |
| break; |
| case LCS_FORSHARE: |
| appendContextKeyword(context, " FOR SHARE", |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); |
| break; |
| case LCS_FORNOKEYUPDATE: |
| appendContextKeyword(context, " FOR NO KEY UPDATE", |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); |
| break; |
| case LCS_FORUPDATE: |
| appendContextKeyword(context, " FOR UPDATE", |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); |
| break; |
| } |
| |
| appendStringInfo(buf, " OF %s", |
| quote_identifier(get_rtable_name(rc->rti, |
| context))); |
| if (rc->waitPolicy == LockWaitError) |
| appendStringInfoString(buf, " NOWAIT"); |
| else if (rc->waitPolicy == LockWaitSkip) |
| appendStringInfoString(buf, " SKIP LOCKED"); |
| } |
| } |
| |
| context->windowClause = save_windowclause; |
| context->windowTList = save_windowtlist; |
| context->groupClause = save_groupclause; |
| } |
| |
| /* |
| * Detect whether query looks like SELECT ... FROM VALUES(), |
| * with no need to rename the output columns of the VALUES RTE. |
| * If so, return the VALUES RTE. Otherwise return NULL. |
| */ |
| static RangeTblEntry * |
| get_simple_values_rte(Query *query, TupleDesc resultDesc) |
| { |
| RangeTblEntry *result = NULL; |
| ListCell *lc; |
| |
| /* |
| * We want to detect a match even if the Query also contains OLD or NEW |
| * rule RTEs. So the idea is to scan the rtable and see if there is only |
| * one inFromCl RTE that is a VALUES RTE. |
| */ |
| foreach(lc, query->rtable) |
| { |
| RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); |
| |
| if (rte->rtekind == RTE_VALUES && rte->inFromCl) |
| { |
| if (result) |
| return NULL; /* multiple VALUES (probably not possible) */ |
| result = rte; |
| } |
| else if (rte->rtekind == RTE_RELATION && !rte->inFromCl) |
| continue; /* ignore rule entries */ |
| else |
| return NULL; /* something else -> not simple VALUES */ |
| } |
| |
| /* |
| * We don't need to check the targetlist in any great detail, because |
| * parser/analyze.c will never generate a "bare" VALUES RTE --- they only |
| * appear inside auto-generated sub-queries with very restricted |
| * structure. However, DefineView might have modified the tlist by |
| * injecting new column aliases, or we might have some other column |
| * aliases forced by a resultDesc. We can only simplify if the RTE's |
| * column names match the names that get_target_list() would select. |
| */ |
| if (result) |
| { |
| ListCell *lcn; |
| int colno; |
| |
| if (list_length(query->targetList) != list_length(result->eref->colnames)) |
| return NULL; /* this probably cannot happen */ |
| colno = 0; |
| forboth(lc, query->targetList, lcn, result->eref->colnames) |
| { |
| TargetEntry *tle = (TargetEntry *) lfirst(lc); |
| char *cname = strVal(lfirst(lcn)); |
| char *colname; |
| |
| if (tle->resjunk) |
| return NULL; /* this probably cannot happen */ |
| |
| /* compute name that get_target_list would use for column */ |
| colno++; |
| if (resultDesc && colno <= resultDesc->natts) |
| colname = NameStr(TupleDescAttr(resultDesc, colno - 1)->attname); |
| else |
| colname = tle->resname; |
| |
| /* does it match the VALUES RTE? */ |
| if (colname == NULL || strcmp(colname, cname) != 0) |
| return NULL; /* column name has been changed */ |
| } |
| } |
| |
| return result; |
| } |
| |
| static void |
| get_basic_select_query(Query *query, deparse_context *context, |
| TupleDesc resultDesc, bool colNamesVisible) |
| { |
| StringInfo buf = context->buf; |
| RangeTblEntry *values_rte; |
| char *sep; |
| ListCell *l; |
| |
| if (PRETTY_INDENT(context)) |
| { |
| context->indentLevel += PRETTYINDENT_STD; |
| appendStringInfoChar(buf, ' '); |
| } |
| |
| /* |
| * If the query looks like SELECT * FROM (VALUES ...), then print just the |
| * VALUES part. This reverses what transformValuesClause() did at parse |
| * time. |
| */ |
| values_rte = get_simple_values_rte(query, resultDesc); |
| if (values_rte) |
| { |
| get_values_def(values_rte->values_lists, context); |
| return; |
| } |
| |
| /* |
| * Build up the query string - first we say SELECT |
| */ |
| if (query->isReturn) |
| appendStringInfoString(buf, "RETURN"); |
| else |
| appendStringInfoString(buf, "SELECT"); |
| |
| /* Add the DISTINCT clause if given */ |
| if (query->distinctClause != NIL) |
| { |
| if (query->hasDistinctOn) |
| { |
| appendStringInfoString(buf, " DISTINCT ON ("); |
| sep = ""; |
| foreach(l, query->distinctClause) |
| { |
| SortGroupClause *srt = (SortGroupClause *) lfirst(l); |
| |
| appendStringInfoString(buf, sep); |
| get_rule_sortgroupclause(srt->tleSortGroupRef, query->targetList, |
| false, context); |
| sep = ", "; |
| } |
| appendStringInfoChar(buf, ')'); |
| } |
| else |
| appendStringInfoString(buf, " DISTINCT"); |
| } |
| |
| /* Then we tell what to select (the targetlist) */ |
| get_target_list(query->targetList, context, resultDesc, colNamesVisible); |
| |
| /* Add the FROM clause if needed */ |
| get_from_clause(query, " FROM ", context); |
| |
| /* Add the WHERE clause if given */ |
| if (query->jointree->quals != NULL) |
| { |
| appendContextKeyword(context, " WHERE ", |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); |
| get_rule_expr(query->jointree->quals, context, false); |
| } |
| |
| /* Add the GROUP BY clause if given */ |
| if (query->groupClause != NULL || query->groupingSets != NULL) |
| { |
| ParseExprKind save_exprkind; |
| |
| appendContextKeyword(context, " GROUP BY ", |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); |
| if (query->groupDistinct) |
| appendStringInfoString(buf, "DISTINCT "); |
| |
| save_exprkind = context->special_exprkind; |
| context->special_exprkind = EXPR_KIND_GROUP_BY; |
| |
| if (query->groupingSets == NIL) |
| { |
| sep = ""; |
| foreach(l, query->groupClause) |
| { |
| SortGroupClause *grp = (SortGroupClause *) lfirst(l); |
| |
| appendStringInfoString(buf, sep); |
| get_rule_sortgroupclause(grp->tleSortGroupRef, query->targetList, |
| false, context); |
| sep = ", "; |
| } |
| } |
| else |
| { |
| sep = ""; |
| foreach(l, query->groupingSets) |
| { |
| GroupingSet *grp = lfirst(l); |
| |
| appendStringInfoString(buf, sep); |
| get_rule_groupingset(grp, query->targetList, true, context); |
| sep = ", "; |
| } |
| } |
| |
| context->special_exprkind = save_exprkind; |
| } |
| |
| /* Add the HAVING clause if given */ |
| if (query->havingQual != NULL) |
| { |
| appendContextKeyword(context, " HAVING ", |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); |
| get_rule_expr(query->havingQual, context, false); |
| } |
| |
| /* The WINDOW clause must be last */ |
| if (query->windowClause) |
| { |
| bool first = true; |
| foreach(l, query->windowClause) |
| { |
| WindowClause *wc = (WindowClause *) lfirst(l); |
| |
| /* unnamed windows will be displayed in the target list */ |
| if (!wc->name) |
| continue; |
| |
| if (first) |
| { |
| appendContextKeyword(context, " WINDOW", |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); |
| first = false; |
| } |
| else |
| appendStringInfoString(buf, ","); |
| |
| appendStringInfo(buf, " %s AS ", quote_identifier(wc->name)); |
| get_rule_windowspec(wc, context->windowTList, context); |
| } |
| } |
| } |
| |
| /* ---------- |
| * get_target_list - Parse back a SELECT target list |
| * |
| * This is also used for RETURNING lists in INSERT/UPDATE/DELETE. |
| * |
| * resultDesc and colNamesVisible are as for get_query_def() |
| * ---------- |
| */ |
| static void |
| get_target_list(List *targetList, deparse_context *context, |
| TupleDesc resultDesc, bool colNamesVisible) |
| { |
| StringInfo buf = context->buf; |
| StringInfoData targetbuf; |
| bool last_was_multiline = false; |
| char *sep; |
| int colno; |
| ListCell *l; |
| |
| /* we use targetbuf to hold each TLE's text temporarily */ |
| initStringInfo(&targetbuf); |
| |
| sep = " "; |
| colno = 0; |
| foreach(l, targetList) |
| { |
| TargetEntry *tle = (TargetEntry *) lfirst(l); |
| char *colname; |
| char *attname; |
| |
| if (tle->resjunk) |
| continue; /* ignore junk entries */ |
| |
| appendStringInfoString(buf, sep); |
| sep = ", "; |
| colno++; |
| |
| /* |
| * Put the new field text into targetbuf so we can decide after we've |
| * got it whether or not it needs to go on a new line. |
| */ |
| resetStringInfo(&targetbuf); |
| context->buf = &targetbuf; |
| |
| /* |
| * We special-case Var nodes rather than using get_rule_expr. This is |
| * needed because get_rule_expr will display a whole-row Var as |
| * "foo.*", which is the preferred notation in most contexts, but at |
| * the top level of a SELECT list it's not right (the parser will |
| * expand that notation into multiple columns, yielding behavior |
| * different from a whole-row Var). We need to call get_variable |
| * directly so that we can tell it to do the right thing, and so that |
| * we can get the attribute name which is the default AS label. |
| */ |
| if (tle->expr && (IsA(tle->expr, Var))) |
| { |
| attname = get_variable((Var *) tle->expr, 0, true, context); |
| } |
| else |
| { |
| get_rule_expr((Node *) tle->expr, context, true); |
| |
| /* |
| * When colNamesVisible is true, we should always show the |
| * assigned column name explicitly. Otherwise, show it only if |
| * it's not FigureColname's fallback. |
| */ |
| attname = colNamesVisible ? NULL : "?column?"; |
| } |
| |
| /* |
| * Figure out what the result column should be called. In the context |
| * of a view, use the view's tuple descriptor (so as to pick up the |
| * effects of any column RENAME that's been done on the view). |
| * Otherwise, just use what we can find in the TLE. |
| */ |
| if (resultDesc && colno <= resultDesc->natts) |
| colname = NameStr(TupleDescAttr(resultDesc, colno - 1)->attname); |
| else |
| colname = tle->resname; |
| |
| /* Show AS unless the column's name is correct as-is */ |
| if (colname) /* resname could be NULL */ |
| { |
| if (attname == NULL || strcmp(attname, colname) != 0) |
| appendStringInfo(&targetbuf, " AS %s", quote_identifier(colname)); |
| } |
| |
| /* Restore context's output buffer */ |
| context->buf = buf; |
| |
| /* Consider line-wrapping if enabled */ |
| if (PRETTY_INDENT(context) && context->wrapColumn >= 0) |
| { |
| int leading_nl_pos; |
| |
| /* Does the new field start with a new line? */ |
| if (targetbuf.len > 0 && targetbuf.data[0] == '\n') |
| leading_nl_pos = 0; |
| else |
| leading_nl_pos = -1; |
| |
| /* If so, we shouldn't add anything */ |
| if (leading_nl_pos >= 0) |
| { |
| /* instead, remove any trailing spaces currently in buf */ |
| removeStringInfoSpaces(buf); |
| } |
| else |
| { |
| char *trailing_nl; |
| |
| /* Locate the start of the current line in the output buffer */ |
| trailing_nl = strrchr(buf->data, '\n'); |
| if (trailing_nl == NULL) |
| trailing_nl = buf->data; |
| else |
| trailing_nl++; |
| |
| /* |
| * Add a newline, plus some indentation, if the new field is |
| * not the first and either the new field would cause an |
| * overflow or the last field used more than one line. |
| */ |
| if (colno > 1 && |
| ((strlen(trailing_nl) + targetbuf.len > context->wrapColumn) || |
| last_was_multiline)) |
| appendContextKeyword(context, "", -PRETTYINDENT_STD, |
| PRETTYINDENT_STD, PRETTYINDENT_VAR); |
| } |
| |
| /* Remember this field's multiline status for next iteration */ |
| last_was_multiline = |
| (strchr(targetbuf.data + leading_nl_pos + 1, '\n') != NULL); |
| } |
| |
| /* Add the new field */ |
| appendBinaryStringInfo(buf, targetbuf.data, targetbuf.len); |
| } |
| |
| /* clean up */ |
| pfree(targetbuf.data); |
| } |
| |
| static void |
| get_setop_query(Node *setOp, Query *query, deparse_context *context, |
| TupleDesc resultDesc, bool colNamesVisible) |
| { |
| StringInfo buf = context->buf; |
| bool need_paren; |
| |
| /* Guard against excessively long or deeply-nested queries */ |
| CHECK_FOR_INTERRUPTS(); |
| check_stack_depth(); |
| |
| if (IsA(setOp, RangeTblRef)) |
| { |
| RangeTblRef *rtr = (RangeTblRef *) setOp; |
| RangeTblEntry *rte = rt_fetch(rtr->rtindex, query->rtable); |
| Query *subquery = rte->subquery; |
| |
| Assert(subquery != NULL); |
| Assert(subquery->setOperations == NULL); |
| /* Need parens if WITH, ORDER BY, FOR UPDATE, or LIMIT; see gram.y */ |
| need_paren = (subquery->cteList || |
| subquery->sortClause || |
| subquery->rowMarks || |
| subquery->limitOffset || |
| subquery->limitCount); |
| if (need_paren) |
| appendStringInfoChar(buf, '('); |
| get_query_def(subquery, buf, context->namespaces, resultDesc, |
| colNamesVisible, |
| context->prettyFlags, context->wrapColumn, |
| context->indentLevel); |
| if (need_paren) |
| appendStringInfoChar(buf, ')'); |
| } |
| else if (IsA(setOp, SetOperationStmt)) |
| { |
| SetOperationStmt *op = (SetOperationStmt *) setOp; |
| int subindent; |
| |
| /* |
| * We force parens when nesting two SetOperationStmts, except when the |
| * lefthand input is another setop of the same kind. Syntactically, |
| * we could omit parens in rather more cases, but it seems best to use |
| * parens to flag cases where the setop operator changes. If we use |
| * parens, we also increase the indentation level for the child query. |
| * |
| * There are some cases in which parens are needed around a leaf query |
| * too, but those are more easily handled at the next level down (see |
| * code above). |
| */ |
| if (IsA(op->larg, SetOperationStmt)) |
| { |
| SetOperationStmt *lop = (SetOperationStmt *) op->larg; |
| |
| if (op->op == lop->op && op->all == lop->all) |
| need_paren = false; |
| else |
| need_paren = true; |
| } |
| else |
| need_paren = false; |
| |
| if (need_paren) |
| { |
| appendStringInfoChar(buf, '('); |
| subindent = PRETTYINDENT_STD; |
| appendContextKeyword(context, "", subindent, 0, 0); |
| } |
| else |
| subindent = 0; |
| |
| get_setop_query(op->larg, query, context, resultDesc, colNamesVisible); |
| |
| if (need_paren) |
| appendContextKeyword(context, ") ", -subindent, 0, 0); |
| else if (PRETTY_INDENT(context)) |
| appendContextKeyword(context, "", -subindent, 0, 0); |
| else |
| appendStringInfoChar(buf, ' '); |
| |
| switch (op->op) |
| { |
| case SETOP_UNION: |
| appendStringInfoString(buf, "UNION "); |
| break; |
| case SETOP_INTERSECT: |
| appendStringInfoString(buf, "INTERSECT "); |
| break; |
| case SETOP_EXCEPT: |
| appendStringInfoString(buf, "EXCEPT "); |
| break; |
| default: |
| elog(ERROR, "unrecognized set op: %d", |
| (int) op->op); |
| } |
| if (op->all) |
| appendStringInfoString(buf, "ALL "); |
| |
| /* Always parenthesize if RHS is another setop */ |
| need_paren = IsA(op->rarg, SetOperationStmt); |
| |
| /* |
| * The indentation code here is deliberately a bit different from that |
| * for the lefthand input, because we want the line breaks in |
| * different places. |
| */ |
| if (need_paren) |
| { |
| appendStringInfoChar(buf, '('); |
| subindent = PRETTYINDENT_STD; |
| } |
| else |
| subindent = 0; |
| appendContextKeyword(context, "", subindent, 0, 0); |
| |
| get_setop_query(op->rarg, query, context, resultDesc, false); |
| |
| if (PRETTY_INDENT(context)) |
| context->indentLevel -= subindent; |
| if (need_paren) |
| appendContextKeyword(context, ")", 0, 0, 0); |
| } |
| else |
| { |
| elog(ERROR, "unrecognized node type: %d", |
| (int) nodeTag(setOp)); |
| } |
| } |
| |
| /* |
| * Display a sort/group clause. |
| * |
| * Also returns the expression tree, so caller need not find it again. |
| */ |
| static Node * |
| get_rule_sortgroupclause(Index ref, List *tlist, bool force_colno, |
| deparse_context *context) |
| { |
| StringInfo buf = context->buf; |
| TargetEntry *tle; |
| Node *expr; |
| |
| tle = get_sortgroupref_tle(ref, tlist); |
| expr = (Node *) tle->expr; |
| |
| /* |
| * Use column-number form if requested by caller. Otherwise, if |
| * expression is a constant, force it to be dumped with an explicit cast |
| * as decoration --- this is because a simple integer constant is |
| * ambiguous (and will be misinterpreted by findTargetlistEntry()) if we |
| * dump it without any decoration. If it's anything more complex than a |
| * simple Var, then force extra parens around it, to ensure it can't be |
| * misinterpreted as a cube() or rollup() construct. |
| */ |
| if (force_colno) |
| { |
| Assert(!tle->resjunk); |
| appendStringInfo(buf, "%d", tle->resno); |
| } |
| else if (expr && IsA(expr, Const)) |
| get_const_expr((Const *) expr, context, 1); |
| else if (!expr || IsA(expr, Var)) |
| get_rule_expr(expr, context, true); |
| else if (expr && IsA(expr, OpExpr)) |
| { |
| OpExpr *opexpr = (OpExpr *)expr; |
| List *args = opexpr->args; |
| bool need_paren = PRETTY_PAREN(context); |
| |
| /* |
| * Const-folder the expression(opexpr plus const) to negative const, |
| * mainly there are two operators we need to worried about, namely |
| * in4um(Int4NegOperator) and numeric_uminus(NumericNegOperator), |
| * and get_const_expr() is responsible for end up getting labeled |
| * with a typecast. |
| */ |
| if (list_length(args) == 1) |
| { |
| Node *arg = (Node *) linitial(args); |
| Oid opno = opexpr->opno; |
| |
| if ((opno == Int4NegOperator || opno == NumericNegOperator) && |
| IsA(arg, Const)) |
| expr = (Node *) expression_planner((Expr *)expr); |
| } |
| |
| if (need_paren) |
| appendStringInfoChar(context->buf, '('); |
| get_rule_expr(expr, context, true); |
| if (need_paren) |
| appendStringInfoChar(context->buf, ')'); |
| } |
| else |
| { |
| /* |
| * We must force parens for function-like expressions even if |
| * PRETTY_PAREN is off, since those are the ones in danger of |
| * misparsing. For other expressions we need to force them only if |
| * PRETTY_PAREN is on, since otherwise the expression will output them |
| * itself. (We can't skip the parens.) |
| */ |
| bool need_paren = (PRETTY_PAREN(context) |
| || IsA(expr, FuncExpr) |
| || IsA(expr, Aggref) |
| || IsA(expr, WindowFunc)); |
| |
| if (need_paren) |
| appendStringInfoChar(context->buf, '('); |
| get_rule_expr(expr, context, true); |
| if (need_paren) |
| appendStringInfoChar(context->buf, ')'); |
| } |
| |
| return expr; |
| } |
| |
| /* |
| * Display a GroupingSet |
| */ |
| static void |
| get_rule_groupingset(GroupingSet *gset, List *targetlist, |
| bool omit_parens, deparse_context *context) |
| { |
| ListCell *l; |
| StringInfo buf = context->buf; |
| bool omit_child_parens = true; |
| char *sep = ""; |
| |
| switch (gset->kind) |
| { |
| case GROUPING_SET_EMPTY: |
| appendStringInfoString(buf, "()"); |
| return; |
| |
| case GROUPING_SET_SIMPLE: |
| { |
| if (!omit_parens || list_length(gset->content) != 1) |
| appendStringInfoChar(buf, '('); |
| |
| foreach(l, gset->content) |
| { |
| Index ref = lfirst_int(l); |
| |
| appendStringInfoString(buf, sep); |
| get_rule_sortgroupclause(ref, targetlist, |
| false, context); |
| sep = ", "; |
| } |
| |
| if (!omit_parens || list_length(gset->content) != 1) |
| appendStringInfoChar(buf, ')'); |
| } |
| return; |
| |
| case GROUPING_SET_ROLLUP: |
| appendStringInfoString(buf, "ROLLUP("); |
| break; |
| case GROUPING_SET_CUBE: |
| appendStringInfoString(buf, "CUBE("); |
| break; |
| case GROUPING_SET_SETS: |
| appendStringInfoString(buf, "GROUPING SETS ("); |
| omit_child_parens = false; |
| break; |
| } |
| |
| foreach(l, gset->content) |
| { |
| appendStringInfoString(buf, sep); |
| get_rule_groupingset(lfirst(l), targetlist, omit_child_parens, context); |
| sep = ", "; |
| } |
| |
| appendStringInfoChar(buf, ')'); |
| } |
| |
| /* |
| * Display an ORDER BY list. |
| */ |
| static void |
| get_rule_orderby(List *orderList, List *targetList, |
| bool force_colno, deparse_context *context) |
| { |
| StringInfo buf = context->buf; |
| const char *sep; |
| ListCell *l; |
| |
| sep = ""; |
| foreach(l, orderList) |
| { |
| SortGroupClause *srt = (SortGroupClause *) lfirst(l); |
| Node *sortexpr; |
| Oid sortcoltype; |
| TypeCacheEntry *typentry; |
| |
| appendStringInfoString(buf, sep); |
| sortexpr = get_rule_sortgroupclause(srt->tleSortGroupRef, targetList, |
| force_colno, context); |
| sortcoltype = exprType(sortexpr); |
| /* See whether operator is default < or > for datatype */ |
| typentry = lookup_type_cache(sortcoltype, |
| TYPECACHE_LT_OPR | TYPECACHE_GT_OPR); |
| if (srt->sortop == typentry->lt_opr) |
| { |
| /* ASC is default, so emit nothing for it */ |
| if (srt->nulls_first) |
| appendStringInfoString(buf, " NULLS FIRST"); |
| } |
| else if (srt->sortop == typentry->gt_opr) |
| { |
| appendStringInfoString(buf, " DESC"); |
| /* DESC defaults to NULLS FIRST */ |
| if (!srt->nulls_first) |
| appendStringInfoString(buf, " NULLS LAST"); |
| } |
| else |
| { |
| appendStringInfo(buf, " USING %s", |
| generate_operator_name(srt->sortop, |
| sortcoltype, |
| sortcoltype)); |
| /* be specific to eliminate ambiguity */ |
| if (srt->nulls_first) |
| appendStringInfoString(buf, " NULLS FIRST"); |
| else |
| appendStringInfoString(buf, " NULLS LAST"); |
| } |
| sep = ", "; |
| } |
| } |
| |
| /* |
| * Display a window definition |
| */ |
| static void |
| get_rule_windowspec(WindowClause *wc, List *targetList, |
| deparse_context *context) |
| { |
| StringInfo buf = context->buf; |
| bool needspace = false; |
| const char *sep; |
| ListCell *l; |
| |
| appendStringInfoChar(buf, '('); |
| if (wc->refname) |
| { |
| appendStringInfoString(buf, quote_identifier(wc->refname)); |
| needspace = true; |
| } |
| /* partition clauses are always inherited, so only print if no refname */ |
| if (wc->partitionClause && !wc->refname) |
| { |
| if (needspace) |
| appendStringInfoChar(buf, ' '); |
| appendStringInfoString(buf, "PARTITION BY "); |
| sep = ""; |
| foreach(l, wc->partitionClause) |
| { |
| SortGroupClause *grp = (SortGroupClause *) lfirst(l); |
| |
| appendStringInfoString(buf, sep); |
| get_rule_sortgroupclause(grp->tleSortGroupRef, targetList, |
| false, context); |
| sep = ", "; |
| } |
| needspace = true; |
| } |
| /* print ordering clause only if not inherited */ |
| if (wc->orderClause && !wc->copiedOrder) |
| { |
| if (needspace) |
| appendStringInfoChar(buf, ' '); |
| appendStringInfoString(buf, "ORDER BY "); |
| get_rule_orderby(wc->orderClause, targetList, false, context); |
| needspace = true; |
| } |
| /* framing clause is never inherited, so print unless it's default */ |
| if (wc->frameOptions & FRAMEOPTION_NONDEFAULT) |
| { |
| if (needspace) |
| appendStringInfoChar(buf, ' '); |
| if (wc->frameOptions & FRAMEOPTION_RANGE) |
| appendStringInfoString(buf, "RANGE "); |
| else if (wc->frameOptions & FRAMEOPTION_ROWS) |
| appendStringInfoString(buf, "ROWS "); |
| else if (wc->frameOptions & FRAMEOPTION_GROUPS) |
| appendStringInfoString(buf, "GROUPS "); |
| else |
| Assert(false); |
| if (wc->frameOptions & FRAMEOPTION_BETWEEN) |
| appendStringInfoString(buf, "BETWEEN "); |
| if (wc->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING) |
| appendStringInfoString(buf, "UNBOUNDED PRECEDING "); |
| else if (wc->frameOptions & FRAMEOPTION_START_CURRENT_ROW) |
| appendStringInfoString(buf, "CURRENT ROW "); |
| else if (wc->frameOptions & FRAMEOPTION_START_OFFSET) |
| { |
| get_rule_expr(wc->startOffset, context, false); |
| if (wc->frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING) |
| appendStringInfoString(buf, " PRECEDING "); |
| else if (wc->frameOptions & FRAMEOPTION_START_OFFSET_FOLLOWING) |
| appendStringInfoString(buf, " FOLLOWING "); |
| else |
| Assert(false); |
| } |
| else |
| Assert(false); |
| if (wc->frameOptions & FRAMEOPTION_BETWEEN) |
| { |
| appendStringInfoString(buf, "AND "); |
| if (wc->frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING) |
| appendStringInfoString(buf, "UNBOUNDED FOLLOWING "); |
| else if (wc->frameOptions & FRAMEOPTION_END_CURRENT_ROW) |
| appendStringInfoString(buf, "CURRENT ROW "); |
| else if (wc->frameOptions & FRAMEOPTION_END_OFFSET) |
| { |
| get_rule_expr(wc->endOffset, context, false); |
| if (wc->frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING) |
| appendStringInfoString(buf, " PRECEDING "); |
| else if (wc->frameOptions & FRAMEOPTION_END_OFFSET_FOLLOWING) |
| appendStringInfoString(buf, " FOLLOWING "); |
| else |
| Assert(false); |
| } |
| else |
| Assert(false); |
| } |
| if (wc->frameOptions & FRAMEOPTION_EXCLUDE_CURRENT_ROW) |
| appendStringInfoString(buf, "EXCLUDE CURRENT ROW "); |
| else if (wc->frameOptions & FRAMEOPTION_EXCLUDE_GROUP) |
| appendStringInfoString(buf, "EXCLUDE GROUP "); |
| else if (wc->frameOptions & FRAMEOPTION_EXCLUDE_TIES) |
| appendStringInfoString(buf, "EXCLUDE TIES "); |
| /* we will now have a trailing space; remove it */ |
| buf->len--; |
| } |
| appendStringInfoChar(buf, ')'); |
| } |
| |
| /* ---------- |
| * get_insert_query_def - Parse back an INSERT parsetree |
| * ---------- |
| */ |
| static void |
| get_insert_query_def(Query *query, deparse_context *context, |
| bool colNamesVisible) |
| { |
| StringInfo buf = context->buf; |
| RangeTblEntry *select_rte = NULL; |
| RangeTblEntry *values_rte = NULL; |
| RangeTblEntry *rte; |
| char *sep; |
| ListCell *l; |
| List *strippedexprs; |
| |
| /* Insert the WITH clause if given */ |
| get_with_clause(query, context); |
| |
| /* |
| * If it's an INSERT ... SELECT or multi-row VALUES, there will be a |
| * single RTE for the SELECT or VALUES. Plain VALUES has neither. |
| */ |
| foreach(l, query->rtable) |
| { |
| rte = (RangeTblEntry *) lfirst(l); |
| |
| if (rte->rtekind == RTE_SUBQUERY) |
| { |
| if (select_rte) |
| elog(ERROR, "too many subquery RTEs in INSERT"); |
| select_rte = rte; |
| } |
| |
| if (rte->rtekind == RTE_VALUES) |
| { |
| if (values_rte) |
| elog(ERROR, "too many values RTEs in INSERT"); |
| values_rte = rte; |
| } |
| } |
| if (select_rte && values_rte) |
| elog(ERROR, "both subquery and values RTEs in INSERT"); |
| |
| /* |
| * Start the query with INSERT INTO relname |
| */ |
| rte = rt_fetch(query->resultRelation, query->rtable); |
| Assert(rte->rtekind == RTE_RELATION); |
| |
| if (PRETTY_INDENT(context)) |
| { |
| context->indentLevel += PRETTYINDENT_STD; |
| appendStringInfoChar(buf, ' '); |
| } |
| appendStringInfo(buf, "INSERT INTO %s ", |
| generate_relation_name(rte->relid, NIL)); |
| /* INSERT requires AS keyword for target alias */ |
| if (rte->alias != NULL) |
| appendStringInfo(buf, "AS %s ", |
| quote_identifier(rte->alias->aliasname)); |
| |
| /* |
| * Add the insert-column-names list. Any indirection decoration needed on |
| * the column names can be inferred from the top targetlist. |
| */ |
| strippedexprs = NIL; |
| sep = ""; |
| if (query->targetList) |
| appendStringInfoChar(buf, '('); |
| foreach(l, query->targetList) |
| { |
| TargetEntry *tle = (TargetEntry *) lfirst(l); |
| |
| if (tle->resjunk) |
| continue; /* ignore junk entries */ |
| |
| appendStringInfoString(buf, sep); |
| sep = ", "; |
| |
| /* |
| * Put out name of target column; look in the catalogs, not at |
| * tle->resname, since resname will fail to track RENAME. |
| */ |
| appendStringInfoString(buf, |
| quote_identifier(get_attname(rte->relid, |
| tle->resno, |
| false))); |
| |
| /* |
| * Print any indirection needed (subfields or subscripts), and strip |
| * off the top-level nodes representing the indirection assignments. |
| * Add the stripped expressions to strippedexprs. (If it's a |
| * single-VALUES statement, the stripped expressions are the VALUES to |
| * print below. Otherwise they're just Vars and not really |
| * interesting.) |
| */ |
| strippedexprs = lappend(strippedexprs, |
| processIndirection((Node *) tle->expr, |
| context)); |
| } |
| if (query->targetList) |
| appendStringInfoString(buf, ") "); |
| |
| if (query->override) |
| { |
| if (query->override == OVERRIDING_SYSTEM_VALUE) |
| appendStringInfoString(buf, "OVERRIDING SYSTEM VALUE "); |
| else if (query->override == OVERRIDING_USER_VALUE) |
| appendStringInfoString(buf, "OVERRIDING USER VALUE "); |
| } |
| |
| if (select_rte) |
| { |
| /* Add the SELECT */ |
| get_query_def(select_rte->subquery, buf, context->namespaces, NULL, |
| false, |
| context->prettyFlags, context->wrapColumn, |
| context->indentLevel); |
| } |
| else if (values_rte) |
| { |
| /* Add the multi-VALUES expression lists */ |
| get_values_def(values_rte->values_lists, context); |
| } |
| else if (strippedexprs) |
| { |
| /* Add the single-VALUES expression list */ |
| appendContextKeyword(context, "VALUES (", |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 2); |
| get_rule_list_toplevel(strippedexprs, context, false); |
| appendStringInfoChar(buf, ')'); |
| } |
| else |
| { |
| /* No expressions, so it must be DEFAULT VALUES */ |
| appendStringInfoString(buf, "DEFAULT VALUES"); |
| } |
| |
| /* Add ON CONFLICT if present */ |
| if (query->onConflict) |
| { |
| OnConflictExpr *confl = query->onConflict; |
| |
| appendStringInfoString(buf, " ON CONFLICT"); |
| |
| if (confl->arbiterElems) |
| { |
| /* Add the single-VALUES expression list */ |
| appendStringInfoChar(buf, '('); |
| get_rule_expr((Node *) confl->arbiterElems, context, false); |
| appendStringInfoChar(buf, ')'); |
| |
| /* Add a WHERE clause (for partial indexes) if given */ |
| if (confl->arbiterWhere != NULL) |
| { |
| bool save_varprefix; |
| |
| /* |
| * Force non-prefixing of Vars, since parser assumes that they |
| * belong to target relation. WHERE clause does not use |
| * InferenceElem, so this is separately required. |
| */ |
| save_varprefix = context->varprefix; |
| context->varprefix = false; |
| |
| appendContextKeyword(context, " WHERE ", |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); |
| get_rule_expr(confl->arbiterWhere, context, false); |
| |
| context->varprefix = save_varprefix; |
| } |
| } |
| else if (OidIsValid(confl->constraint)) |
| { |
| char *constraint = get_constraint_name(confl->constraint); |
| |
| if (!constraint) |
| elog(ERROR, "cache lookup failed for constraint %u", |
| confl->constraint); |
| appendStringInfo(buf, " ON CONSTRAINT %s", |
| quote_identifier(constraint)); |
| } |
| |
| if (confl->action == ONCONFLICT_NOTHING) |
| { |
| appendStringInfoString(buf, " DO NOTHING"); |
| } |
| else |
| { |
| appendStringInfoString(buf, " DO UPDATE SET "); |
| /* Deparse targetlist */ |
| get_update_query_targetlist_def(query, confl->onConflictSet, |
| context, rte); |
| |
| /* Add a WHERE clause if given */ |
| if (confl->onConflictWhere != NULL) |
| { |
| appendContextKeyword(context, " WHERE ", |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); |
| get_rule_expr(confl->onConflictWhere, context, false); |
| } |
| } |
| } |
| |
| /* Add RETURNING if present */ |
| if (query->returningList) |
| { |
| appendContextKeyword(context, " RETURNING", |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); |
| get_target_list(query->returningList, context, NULL, colNamesVisible); |
| } |
| } |
| |
| |
| /* ---------- |
| * get_update_query_def - Parse back an UPDATE parsetree |
| * ---------- |
| */ |
| static void |
| get_update_query_def(Query *query, deparse_context *context, |
| bool colNamesVisible) |
| { |
| StringInfo buf = context->buf; |
| RangeTblEntry *rte; |
| |
| /* Insert the WITH clause if given */ |
| get_with_clause(query, context); |
| |
| /* |
| * Start the query with UPDATE relname SET |
| */ |
| rte = rt_fetch(query->resultRelation, query->rtable); |
| Assert(rte->rtekind == RTE_RELATION); |
| if (PRETTY_INDENT(context)) |
| { |
| appendStringInfoChar(buf, ' '); |
| context->indentLevel += PRETTYINDENT_STD; |
| } |
| appendStringInfo(buf, "UPDATE %s%s", |
| only_marker(rte), |
| generate_relation_name(rte->relid, NIL)); |
| if (rte->alias != NULL) |
| appendStringInfo(buf, " %s", |
| quote_identifier(rte->alias->aliasname)); |
| appendStringInfoString(buf, " SET "); |
| |
| /* Deparse targetlist */ |
| get_update_query_targetlist_def(query, query->targetList, context, rte); |
| |
| /* Add the FROM clause if needed */ |
| get_from_clause(query, " FROM ", context); |
| |
| /* Add a WHERE clause if given */ |
| if (query->jointree->quals != NULL) |
| { |
| appendContextKeyword(context, " WHERE ", |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); |
| get_rule_expr(query->jointree->quals, context, false); |
| } |
| |
| /* Add RETURNING if present */ |
| if (query->returningList) |
| { |
| appendContextKeyword(context, " RETURNING", |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); |
| get_target_list(query->returningList, context, NULL, colNamesVisible); |
| } |
| } |
| |
| |
| /* ---------- |
| * get_update_query_targetlist_def - Parse back an UPDATE targetlist |
| * ---------- |
| */ |
| static void |
| get_update_query_targetlist_def(Query *query, List *targetList, |
| deparse_context *context, RangeTblEntry *rte) |
| { |
| StringInfo buf = context->buf; |
| ListCell *l; |
| ListCell *next_ma_cell; |
| int remaining_ma_columns; |
| const char *sep; |
| SubLink *cur_ma_sublink; |
| List *ma_sublinks; |
| |
| /* |
| * Prepare to deal with MULTIEXPR assignments: collect the source SubLinks |
| * into a list. We expect them to appear, in ID order, in resjunk tlist |
| * entries. |
| */ |
| ma_sublinks = NIL; |
| if (query->hasSubLinks) /* else there can't be any */ |
| { |
| foreach(l, targetList) |
| { |
| TargetEntry *tle = (TargetEntry *) lfirst(l); |
| |
| if (tle->resjunk && IsA(tle->expr, SubLink)) |
| { |
| SubLink *sl = (SubLink *) tle->expr; |
| |
| if (sl->subLinkType == MULTIEXPR_SUBLINK) |
| { |
| ma_sublinks = lappend(ma_sublinks, sl); |
| Assert(sl->subLinkId == list_length(ma_sublinks)); |
| } |
| } |
| } |
| } |
| next_ma_cell = list_head(ma_sublinks); |
| cur_ma_sublink = NULL; |
| remaining_ma_columns = 0; |
| |
| /* Add the comma separated list of 'attname = value' */ |
| sep = ""; |
| foreach(l, targetList) |
| { |
| TargetEntry *tle = (TargetEntry *) lfirst(l); |
| Node *expr; |
| |
| if (tle->resjunk) |
| continue; /* ignore junk entries */ |
| |
| /* Emit separator (OK whether we're in multiassignment or not) */ |
| appendStringInfoString(buf, sep); |
| sep = ", "; |
| |
| /* |
| * Check to see if we're starting a multiassignment group: if so, |
| * output a left paren. |
| */ |
| if (next_ma_cell != NULL && cur_ma_sublink == NULL) |
| { |
| /* |
| * We must dig down into the expr to see if it's a PARAM_MULTIEXPR |
| * Param. That could be buried under FieldStores and |
| * SubscriptingRefs and CoerceToDomains (cf processIndirection()), |
| * and underneath those there could be an implicit type coercion. |
| * Because we would ignore implicit type coercions anyway, we |
| * don't need to be as careful as processIndirection() is about |
| * descending past implicit CoerceToDomains. |
| */ |
| expr = (Node *) tle->expr; |
| while (expr) |
| { |
| if (IsA(expr, FieldStore)) |
| { |
| FieldStore *fstore = (FieldStore *) expr; |
| |
| expr = (Node *) linitial(fstore->newvals); |
| } |
| else if (IsA(expr, SubscriptingRef)) |
| { |
| SubscriptingRef *sbsref = (SubscriptingRef *) expr; |
| |
| if (sbsref->refassgnexpr == NULL) |
| break; |
| |
| expr = (Node *) sbsref->refassgnexpr; |
| } |
| else if (IsA(expr, CoerceToDomain)) |
| { |
| CoerceToDomain *cdomain = (CoerceToDomain *) expr; |
| |
| if (cdomain->coercionformat != COERCE_IMPLICIT_CAST) |
| break; |
| expr = (Node *) cdomain->arg; |
| } |
| else |
| break; |
| } |
| expr = strip_implicit_coercions(expr); |
| |
| if (expr && IsA(expr, Param) && |
| ((Param *) expr)->paramkind == PARAM_MULTIEXPR) |
| { |
| cur_ma_sublink = (SubLink *) lfirst(next_ma_cell); |
| next_ma_cell = lnext(ma_sublinks, next_ma_cell); |
| remaining_ma_columns = count_nonjunk_tlist_entries(((Query *) cur_ma_sublink->subselect)->targetList); |
| Assert(((Param *) expr)->paramid == |
| ((cur_ma_sublink->subLinkId << 16) | 1)); |
| appendStringInfoChar(buf, '('); |
| } |
| } |
| |
| /* |
| * Put out name of target column; look in the catalogs, not at |
| * tle->resname, since resname will fail to track RENAME. |
| */ |
| appendStringInfoString(buf, |
| quote_identifier(get_attname(rte->relid, |
| tle->resno, |
| false))); |
| |
| /* |
| * Print any indirection needed (subfields or subscripts), and strip |
| * off the top-level nodes representing the indirection assignments. |
| */ |
| expr = processIndirection((Node *) tle->expr, context); |
| |
| /* |
| * If we're in a multiassignment, skip printing anything more, unless |
| * this is the last column; in which case, what we print should be the |
| * sublink, not the Param. |
| */ |
| if (cur_ma_sublink != NULL) |
| { |
| if (--remaining_ma_columns > 0) |
| continue; /* not the last column of multiassignment */ |
| appendStringInfoChar(buf, ')'); |
| expr = (Node *) cur_ma_sublink; |
| cur_ma_sublink = NULL; |
| } |
| |
| appendStringInfoString(buf, " = "); |
| |
| get_rule_expr(expr, context, false); |
| } |
| } |
| |
| |
| /* ---------- |
| * get_delete_query_def - Parse back a DELETE parsetree |
| * ---------- |
| */ |
| static void |
| get_delete_query_def(Query *query, deparse_context *context, |
| bool colNamesVisible) |
| { |
| StringInfo buf = context->buf; |
| RangeTblEntry *rte; |
| |
| /* Insert the WITH clause if given */ |
| get_with_clause(query, context); |
| |
| /* |
| * Start the query with DELETE FROM relname |
| */ |
| rte = rt_fetch(query->resultRelation, query->rtable); |
| Assert(rte->rtekind == RTE_RELATION); |
| if (PRETTY_INDENT(context)) |
| { |
| appendStringInfoChar(buf, ' '); |
| context->indentLevel += PRETTYINDENT_STD; |
| } |
| appendStringInfo(buf, "DELETE FROM %s%s", |
| only_marker(rte), |
| generate_relation_name(rte->relid, NIL)); |
| if (rte->alias != NULL) |
| appendStringInfo(buf, " %s", |
| quote_identifier(rte->alias->aliasname)); |
| |
| /* Add the USING clause if given */ |
| get_from_clause(query, " USING ", context); |
| |
| /* Add a WHERE clause if given */ |
| if (query->jointree->quals != NULL) |
| { |
| appendContextKeyword(context, " WHERE ", |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); |
| get_rule_expr(query->jointree->quals, context, false); |
| } |
| |
| /* Add RETURNING if present */ |
| if (query->returningList) |
| { |
| appendContextKeyword(context, " RETURNING", |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); |
| get_target_list(query->returningList, context, NULL, colNamesVisible); |
| } |
| } |
| |
| |
| /* ---------- |
| * get_utility_query_def - Parse back a UTILITY parsetree |
| * ---------- |
| */ |
| static void |
| get_utility_query_def(Query *query, deparse_context *context) |
| { |
| StringInfo buf = context->buf; |
| |
| if (query->utilityStmt && IsA(query->utilityStmt, NotifyStmt)) |
| { |
| NotifyStmt *stmt = (NotifyStmt *) query->utilityStmt; |
| |
| appendContextKeyword(context, "", |
| 0, PRETTYINDENT_STD, 1); |
| appendStringInfo(buf, "NOTIFY %s", |
| quote_identifier(stmt->conditionname)); |
| if (stmt->payload) |
| { |
| appendStringInfoString(buf, ", "); |
| simple_quote_literal(buf, stmt->payload); |
| } |
| } |
| else |
| { |
| /* Currently only NOTIFY utility commands can appear in rules */ |
| elog(ERROR, "unexpected utility statement type"); |
| } |
| } |
| |
| /* |
| * Display a Var appropriately. |
| * |
| * In some cases (currently only when recursing into an unnamed join) |
| * the Var's varlevelsup has to be interpreted with respect to a context |
| * above the current one; levelsup indicates the offset. |
| * |
| * If istoplevel is true, the Var is at the top level of a SELECT's |
| * targetlist, which means we need special treatment of whole-row Vars. |
| * Instead of the normal "tab.*", we'll print "tab.*::typename", which is a |
| * dirty hack to prevent "tab.*" from being expanded into multiple columns. |
| * (The parser will strip the useless coercion, so no inefficiency is added in |
| * dump and reload.) We used to print just "tab" in such cases, but that is |
| * ambiguous and will yield the wrong result if "tab" is also a plain column |
| * name in the query. |
| * |
| * Returns the attname of the Var, or NULL if the Var has no attname (because |
| * it is a whole-row Var or a subplan output reference). |
| */ |
| static char * |
| get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context) |
| { |
| StringInfo buf = context->buf; |
| RangeTblEntry *rte; |
| AttrNumber attnum; |
| int netlevelsup; |
| deparse_namespace *dpns; |
| Index varno; |
| AttrNumber varattno; |
| deparse_columns *colinfo; |
| char *refname; |
| char *attname; |
| |
| /* Find appropriate nesting depth */ |
| netlevelsup = var->varlevelsup + levelsup; |
| if (netlevelsup >= list_length(context->namespaces)) |
| elog(ERROR, "bogus varlevelsup: %d offset %d", |
| var->varlevelsup, levelsup); |
| dpns = (deparse_namespace *) list_nth(context->namespaces, |
| netlevelsup); |
| |
| /* |
| * If we have a syntactic referent for the Var, and we're working from a |
| * parse tree, prefer to use the syntactic referent. Otherwise, fall back |
| * on the semantic referent. (Forcing use of the semantic referent when |
| * printing plan trees is a design choice that's perhaps more motivated by |
| * backwards compatibility than anything else. But it does have the |
| * advantage of making plans more explicit.) |
| */ |
| if (var->varnosyn > 0 && dpns->plan == NULL) |
| { |
| varno = var->varnosyn; |
| varattno = var->varattnosyn; |
| } |
| else |
| { |
| varno = var->varno; |
| varattno = var->varattno; |
| } |
| |
| /* |
| * Try to find the relevant RTE in this rtable. In a plan tree, it's |
| * likely that varno is OUTER_VAR or INNER_VAR, in which case we must dig |
| * down into the subplans, or INDEX_VAR, which is resolved similarly. Also |
| * find the aliases previously assigned for this RTE. |
| */ |
| if (varno >= 1 && varno <= list_length(dpns->rtable)) |
| { |
| /* |
| * We might have been asked to map child Vars to some parent relation. |
| */ |
| if (context->appendparents && dpns->appendrels) |
| { |
| Index pvarno = varno; |
| AttrNumber pvarattno = varattno; |
| AppendRelInfo *appinfo = dpns->appendrels[pvarno]; |
| bool found = false; |
| |
| /* Only map up to inheritance parents, not UNION ALL appendrels */ |
| while (appinfo && |
| rt_fetch(appinfo->parent_relid, |
| dpns->rtable)->rtekind == RTE_RELATION) |
| { |
| found = false; |
| if (pvarattno > 0) /* system columns stay as-is */ |
| { |
| if (pvarattno > appinfo->num_child_cols) |
| break; /* safety check */ |
| pvarattno = appinfo->parent_colnos[pvarattno - 1]; |
| if (pvarattno == 0) |
| break; /* Var is local to child */ |
| } |
| |
| pvarno = appinfo->parent_relid; |
| found = true; |
| |
| /* If the parent is itself a child, continue up. */ |
| Assert(pvarno > 0 && pvarno <= list_length(dpns->rtable)); |
| appinfo = dpns->appendrels[pvarno]; |
| } |
| |
| /* |
| * If we found an ancestral rel, and that rel is included in |
| * appendparents, print that column not the original one. |
| */ |
| if (found && bms_is_member(pvarno, context->appendparents)) |
| { |
| varno = pvarno; |
| varattno = pvarattno; |
| } |
| } |
| |
| rte = rt_fetch(varno, dpns->rtable); |
| refname = (char *) list_nth(dpns->rtable_names, varno - 1); |
| colinfo = deparse_columns_fetch(varno, dpns); |
| attnum = varattno; |
| } |
| else |
| { |
| resolve_special_varno((Node *) var, context, |
| get_special_variable, NULL); |
| return NULL; |
| } |
| |
| /* |
| * The planner will sometimes emit Vars referencing resjunk elements of a |
| * subquery's target list (this is currently only possible if it chooses |
| * to generate a "physical tlist" for a SubqueryScan or CteScan node). |
| * Although we prefer to print subquery-referencing Vars using the |
| * subquery's alias, that's not possible for resjunk items since they have |
| * no alias. So in that case, drill down to the subplan and print the |
| * contents of the referenced tlist item. This works because in a plan |
| * tree, such Vars can only occur in a SubqueryScan or CteScan node, and |
| * we'll have set dpns->inner_plan to reference the child plan node. |
| */ |
| if ((rte->rtekind == RTE_SUBQUERY || rte->rtekind == RTE_CTE) && |
| attnum > list_length(rte->eref->colnames) && |
| dpns->inner_plan) |
| { |
| TargetEntry *tle; |
| deparse_namespace save_dpns; |
| |
| tle = get_tle_by_resno(dpns->inner_tlist, attnum); |
| if (!tle) |
| elog(ERROR, "invalid attnum %d for relation \"%s\"", |
| attnum, rte->eref->aliasname); |
| |
| Assert(netlevelsup == 0); |
| push_child_plan(dpns, dpns->inner_plan, &save_dpns); |
| |
| /* |
| * Force parentheses because our caller probably assumed a Var is a |
| * simple expression. |
| */ |
| if (!IsA(tle->expr, Var)) |
| appendStringInfoChar(buf, '('); |
| get_rule_expr((Node *) tle->expr, context, true); |
| if (!IsA(tle->expr, Var)) |
| appendStringInfoChar(buf, ')'); |
| |
| pop_child_plan(dpns, &save_dpns); |
| return NULL; |
| } |
| |
| /* |
| * If it's an unnamed join, look at the expansion of the alias variable. |
| * If it's a simple reference to one of the input vars, then recursively |
| * print the name of that var instead. When it's not a simple reference, |
| * we have to just print the unqualified join column name. (This can only |
| * happen with "dangerous" merged columns in a JOIN USING; we took pains |
| * previously to make the unqualified column name unique in such cases.) |
| * |
| * This wouldn't work in decompiling plan trees, because we don't store |
| * joinaliasvars lists after planning; but a plan tree should never |
| * contain a join alias variable. |
| */ |
| if (rte->rtekind == RTE_JOIN && rte->alias == NULL) |
| { |
| if (rte->joinaliasvars == NIL) |
| elog(ERROR, "cannot decompile join alias var in plan tree"); |
| if (attnum > 0) |
| { |
| Var *aliasvar; |
| |
| aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1); |
| /* we intentionally don't strip implicit coercions here */ |
| if (aliasvar && IsA(aliasvar, Var)) |
| { |
| return get_variable(aliasvar, var->varlevelsup + levelsup, |
| istoplevel, context); |
| } |
| } |
| |
| /* |
| * Unnamed join has no refname. (Note: since it's unnamed, there is |
| * no way the user could have referenced it to create a whole-row Var |
| * for it. So we don't have to cover that case below.) |
| */ |
| Assert(refname == NULL); |
| } |
| |
| if (attnum == InvalidAttrNumber) |
| attname = NULL; |
| else if (attnum > 0) |
| { |
| /* Get column name to use from the colinfo struct */ |
| if (attnum > colinfo->num_cols) |
| elog(ERROR, "invalid attnum %d for relation \"%s\"", |
| attnum, rte->eref->aliasname); |
| attname = colinfo->colnames[attnum - 1]; |
| |
| /* |
| * If we find a Var referencing a dropped column, it seems better to |
| * print something (anything) than to fail. In general this should |
| * not happen, but there are specific cases involving functions |
| * returning named composite types where we don't sufficiently enforce |
| * that you can't drop a column that's referenced in some view. |
| */ |
| if (attname == NULL) |
| attname = "?dropped?column?"; |
| } |
| else |
| { |
| /* System column - name is fixed, get it from the catalog */ |
| attname = get_rte_attribute_name(rte, attnum); |
| } |
| |
| if (refname && (context->varprefix || attname == NULL)) |
| { |
| appendStringInfoString(buf, quote_identifier(refname)); |
| appendStringInfoChar(buf, '.'); |
| } |
| if (attname) |
| appendStringInfoString(buf, quote_identifier(attname)); |
| else |
| { |
| appendStringInfoChar(buf, '*'); |
| if (istoplevel) |
| appendStringInfo(buf, "::%s", |
| format_type_with_typemod(var->vartype, |
| var->vartypmod)); |
| } |
| |
| return attname; |
| } |
| |
| /* |
| * Deparse a Var which references OUTER_VAR, INNER_VAR, or INDEX_VAR. This |
| * routine is actually a callback for resolve_special_varno, which handles |
| * finding the correct TargetEntry. We get the expression contained in that |
| * TargetEntry and just need to deparse it, a job we can throw back on |
| * get_rule_expr. |
| */ |
| static void |
| get_special_variable(Node *node, deparse_context *context, void *callback_arg) |
| { |
| StringInfo buf = context->buf; |
| |
| /* |
| * For a non-Var referent, force parentheses because our caller probably |
| * assumed a Var is a simple expression. |
| */ |
| if (!IsA(node, Var)) |
| appendStringInfoChar(buf, '('); |
| get_rule_expr(node, context, true); |
| if (!IsA(node, Var)) |
| appendStringInfoChar(buf, ')'); |
| } |
| |
| /* |
| * Chase through plan references to special varnos (OUTER_VAR, INNER_VAR, |
| * INDEX_VAR) until we find a real Var or some kind of non-Var node; then, |
| * invoke the callback provided. |
| */ |
| static void |
| resolve_special_varno(Node *node, deparse_context *context, |
| rsv_callback callback, void *callback_arg) |
| { |
| Var *var; |
| deparse_namespace *dpns; |
| |
| /* This function is recursive, so let's be paranoid. */ |
| check_stack_depth(); |
| |
| /* If it's not a Var, invoke the callback. */ |
| if (!IsA(node, Var)) |
| { |
| (*callback) (node, context, callback_arg); |
| return; |
| } |
| |
| /* Find appropriate nesting depth */ |
| var = (Var *) node; |
| dpns = (deparse_namespace *) list_nth(context->namespaces, |
| var->varlevelsup); |
| |
| /* |
| * If varno is special, recurse. (Don't worry about varnosyn; if we're |
| * here, we already decided not to use that.) |
| */ |
| if (var->varno == OUTER_VAR && dpns->outer_tlist) |
| { |
| TargetEntry *tle; |
| deparse_namespace save_dpns; |
| Bitmapset *save_appendparents; |
| |
| tle = get_tle_by_resno(dpns->outer_tlist, var->varattno); |
| if (!tle) |
| elog(ERROR, "bogus varattno for OUTER_VAR var: %d", var->varattno); |
| |
| /* |
| * If we're descending to the first child of an Append or MergeAppend, |
| * update appendparents. This will affect deparsing of all Vars |
| * appearing within the eventually-resolved subexpression. |
| */ |
| save_appendparents = context->appendparents; |
| |
| if (IsA(dpns->plan, Append)) |
| context->appendparents = bms_union(context->appendparents, |
| ((Append *) dpns->plan)->apprelids); |
| else if (IsA(dpns->plan, MergeAppend)) |
| context->appendparents = bms_union(context->appendparents, |
| ((MergeAppend *) dpns->plan)->apprelids); |
| |
| push_child_plan(dpns, dpns->outer_plan, &save_dpns); |
| resolve_special_varno((Node *) tle->expr, context, |
| callback, callback_arg); |
| pop_child_plan(dpns, &save_dpns); |
| context->appendparents = save_appendparents; |
| return; |
| } |
| else if (var->varno == INNER_VAR && dpns->inner_tlist) |
| { |
| TargetEntry *tle; |
| deparse_namespace save_dpns; |
| |
| tle = get_tle_by_resno(dpns->inner_tlist, var->varattno); |
| if (!tle) |
| elog(ERROR, "bogus varattno for INNER_VAR var: %d", var->varattno); |
| |
| push_child_plan(dpns, dpns->inner_plan, &save_dpns); |
| resolve_special_varno((Node *) tle->expr, context, |
| callback, callback_arg); |
| pop_child_plan(dpns, &save_dpns); |
| return; |
| } |
| else if (var->varno == INDEX_VAR && dpns->index_tlist) |
| { |
| TargetEntry *tle; |
| |
| tle = get_tle_by_resno(dpns->index_tlist, var->varattno); |
| if (!tle) |
| elog(ERROR, "bogus varattno for INDEX_VAR var: %d", var->varattno); |
| |
| resolve_special_varno((Node *) tle->expr, context, |
| callback, callback_arg); |
| return; |
| } |
| else if (var->varno < 1 || var->varno > list_length(dpns->rtable)) |
| elog(ERROR, "bogus varno: %d", var->varno); |
| |
| /* Not special. Just invoke the callback. */ |
| (*callback) (node, context, callback_arg); |
| } |
| |
| /* |
| * Get the name of a field of an expression of composite type. The |
| * expression is usually a Var, but we handle other cases too. |
| * |
| * levelsup is an extra offset to interpret the Var's varlevelsup correctly. |
| * |
| * This is fairly straightforward when the expression has a named composite |
| * type; we need only look up the type in the catalogs. However, the type |
| * could also be RECORD. Since no actual table or view column is allowed to |
| * have type RECORD, a Var of type RECORD must refer to a JOIN or FUNCTION RTE |
| * or to a subquery output. We drill down to find the ultimate defining |
| * expression and attempt to infer the field name from it. We ereport if we |
| * can't determine the name. |
| * |
| * Similarly, a PARAM of type RECORD has to refer to some expression of |
| * a determinable composite type. |
| */ |
| static const char * |
| get_name_for_var_field(Var *var, int fieldno, |
| int levelsup, deparse_context *context) |
| { |
| RangeTblEntry *rte; |
| AttrNumber attnum; |
| int netlevelsup; |
| deparse_namespace *dpns; |
| Index varno; |
| AttrNumber varattno; |
| TupleDesc tupleDesc; |
| Node *expr; |
| |
| /* |
| * If it's a RowExpr that was expanded from a whole-row Var, use the |
| * column names attached to it. |
| */ |
| if (IsA(var, RowExpr)) |
| { |
| RowExpr *r = (RowExpr *) var; |
| |
| if (fieldno > 0 && fieldno <= list_length(r->colnames)) |
| return strVal(list_nth(r->colnames, fieldno - 1)); |
| } |
| |
| /* |
| * If it's a Param of type RECORD, try to find what the Param refers to. |
| */ |
| if (IsA(var, Param)) |
| { |
| Param *param = (Param *) var; |
| ListCell *ancestor_cell; |
| |
| expr = find_param_referent(param, context, &dpns, &ancestor_cell); |
| if (expr) |
| { |
| /* Found a match, so recurse to decipher the field name */ |
| deparse_namespace save_dpns; |
| const char *result; |
| |
| push_ancestor_plan(dpns, ancestor_cell, &save_dpns); |
| result = get_name_for_var_field((Var *) expr, fieldno, |
| 0, context); |
| pop_ancestor_plan(dpns, &save_dpns); |
| return result; |
| } |
| } |
| |
| /* |
| * If it's a Var of type RECORD, we have to find what the Var refers to; |
| * if not, we can use get_expr_result_tupdesc(). |
| */ |
| if (!IsA(var, Var) || |
| var->vartype != RECORDOID) |
| { |
| tupleDesc = get_expr_result_tupdesc((Node *) var, false); |
| /* Got the tupdesc, so we can extract the field name */ |
| Assert(fieldno >= 1 && fieldno <= tupleDesc->natts); |
| return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname); |
| } |
| |
| /* Find appropriate nesting depth */ |
| netlevelsup = var->varlevelsup + levelsup; |
| if (netlevelsup >= list_length(context->namespaces)) |
| elog(ERROR, "bogus varlevelsup: %d offset %d", |
| var->varlevelsup, levelsup); |
| dpns = (deparse_namespace *) list_nth(context->namespaces, |
| netlevelsup); |
| |
| /* |
| * If we have a syntactic referent for the Var, and we're working from a |
| * parse tree, prefer to use the syntactic referent. Otherwise, fall back |
| * on the semantic referent. (See comments in get_variable().) |
| */ |
| if (var->varnosyn > 0 && dpns->plan == NULL) |
| { |
| varno = var->varnosyn; |
| varattno = var->varattnosyn; |
| } |
| else |
| { |
| varno = var->varno; |
| varattno = var->varattno; |
| } |
| |
| /* |
| * Try to find the relevant RTE in this rtable. In a plan tree, it's |
| * likely that varno is OUTER_VAR or INNER_VAR, in which case we must dig |
| * down into the subplans, or INDEX_VAR, which is resolved similarly. |
| * |
| * Note: unlike get_variable and resolve_special_varno, we need not worry |
| * about inheritance mapping: a child Var should have the same datatype as |
| * its parent, and here we're really only interested in the Var's type. |
| */ |
| if (varno >= 1 && varno <= list_length(dpns->rtable)) |
| { |
| rte = rt_fetch(varno, dpns->rtable); |
| attnum = varattno; |
| } |
| else if (varno == OUTER_VAR && dpns->outer_tlist) |
| { |
| TargetEntry *tle; |
| deparse_namespace save_dpns; |
| const char *result; |
| |
| tle = get_tle_by_resno(dpns->outer_tlist, varattno); |
| if (!tle) |
| elog(ERROR, "bogus varattno for OUTER_VAR var: %d", varattno); |
| |
| Assert(netlevelsup == 0); |
| push_child_plan(dpns, dpns->outer_plan, &save_dpns); |
| |
| result = get_name_for_var_field((Var *) tle->expr, fieldno, |
| levelsup, context); |
| |
| pop_child_plan(dpns, &save_dpns); |
| return result; |
| } |
| else if (varno == INNER_VAR && dpns->inner_tlist) |
| { |
| TargetEntry *tle; |
| deparse_namespace save_dpns; |
| const char *result; |
| |
| tle = get_tle_by_resno(dpns->inner_tlist, varattno); |
| if (!tle) |
| elog(ERROR, "bogus varattno for INNER_VAR var: %d", varattno); |
| |
| Assert(netlevelsup == 0); |
| push_child_plan(dpns, dpns->inner_plan, &save_dpns); |
| |
| result = get_name_for_var_field((Var *) tle->expr, fieldno, |
| levelsup, context); |
| |
| pop_child_plan(dpns, &save_dpns); |
| return result; |
| } |
| else if (varno == INDEX_VAR && dpns->index_tlist) |
| { |
| TargetEntry *tle; |
| const char *result; |
| |
| tle = get_tle_by_resno(dpns->index_tlist, varattno); |
| if (!tle) |
| elog(ERROR, "bogus varattno for INDEX_VAR var: %d", varattno); |
| |
| Assert(netlevelsup == 0); |
| |
| result = get_name_for_var_field((Var *) tle->expr, fieldno, |
| levelsup, context); |
| |
| return result; |
| } |
| else |
| { |
| elog(WARNING, "bogus varno: %d", varno); |
| return psprintf("<BOGUS %d>", var->varattno); |
| } |
| |
| if (rte == NULL) |
| { |
| ereport(WARNING, (errcode(ERRCODE_INTERNAL_ERROR), |
| errmsg_internal("bogus var: varno=%d varattno=%d", |
| var->varno, var->varattno) )); |
| return "*BOGUS*"; |
| } |
| |
| if (attnum == InvalidAttrNumber) |
| { |
| /* Var is whole-row reference to RTE, so select the right field */ |
| return get_rte_attribute_name(rte, fieldno); |
| } |
| |
| /* |
| * This part has essentially the same logic as the parser's |
| * expandRecordVariable() function, but we are dealing with a different |
| * representation of the input context, and we only need one field name |
| * not a TupleDesc. Also, we need special cases for finding subquery and |
| * CTE subplans when deparsing Plan trees. |
| */ |
| expr = (Node *) var; /* default if we can't drill down */ |
| |
| switch (rte->rtekind) |
| { |
| case RTE_RELATION: |
| case RTE_VALUES: |
| case RTE_NAMEDTUPLESTORE: |
| case RTE_RESULT: |
| |
| /* |
| * This case should not occur: a column of a table, values list, |
| * or ENR shouldn't have type RECORD. Fall through and fail (most |
| * likely) at the bottom. |
| */ |
| break; |
| case RTE_SUBQUERY: |
| /* Subselect-in-FROM: examine sub-select's output expr */ |
| { |
| if (rte->subquery) |
| { |
| TargetEntry *ste = get_tle_by_resno(rte->subquery->targetList, |
| attnum); |
| |
| if (ste == NULL || ste->resjunk) |
| elog(ERROR, "subquery %s does not have attribute %d", |
| rte->eref->aliasname, attnum); |
| expr = (Node *) ste->expr; |
| if (IsA(expr, Var)) |
| { |
| /* |
| * Recurse into the sub-select to see what its Var |
| * refers to. We have to build an additional level of |
| * namespace to keep in step with varlevelsup in the |
| * subselect. |
| */ |
| deparse_namespace mydpns; |
| const char *result; |
| |
| set_deparse_for_query(&mydpns, rte->subquery, |
| context->namespaces); |
| |
| context->namespaces = lcons(&mydpns, |
| context->namespaces); |
| |
| result = get_name_for_var_field((Var *) expr, fieldno, |
| 0, context); |
| |
| context->namespaces = |
| list_delete_first(context->namespaces); |
| |
| return result; |
| } |
| /* else fall through to inspect the expression */ |
| } |
| else |
| { |
| /* |
| * We're deparsing a Plan tree so we don't have complete |
| * RTE entries (in particular, rte->subquery is NULL). But |
| * the only place we'd see a Var directly referencing a |
| * SUBQUERY RTE is in a SubqueryScan plan node, and we can |
| * look into the child plan's tlist instead. |
| */ |
| TargetEntry *tle; |
| deparse_namespace save_dpns; |
| const char *result; |
| |
| if (!dpns->inner_plan) |
| elog(ERROR, "failed to find plan for subquery %s", |
| rte->eref->aliasname); |
| tle = get_tle_by_resno(dpns->inner_tlist, attnum); |
| if (!tle) |
| elog(ERROR, "bogus varattno for subquery var: %d", |
| attnum); |
| Assert(netlevelsup == 0); |
| push_child_plan(dpns, dpns->inner_plan, &save_dpns); |
| |
| result = get_name_for_var_field((Var *) tle->expr, fieldno, |
| levelsup, context); |
| |
| pop_child_plan(dpns, &save_dpns); |
| return result; |
| } |
| } |
| break; |
| case RTE_JOIN: |
| /* Join RTE --- recursively inspect the alias variable */ |
| if (rte->joinaliasvars == NIL) |
| elog(ERROR, "cannot decompile join alias var in plan tree"); |
| Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars)); |
| expr = (Node *) list_nth(rte->joinaliasvars, attnum - 1); |
| Assert(expr != NULL); |
| /* we intentionally don't strip implicit coercions here */ |
| if (IsA(expr, Var)) |
| return get_name_for_var_field((Var *) expr, fieldno, |
| var->varlevelsup + levelsup, |
| context); |
| /* else fall through to inspect the expression */ |
| break; |
| case RTE_TABLEFUNCTION: |
| case RTE_FUNCTION: |
| case RTE_TABLEFUNC: |
| |
| /* |
| * We couldn't get here unless a function is declared with one of |
| * its result columns as RECORD, which is not allowed. |
| */ |
| break; |
| case RTE_CTE: |
| /* CTE reference: examine subquery's output expr */ |
| { |
| CommonTableExpr *cte = NULL; |
| Index ctelevelsup; |
| ListCell *lc; |
| |
| /* |
| * Try to find the referenced CTE using the namespace stack. |
| */ |
| ctelevelsup = rte->ctelevelsup + netlevelsup; |
| if (ctelevelsup >= list_length(context->namespaces)) |
| lc = NULL; |
| else |
| { |
| deparse_namespace *ctedpns; |
| |
| ctedpns = (deparse_namespace *) |
| list_nth(context->namespaces, ctelevelsup); |
| foreach(lc, ctedpns->ctes) |
| { |
| cte = (CommonTableExpr *) lfirst(lc); |
| if (strcmp(cte->ctename, rte->ctename) == 0) |
| break; |
| } |
| } |
| if (lc != NULL) |
| { |
| Query *ctequery = (Query *) cte->ctequery; |
| TargetEntry *ste = get_tle_by_resno(GetCTETargetList(cte), |
| attnum); |
| |
| if (ste == NULL || ste->resjunk) |
| elog(ERROR, "subquery %s does not have attribute %d", |
| rte->eref->aliasname, attnum); |
| expr = (Node *) ste->expr; |
| if (IsA(expr, Var)) |
| { |
| /* |
| * Recurse into the CTE to see what its Var refers to. |
| * We have to build an additional level of namespace |
| * to keep in step with varlevelsup in the CTE. |
| * Furthermore it could be an outer CTE, so we may |
| * have to delete some levels of namespace. |
| */ |
| List *save_nslist = context->namespaces; |
| List *new_nslist; |
| deparse_namespace mydpns; |
| const char *result; |
| |
| set_deparse_for_query(&mydpns, ctequery, |
| context->namespaces); |
| |
| new_nslist = list_copy_tail(context->namespaces, |
| ctelevelsup); |
| context->namespaces = lcons(&mydpns, new_nslist); |
| |
| result = get_name_for_var_field((Var *) expr, fieldno, |
| 0, context); |
| |
| context->namespaces = save_nslist; |
| |
| return result; |
| } |
| /* else fall through to inspect the expression */ |
| } |
| else |
| { |
| /* |
| * We're deparsing a Plan tree so we don't have a CTE |
| * list. But the only places we'd see a Var directly |
| * referencing a CTE RTE are in CteScan or WorkTableScan |
| * plan nodes. For those cases, set_deparse_plan arranged |
| * for dpns->inner_plan to be the plan node that emits the |
| * CTE or RecursiveUnion result, and we can look at its |
| * tlist instead. |
| */ |
| TargetEntry *tle; |
| deparse_namespace save_dpns; |
| const char *result; |
| |
| if (!dpns->inner_plan) |
| elog(ERROR, "failed to find plan for CTE %s", |
| rte->eref->aliasname); |
| tle = get_tle_by_resno(dpns->inner_tlist, attnum); |
| if (!tle) |
| elog(ERROR, "bogus varattno for subquery var: %d", |
| attnum); |
| Assert(netlevelsup == 0); |
| push_child_plan(dpns, dpns->inner_plan, &save_dpns); |
| |
| result = get_name_for_var_field((Var *) tle->expr, fieldno, |
| levelsup, context); |
| |
| pop_child_plan(dpns, &save_dpns); |
| return result; |
| } |
| } |
| break; |
| case RTE_VOID: |
| /* No references should exist to a deleted RTE. */ |
| break; |
| } |
| |
| /* |
| * We now have an expression we can't expand any more, so see if |
| * get_expr_result_tupdesc() can do anything with it. |
| */ |
| tupleDesc = get_expr_result_tupdesc(expr, false); |
| /* Got the tupdesc, so we can extract the field name */ |
| Assert(fieldno >= 1 && fieldno <= tupleDesc->natts); |
| return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname); |
| } |
| |
| /* |
| * Try to find the referenced expression for a PARAM_EXEC Param that might |
| * reference a parameter supplied by an upper NestLoop or SubPlan plan node. |
| * |
| * If successful, return the expression and set *dpns_p and *ancestor_cell_p |
| * appropriately for calling push_ancestor_plan(). If no referent can be |
| * found, return NULL. |
| */ |
| static Node * |
| find_param_referent(Param *param, deparse_context *context, |
| deparse_namespace **dpns_p, ListCell **ancestor_cell_p) |
| { |
| /* Initialize output parameters to prevent compiler warnings */ |
| *dpns_p = NULL; |
| *ancestor_cell_p = NULL; |
| |
| /* |
| * If it's a PARAM_EXEC parameter, look for a matching NestLoopParam or |
| * SubPlan argument. This will necessarily be in some ancestor of the |
| * current expression's Plan node. |
| */ |
| if (param->paramkind == PARAM_EXEC) |
| { |
| deparse_namespace *dpns; |
| Plan *child_plan; |
| bool in_same_plan_level; |
| ListCell *lc; |
| |
| dpns = (deparse_namespace *) linitial(context->namespaces); |
| child_plan = dpns->plan; |
| in_same_plan_level = true; |
| |
| foreach(lc, dpns->ancestors) |
| { |
| Node *ancestor = (Node *) lfirst(lc); |
| ListCell *lc2; |
| |
| /* |
| * NestLoops transmit params to their inner child only; also, once |
| * we've crawled up out of a subplan, this couldn't possibly be |
| * the right match. |
| */ |
| if (IsA(ancestor, NestLoop) && |
| child_plan == innerPlan(ancestor) && |
| in_same_plan_level) |
| { |
| NestLoop *nl = (NestLoop *) ancestor; |
| |
| foreach(lc2, nl->nestParams) |
| { |
| NestLoopParam *nlp = (NestLoopParam *) lfirst(lc2); |
| |
| if (nlp->paramno == param->paramid) |
| { |
| /* Found a match, so return it */ |
| *dpns_p = dpns; |
| *ancestor_cell_p = lc; |
| return (Node *) nlp->paramval; |
| } |
| } |
| } |
| |
| /* |
| * If ancestor is a SubPlan, check the arguments it provides. |
| */ |
| if (IsA(ancestor, SubPlan)) |
| { |
| SubPlan *subplan = (SubPlan *) ancestor; |
| ListCell *lc3; |
| ListCell *lc4; |
| |
| forboth(lc3, subplan->parParam, lc4, subplan->args) |
| { |
| int paramid = lfirst_int(lc3); |
| Node *arg = (Node *) lfirst(lc4); |
| |
| if (paramid == param->paramid) |
| { |
| /* |
| * Found a match, so return it. But, since Vars in |
| * the arg are to be evaluated in the surrounding |
| * context, we have to point to the next ancestor item |
| * that is *not* a SubPlan. |
| */ |
| ListCell *rest; |
| |
| for_each_cell(rest, dpns->ancestors, |
| lnext(dpns->ancestors, lc)) |
| { |
| Node *ancestor2 = (Node *) lfirst(rest); |
| |
| if (!IsA(ancestor2, SubPlan)) |
| { |
| *dpns_p = dpns; |
| *ancestor_cell_p = rest; |
| return arg; |
| } |
| } |
| elog(ERROR, "SubPlan cannot be outermost ancestor"); |
| } |
| } |
| |
| /* We have emerged from a subplan. */ |
| in_same_plan_level = false; |
| |
| /* SubPlan isn't a kind of Plan, so skip the rest */ |
| continue; |
| } |
| |
| /* |
| * Check to see if we're emerging from an initplan of the current |
| * ancestor plan. Initplans never have any parParams, so no need |
| * to search that list, but we need to know if we should reset |
| * in_same_plan_level. |
| */ |
| foreach(lc2, ((Plan *) ancestor)->initPlan) |
| { |
| SubPlan *subplan = castNode(SubPlan, lfirst(lc2)); |
| |
| if (child_plan != (Plan *) list_nth(dpns->subplans, |
| subplan->plan_id - 1)) |
| continue; |
| |
| /* No parameters to be had here. */ |
| Assert(subplan->parParam == NIL); |
| |
| /* We have emerged from an initplan. */ |
| in_same_plan_level = false; |
| break; |
| } |
| |
| /* No luck, crawl up to next ancestor */ |
| child_plan = (Plan *) ancestor; |
| } |
| } |
| |
| /* No referent found */ |
| return NULL; |
| } |
| |
| /* |
| * Display a Param appropriately. |
| */ |
| static void |
| get_parameter(Param *param, deparse_context *context) |
| { |
| Node *expr; |
| deparse_namespace *dpns; |
| ListCell *ancestor_cell; |
| |
| /* |
| * If it's a PARAM_EXEC parameter, try to locate the expression from which |
| * the parameter was computed. Note that failing to find a referent isn't |
| * an error, since the Param might well be a subplan output rather than an |
| * input. |
| */ |
| expr = find_param_referent(param, context, &dpns, &ancestor_cell); |
| if (expr) |
| { |
| /* Found a match, so print it */ |
| deparse_namespace save_dpns; |
| bool save_varprefix; |
| bool need_paren; |
| |
| /* Switch attention to the ancestor plan node */ |
| push_ancestor_plan(dpns, ancestor_cell, &save_dpns); |
| |
| /* |
| * Force prefixing of Vars, since they won't belong to the relation |
| * being scanned in the original plan node. |
| */ |
| save_varprefix = context->varprefix; |
| context->varprefix = true; |
| |
| /* |
| * A Param's expansion is typically a Var, Aggref, GroupingFunc, or |
| * upper-level Param, which wouldn't need extra parentheses. |
| * Otherwise, insert parens to ensure the expression looks atomic. |
| */ |
| need_paren = !(IsA(expr, Var) || |
| IsA(expr, Aggref) || |
| IsA(expr, GroupingFunc) || |
| IsA(expr, Param)); |
| if (need_paren) |
| appendStringInfoChar(context->buf, '('); |
| |
| get_rule_expr(expr, context, false); |
| |
| if (need_paren) |
| appendStringInfoChar(context->buf, ')'); |
| |
| context->varprefix = save_varprefix; |
| |
| pop_ancestor_plan(dpns, &save_dpns); |
| |
| return; |
| } |
| |
| /* |
| * If it's an external parameter, see if the outermost namespace provides |
| * function argument names. |
| */ |
| if (param->paramkind == PARAM_EXTERN && context->namespaces != NIL) |
| { |
| dpns = llast(context->namespaces); |
| if (dpns->argnames && |
| param->paramid > 0 && |
| param->paramid <= dpns->numargs) |
| { |
| char *argname = dpns->argnames[param->paramid - 1]; |
| |
| if (argname) |
| { |
| bool should_qualify = false; |
| ListCell *lc; |
| |
| /* |
| * Qualify the parameter name if there are any other deparse |
| * namespaces with range tables. This avoids qualifying in |
| * trivial cases like "RETURN a + b", but makes it safe in all |
| * other cases. |
| */ |
| foreach(lc, context->namespaces) |
| { |
| deparse_namespace *dpns = lfirst(lc); |
| |
| if (list_length(dpns->rtable_names) > 0) |
| { |
| should_qualify = true; |
| break; |
| } |
| } |
| if (should_qualify) |
| { |
| appendStringInfoString(context->buf, quote_identifier(dpns->funcname)); |
| appendStringInfoChar(context->buf, '.'); |
| } |
| |
| appendStringInfoString(context->buf, quote_identifier(argname)); |
| return; |
| } |
| } |
| } |
| |
| /* |
| * Not PARAM_EXEC, or couldn't find referent: just print $N. |
| */ |
| appendStringInfo(context->buf, "$%d", param->paramid); |
| } |
| |
| /* |
| * get_simple_binary_op_name |
| * |
| * helper function for isSimpleNode |
| * will return single char binary operator name, or NULL if it's not |
| */ |
| static const char * |
| get_simple_binary_op_name(OpExpr *expr) |
| { |
| List *args = expr->args; |
| |
| if (list_length(args) == 2) |
| { |
| /* binary operator */ |
| Node *arg1 = (Node *) linitial(args); |
| Node *arg2 = (Node *) lsecond(args); |
| const char *op; |
| |
| op = generate_operator_name(expr->opno, exprType(arg1), exprType(arg2)); |
| if (strlen(op) == 1) |
| return op; |
| } |
| return NULL; |
| } |
| |
| |
| /* |
| * isSimpleNode - check if given node is simple (doesn't need parenthesizing) |
| * |
| * true : simple in the context of parent node's type |
| * false : not simple |
| */ |
| static bool |
| isSimpleNode(Node *node, Node *parentNode, int prettyFlags) |
| { |
| if (!node) |
| return false; |
| |
| switch (nodeTag(node)) |
| { |
| case T_Var: |
| case T_Const: |
| case T_Param: |
| case T_CoerceToDomainValue: |
| case T_SetToDefault: |
| case T_CurrentOfExpr: |
| /* single words: always simple */ |
| return true; |
| |
| case T_SubscriptingRef: |
| case T_ArrayExpr: |
| case T_RowExpr: |
| case T_CoalesceExpr: |
| case T_MinMaxExpr: |
| case T_SQLValueFunction: |
| case T_XmlExpr: |
| case T_NextValueExpr: |
| case T_NullIfExpr: |
| case T_Aggref: |
| case T_GroupingFunc: |
| case T_WindowFunc: |
| case T_FuncExpr: |
| /* function-like: name(..) or name[..] */ |
| return true; |
| |
| /* CASE keywords act as parentheses */ |
| case T_CaseExpr: |
| return true; |
| |
| case T_FieldSelect: |
| |
| /* |
| * appears simple since . has top precedence, unless parent is |
| * T_FieldSelect itself! |
| */ |
| return (IsA(parentNode, FieldSelect) ? false : true); |
| |
| case T_FieldStore: |
| |
| /* |
| * treat like FieldSelect (probably doesn't matter) |
| */ |
| return (IsA(parentNode, FieldStore) ? false : true); |
| |
| case T_CoerceToDomain: |
| /* maybe simple, check args */ |
| return isSimpleNode((Node *) ((CoerceToDomain *) node)->arg, |
| node, prettyFlags); |
| case T_RelabelType: |
| return isSimpleNode((Node *) ((RelabelType *) node)->arg, |
| node, prettyFlags); |
| case T_CoerceViaIO: |
| return isSimpleNode((Node *) ((CoerceViaIO *) node)->arg, |
| node, prettyFlags); |
| case T_ArrayCoerceExpr: |
| return isSimpleNode((Node *) ((ArrayCoerceExpr *) node)->arg, |
| node, prettyFlags); |
| case T_ConvertRowtypeExpr: |
| return isSimpleNode((Node *) ((ConvertRowtypeExpr *) node)->arg, |
| node, prettyFlags); |
| |
| case T_OpExpr: |
| { |
| /* depends on parent node type; needs further checking */ |
| if (prettyFlags & PRETTYFLAG_PAREN && IsA(parentNode, OpExpr)) |
| { |
| const char *op; |
| const char *parentOp; |
| bool is_lopriop; |
| bool is_hipriop; |
| bool is_lopriparent; |
| bool is_hipriparent; |
| |
| op = get_simple_binary_op_name((OpExpr *) node); |
| if (!op) |
| return false; |
| |
| /* We know only the basic operators + - and * / % */ |
| is_lopriop = (strchr("+-", *op) != NULL); |
| is_hipriop = (strchr("*/%", *op) != NULL); |
| if (!(is_lopriop || is_hipriop)) |
| return false; |
| |
| parentOp = get_simple_binary_op_name((OpExpr *) parentNode); |
| if (!parentOp) |
| return false; |
| |
| is_lopriparent = (strchr("+-", *parentOp) != NULL); |
| is_hipriparent = (strchr("*/%", *parentOp) != NULL); |
| if (!(is_lopriparent || is_hipriparent)) |
| return false; |
| |
| if (is_hipriop && is_lopriparent) |
| return true; /* op binds tighter than parent */ |
| |
| if (is_lopriop && is_hipriparent) |
| return false; |
| |
| /* |
| * Operators are same priority --- can skip parens only if |
| * we have (a - b) - c, not a - (b - c). |
| */ |
| if (node == (Node *) linitial(((OpExpr *) parentNode)->args)) |
| return true; |
| |
| return false; |
| } |
| /* else do the same stuff as for T_SubLink et al. */ |
| } |
| /* FALLTHROUGH */ |
| |
| case T_SubLink: |
| case T_NullTest: |
| case T_BooleanTest: |
| case T_DistinctExpr: |
| switch (nodeTag(parentNode)) |
| { |
| case T_FuncExpr: |
| { |
| /* special handling for casts */ |
| CoercionForm type = ((FuncExpr *) parentNode)->funcformat; |
| |
| if (type == COERCE_EXPLICIT_CAST || |
| type == COERCE_IMPLICIT_CAST) |
| return false; |
| return true; /* own parentheses */ |
| } |
| case T_BoolExpr: /* lower precedence */ |
| case T_SubscriptingRef: /* other separators */ |
| case T_ArrayExpr: /* other separators */ |
| case T_RowExpr: /* other separators */ |
| case T_CoalesceExpr: /* own parentheses */ |
| case T_MinMaxExpr: /* own parentheses */ |
| case T_XmlExpr: /* own parentheses */ |
| case T_NullIfExpr: /* other separators */ |
| case T_Aggref: /* own parentheses */ |
| case T_GroupingFunc: /* own parentheses */ |
| case T_WindowFunc: /* own parentheses */ |
| case T_CaseExpr: /* other separators */ |
| return true; |
| default: |
| return false; |
| } |
| |
| case T_BoolExpr: |
| switch (nodeTag(parentNode)) |
| { |
| case T_BoolExpr: |
| if (prettyFlags & PRETTYFLAG_PAREN) |
| { |
| BoolExprType type; |
| BoolExprType parentType; |
| |
| type = ((BoolExpr *) node)->boolop; |
| parentType = ((BoolExpr *) parentNode)->boolop; |
| switch (type) |
| { |
| case NOT_EXPR: |
| case AND_EXPR: |
| if (parentType == AND_EXPR) |
| return true; |
| break; |
| case OR_EXPR: |
| if (parentType == OR_EXPR) |
| return true; |
| break; |
| } |
| } |
| return false; |
| case T_FuncExpr: |
| { |
| /* special handling for casts */ |
| CoercionForm type = ((FuncExpr *) parentNode)->funcformat; |
| |
| if (type == COERCE_EXPLICIT_CAST || |
| type == COERCE_IMPLICIT_CAST) |
| return false; |
| return true; /* own parentheses */ |
| } |
| case T_SubscriptingRef: /* other separators */ |
| case T_ArrayExpr: /* other separators */ |
| case T_RowExpr: /* other separators */ |
| case T_CoalesceExpr: /* own parentheses */ |
| case T_MinMaxExpr: /* own parentheses */ |
| case T_XmlExpr: /* own parentheses */ |
| case T_NullIfExpr: /* other separators */ |
| case T_Aggref: /* own parentheses */ |
| case T_GroupingFunc: /* own parentheses */ |
| case T_WindowFunc: /* own parentheses */ |
| case T_CaseExpr: /* other separators */ |
| return true; |
| default: |
| return false; |
| } |
| |
| default: |
| break; |
| } |
| /* those we don't know: in dubio complexo */ |
| return false; |
| } |
| |
| |
| /* |
| * appendContextKeyword - append a keyword to buffer |
| * |
| * If prettyPrint is enabled, perform a line break, and adjust indentation. |
| * Otherwise, just append the keyword. |
| */ |
| static void |
| appendContextKeyword(deparse_context *context, const char *str, |
| int indentBefore, int indentAfter, int indentPlus) |
| { |
| StringInfo buf = context->buf; |
| |
| if (PRETTY_INDENT(context)) |
| { |
| int indentAmount; |
| |
| context->indentLevel += indentBefore; |
| |
| /* remove any trailing spaces currently in the buffer ... */ |
| removeStringInfoSpaces(buf); |
| /* ... then add a newline and some spaces */ |
| appendStringInfoChar(buf, '\n'); |
| |
| if (context->indentLevel < PRETTYINDENT_LIMIT) |
| indentAmount = Max(context->indentLevel, 0) + indentPlus; |
| else |
| { |
| /* |
| * If we're indented more than PRETTYINDENT_LIMIT characters, try |
| * to conserve horizontal space by reducing the per-level |
| * indentation. For best results the scale factor here should |
| * divide all the indent amounts that get added to indentLevel |
| * (PRETTYINDENT_STD, etc). It's important that the indentation |
| * not grow unboundedly, else deeply-nested trees use O(N^2) |
| * whitespace; so we also wrap modulo PRETTYINDENT_LIMIT. |
| */ |
| indentAmount = PRETTYINDENT_LIMIT + |
| (context->indentLevel - PRETTYINDENT_LIMIT) / |
| (PRETTYINDENT_STD / 2); |
| indentAmount %= PRETTYINDENT_LIMIT; |
| /* scale/wrap logic affects indentLevel, but not indentPlus */ |
| indentAmount += indentPlus; |
| } |
| appendStringInfoSpaces(buf, indentAmount); |
| |
| appendStringInfoString(buf, str); |
| |
| context->indentLevel += indentAfter; |
| if (context->indentLevel < 0) |
| context->indentLevel = 0; |
| } |
| else |
| appendStringInfoString(buf, str); |
| } |
| |
| /* |
| * removeStringInfoSpaces - delete trailing spaces from a buffer. |
| * |
| * Possibly this should move to stringinfo.c at some point. |
| */ |
| static void |
| removeStringInfoSpaces(StringInfo str) |
| { |
| while (str->len > 0 && str->data[str->len - 1] == ' ') |
| str->data[--(str->len)] = '\0'; |
| } |
| |
| |
| /* |
| * get_rule_expr_paren - deparse expr using get_rule_expr, |
| * embracing the string with parentheses if necessary for prettyPrint. |
| * |
| * Never embrace if prettyFlags=0, because it's done in the calling node. |
| * |
| * Any node that does *not* embrace its argument node by sql syntax (with |
| * parentheses, non-operator keywords like CASE/WHEN/ON, or comma etc) should |
| * use get_rule_expr_paren instead of get_rule_expr so parentheses can be |
| * added. |
| */ |
| static void |
| get_rule_expr_paren(Node *node, deparse_context *context, |
| bool showimplicit, Node *parentNode) |
| { |
| bool need_paren; |
| |
| need_paren = PRETTY_PAREN(context) && |
| !isSimpleNode(node, parentNode, context->prettyFlags); |
| |
| if (need_paren) |
| appendStringInfoChar(context->buf, '('); |
| |
| get_rule_expr(node, context, showimplicit); |
| |
| if (need_paren) |
| appendStringInfoChar(context->buf, ')'); |
| } |
| |
| |
| /* ---------- |
| * get_rule_expr - Parse back an expression |
| * |
| * Note: showimplicit determines whether we display any implicit cast that |
| * is present at the top of the expression tree. It is a passed argument, |
| * not a field of the context struct, because we change the value as we |
| * recurse down into the expression. In general we suppress implicit casts |
| * when the result type is known with certainty (eg, the arguments of an |
| * OR must be boolean). We display implicit casts for arguments of functions |
| * and operators, since this is needed to be certain that the same function |
| * or operator will be chosen when the expression is re-parsed. |
| * ---------- |
| */ |
| static void |
| get_rule_expr(Node *node, deparse_context *context, |
| bool showimplicit) |
| { |
| StringInfo buf = context->buf; |
| |
| if (node == NULL) |
| return; |
| |
| /* Guard against excessively long or deeply-nested queries */ |
| CHECK_FOR_INTERRUPTS(); |
| check_stack_depth(); |
| |
| /* |
| * Each level of get_rule_expr must emit an indivisible term |
| * (parenthesized if necessary) to ensure result is reparsed into the same |
| * expression tree. The only exception is that when the input is a List, |
| * we emit the component items comma-separated with no surrounding |
| * decoration; this is convenient for most callers. |
| */ |
| switch (nodeTag(node)) |
| { |
| case T_Var: |
| (void) get_variable((Var *) node, 0, false, context); |
| break; |
| |
| case T_Const: |
| get_const_expr((Const *) node, context, 0); |
| break; |
| |
| case T_Param: |
| get_parameter((Param *) node, context); |
| break; |
| |
| case T_Aggref: |
| get_agg_expr((Aggref *) node, context, (Aggref *) node); |
| break; |
| case T_DQAExpr: |
| get_dqa_expr((DQAExpr *) node, context); |
| break; |
| |
| case T_GroupingFunc: |
| { |
| GroupingFunc *gexpr = (GroupingFunc *) node; |
| |
| appendStringInfoString(buf, "GROUPING("); |
| get_rule_expr((Node *) gexpr->args, context, true); |
| appendStringInfoChar(buf, ')'); |
| } |
| break; |
| case T_GroupId: |
| appendStringInfo(buf, "GROUP_ID()"); |
| break; |
| |
| case T_GroupingSetId: |
| appendStringInfo(buf, "GROUPINGSET_ID()"); |
| break; |
| |
| case T_WindowFunc: |
| get_windowfunc_expr((WindowFunc *) node, context); |
| break; |
| |
| case T_SubscriptingRef: |
| { |
| SubscriptingRef *sbsref = (SubscriptingRef *) node; |
| bool need_parens; |
| |
| /* |
| * If the argument is a CaseTestExpr, we must be inside a |
| * FieldStore, ie, we are assigning to an element of an array |
| * within a composite column. Since we already punted on |
| * displaying the FieldStore's target information, just punt |
| * here too, and display only the assignment source |
| * expression. |
| */ |
| if (IsA(sbsref->refexpr, CaseTestExpr)) |
| { |
| Assert(sbsref->refassgnexpr); |
| get_rule_expr((Node *) sbsref->refassgnexpr, |
| context, showimplicit); |
| break; |
| } |
| |
| /* |
| * Parenthesize the argument unless it's a simple Var or a |
| * FieldSelect. (In particular, if it's another |
| * SubscriptingRef, we *must* parenthesize to avoid |
| * confusion.) |
| */ |
| need_parens = !IsA(sbsref->refexpr, Var) && |
| !IsA(sbsref->refexpr, FieldSelect); |
| if (need_parens) |
| appendStringInfoChar(buf, '('); |
| get_rule_expr((Node *) sbsref->refexpr, context, showimplicit); |
| if (need_parens) |
| appendStringInfoChar(buf, ')'); |
| |
| /* |
| * If there's a refassgnexpr, we want to print the node in the |
| * format "container[subscripts] := refassgnexpr". This is |
| * not legal SQL, so decompilation of INSERT or UPDATE |
| * statements should always use processIndirection as part of |
| * the statement-level syntax. We should only see this when |
| * EXPLAIN tries to print the targetlist of a plan resulting |
| * from such a statement. |
| */ |
| if (sbsref->refassgnexpr) |
| { |
| Node *refassgnexpr; |
| |
| /* |
| * Use processIndirection to print this node's subscripts |
| * as well as any additional field selections or |
| * subscripting in immediate descendants. It returns the |
| * RHS expr that is actually being "assigned". |
| */ |
| refassgnexpr = processIndirection(node, context); |
| appendStringInfoString(buf, " := "); |
| get_rule_expr(refassgnexpr, context, showimplicit); |
| } |
| else |
| { |
| /* Just an ordinary container fetch, so print subscripts */ |
| printSubscripts(sbsref, context); |
| } |
| } |
| break; |
| |
| case T_FuncExpr: |
| get_func_expr((FuncExpr *) node, context, showimplicit); |
| break; |
| |
| case T_NamedArgExpr: |
| { |
| NamedArgExpr *na = (NamedArgExpr *) node; |
| |
| appendStringInfo(buf, "%s => ", quote_identifier(na->name)); |
| get_rule_expr((Node *) na->arg, context, showimplicit); |
| } |
| break; |
| |
| case T_OpExpr: |
| get_oper_expr((OpExpr *) node, context); |
| break; |
| |
| case T_DistinctExpr: |
| { |
| DistinctExpr *expr = (DistinctExpr *) node; |
| List *args = expr->args; |
| Node *arg1 = (Node *) linitial(args); |
| Node *arg2 = (Node *) lsecond(args); |
| |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, '('); |
| get_rule_expr_paren(arg1, context, true, node); |
| appendStringInfoString(buf, " IS DISTINCT FROM "); |
| get_rule_expr_paren(arg2, context, true, node); |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, ')'); |
| } |
| break; |
| |
| case T_NullIfExpr: |
| { |
| NullIfExpr *nullifexpr = (NullIfExpr *) node; |
| |
| appendStringInfoString(buf, "NULLIF("); |
| get_rule_expr((Node *) nullifexpr->args, context, true); |
| appendStringInfoChar(buf, ')'); |
| } |
| break; |
| |
| case T_ScalarArrayOpExpr: |
| { |
| ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node; |
| List *args = expr->args; |
| Node *arg1 = (Node *) linitial(args); |
| Node *arg2 = (Node *) lsecond(args); |
| |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, '('); |
| get_rule_expr_paren(arg1, context, true, node); |
| appendStringInfo(buf, " %s %s (", |
| generate_operator_name(expr->opno, |
| exprType(arg1), |
| get_base_element_type(exprType(arg2))), |
| expr->useOr ? "ANY" : "ALL"); |
| get_rule_expr_paren(arg2, context, true, node); |
| |
| /* |
| * There's inherent ambiguity in "x op ANY/ALL (y)" when y is |
| * a bare sub-SELECT. Since we're here, the sub-SELECT must |
| * be meant as a scalar sub-SELECT yielding an array value to |
| * be used in ScalarArrayOpExpr; but the grammar will |
| * preferentially interpret such a construct as an ANY/ALL |
| * SubLink. To prevent misparsing the output that way, insert |
| * a dummy coercion (which will be stripped by parse analysis, |
| * so no inefficiency is added in dump and reload). This is |
| * indeed most likely what the user wrote to get the construct |
| * accepted in the first place. |
| */ |
| if (IsA(arg2, SubLink) && |
| ((SubLink *) arg2)->subLinkType == EXPR_SUBLINK) |
| appendStringInfo(buf, "::%s", |
| format_type_with_typemod(exprType(arg2), |
| exprTypmod(arg2))); |
| appendStringInfoChar(buf, ')'); |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, ')'); |
| } |
| break; |
| |
| case T_BoolExpr: |
| { |
| BoolExpr *expr = (BoolExpr *) node; |
| Node *first_arg = linitial(expr->args); |
| ListCell *arg; |
| |
| switch (expr->boolop) |
| { |
| case AND_EXPR: |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, '('); |
| get_rule_expr_paren(first_arg, context, |
| false, node); |
| for_each_from(arg, expr->args, 1) |
| { |
| appendStringInfoString(buf, " AND "); |
| get_rule_expr_paren((Node *) lfirst(arg), context, |
| false, node); |
| } |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, ')'); |
| break; |
| |
| case OR_EXPR: |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, '('); |
| get_rule_expr_paren(first_arg, context, |
| false, node); |
| for_each_from(arg, expr->args, 1) |
| { |
| appendStringInfoString(buf, " OR "); |
| get_rule_expr_paren((Node *) lfirst(arg), context, |
| false, node); |
| } |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, ')'); |
| break; |
| |
| case NOT_EXPR: |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, '('); |
| appendStringInfoString(buf, "NOT "); |
| get_rule_expr_paren(first_arg, context, |
| false, node); |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, ')'); |
| break; |
| |
| default: |
| elog(ERROR, "unrecognized boolop: %d", |
| (int) expr->boolop); |
| } |
| } |
| break; |
| |
| case T_SubLink: |
| get_sublink_expr((SubLink *) node, context); |
| break; |
| |
| case T_SubPlan: |
| { |
| SubPlan *subplan = (SubPlan *) node; |
| |
| /* |
| * We cannot see an already-planned subplan in rule deparsing, |
| * only while EXPLAINing a query plan. We don't try to |
| * reconstruct the original SQL, just reference the subplan |
| * that appears elsewhere in EXPLAIN's result. |
| */ |
| if (subplan->useHashTable) |
| appendStringInfo(buf, "(hashed %s)", subplan->plan_name); |
| else |
| appendStringInfo(buf, "(%s)", subplan->plan_name); |
| } |
| break; |
| |
| case T_AlternativeSubPlan: |
| { |
| AlternativeSubPlan *asplan = (AlternativeSubPlan *) node; |
| ListCell *lc; |
| |
| /* |
| * This case cannot be reached in normal usage, since no |
| * AlternativeSubPlan can appear either in parsetrees or |
| * finished plan trees. We keep it just in case somebody |
| * wants to use this code to print planner data structures. |
| */ |
| appendStringInfoString(buf, "(alternatives: "); |
| foreach(lc, asplan->subplans) |
| { |
| SubPlan *splan = lfirst_node(SubPlan, lc); |
| |
| if (splan->useHashTable) |
| appendStringInfo(buf, "hashed %s", splan->plan_name); |
| else |
| appendStringInfoString(buf, splan->plan_name); |
| if (lnext(asplan->subplans, lc)) |
| appendStringInfoString(buf, " or "); |
| } |
| appendStringInfoChar(buf, ')'); |
| } |
| break; |
| |
| case T_FieldSelect: |
| { |
| FieldSelect *fselect = (FieldSelect *) node; |
| Node *arg = (Node *) fselect->arg; |
| int fno = fselect->fieldnum; |
| const char *fieldname; |
| bool need_parens; |
| |
| /* |
| * Parenthesize the argument unless it's an SubscriptingRef or |
| * another FieldSelect. Note in particular that it would be |
| * WRONG to not parenthesize a Var argument; simplicity is not |
| * the issue here, having the right number of names is. |
| */ |
| need_parens = !IsA(arg, SubscriptingRef) && |
| !IsA(arg, FieldSelect); |
| if (need_parens) |
| appendStringInfoChar(buf, '('); |
| get_rule_expr(arg, context, true); |
| if (need_parens) |
| appendStringInfoChar(buf, ')'); |
| |
| /* |
| * Get and print the field name. |
| */ |
| fieldname = get_name_for_var_field((Var *) arg, fno, |
| 0, context); |
| appendStringInfo(buf, ".%s", quote_identifier(fieldname)); |
| } |
| break; |
| |
| case T_FieldStore: |
| { |
| FieldStore *fstore = (FieldStore *) node; |
| bool need_parens; |
| |
| /* |
| * There is no good way to represent a FieldStore as real SQL, |
| * so decompilation of INSERT or UPDATE statements should |
| * always use processIndirection as part of the |
| * statement-level syntax. We should only get here when |
| * EXPLAIN tries to print the targetlist of a plan resulting |
| * from such a statement. The plan case is even harder than |
| * ordinary rules would be, because the planner tries to |
| * collapse multiple assignments to the same field or subfield |
| * into one FieldStore; so we can see a list of target fields |
| * not just one, and the arguments could be FieldStores |
| * themselves. We don't bother to try to print the target |
| * field names; we just print the source arguments, with a |
| * ROW() around them if there's more than one. This isn't |
| * terribly complete, but it's probably good enough for |
| * EXPLAIN's purposes; especially since anything more would be |
| * either hopelessly confusing or an even poorer |
| * representation of what the plan is actually doing. |
| */ |
| need_parens = (list_length(fstore->newvals) != 1); |
| if (need_parens) |
| appendStringInfoString(buf, "ROW("); |
| get_rule_expr((Node *) fstore->newvals, context, showimplicit); |
| if (need_parens) |
| appendStringInfoChar(buf, ')'); |
| } |
| break; |
| |
| case T_RelabelType: |
| { |
| RelabelType *relabel = (RelabelType *) node; |
| Node *arg = (Node *) relabel->arg; |
| |
| if (relabel->relabelformat == COERCE_IMPLICIT_CAST && |
| !showimplicit) |
| { |
| /* don't show the implicit cast */ |
| get_rule_expr_paren(arg, context, false, node); |
| } |
| else |
| { |
| get_coercion_expr(arg, context, |
| relabel->resulttype, |
| relabel->resulttypmod, |
| node); |
| } |
| } |
| break; |
| |
| case T_CoerceViaIO: |
| { |
| CoerceViaIO *iocoerce = (CoerceViaIO *) node; |
| Node *arg = (Node *) iocoerce->arg; |
| |
| if (iocoerce->coerceformat == COERCE_IMPLICIT_CAST && |
| !showimplicit) |
| { |
| /* don't show the implicit cast */ |
| get_rule_expr_paren(arg, context, false, node); |
| } |
| else |
| { |
| get_coercion_expr(arg, context, |
| iocoerce->resulttype, |
| -1, |
| node); |
| } |
| } |
| break; |
| |
| case T_ArrayCoerceExpr: |
| { |
| ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node; |
| Node *arg = (Node *) acoerce->arg; |
| |
| if (acoerce->coerceformat == COERCE_IMPLICIT_CAST && |
| !showimplicit) |
| { |
| /* don't show the implicit cast */ |
| get_rule_expr_paren(arg, context, false, node); |
| } |
| else |
| { |
| get_coercion_expr(arg, context, |
| acoerce->resulttype, |
| acoerce->resulttypmod, |
| node); |
| } |
| } |
| break; |
| |
| case T_ConvertRowtypeExpr: |
| { |
| ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node; |
| Node *arg = (Node *) convert->arg; |
| |
| if (convert->convertformat == COERCE_IMPLICIT_CAST && |
| !showimplicit) |
| { |
| /* don't show the implicit cast */ |
| get_rule_expr_paren(arg, context, false, node); |
| } |
| else |
| { |
| get_coercion_expr(arg, context, |
| convert->resulttype, -1, |
| node); |
| } |
| } |
| break; |
| |
| case T_CollateExpr: |
| { |
| CollateExpr *collate = (CollateExpr *) node; |
| Node *arg = (Node *) collate->arg; |
| |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, '('); |
| get_rule_expr_paren(arg, context, showimplicit, node); |
| appendStringInfo(buf, " COLLATE %s", |
| generate_collation_name(collate->collOid)); |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, ')'); |
| } |
| break; |
| |
| case T_CaseExpr: |
| { |
| CaseExpr *caseexpr = (CaseExpr *) node; |
| ListCell *temp; |
| |
| appendContextKeyword(context, "CASE", |
| 0, PRETTYINDENT_VAR, 0); |
| if (caseexpr->arg) |
| { |
| appendStringInfoChar(buf, ' '); |
| get_rule_expr((Node *) caseexpr->arg, context, true); |
| } |
| foreach(temp, caseexpr->args) |
| { |
| CaseWhen *when = (CaseWhen *) lfirst(temp); |
| Node *w = (Node *) when->expr; |
| |
| if (caseexpr->arg) |
| { |
| /* |
| * The parser should have produced WHEN clauses of |
| * the form "CaseTestExpr = RHS", possibly with an |
| * implicit coercion inserted above the CaseTestExpr. |
| * For accurate decompilation of rules it's essential |
| * that we show just the RHS. However in an |
| * expression that's been through the optimizer, the |
| * WHEN clause could be almost anything (since the |
| * equality operator could have been expanded into an |
| * inline function). If we don't recognize the form |
| * of the WHEN clause, just punt and display it as-is. |
| */ |
| if (IsA(w, OpExpr)) |
| { |
| List *args = ((OpExpr *) w)->args; |
| |
| if (list_length(args) == 2 && |
| IsA(strip_implicit_coercions(linitial(args)), |
| CaseTestExpr)) |
| w = (Node *) lsecond(args); |
| } |
| } |
| |
| if (!PRETTY_INDENT(context)) |
| appendStringInfoChar(buf, ' '); |
| appendContextKeyword(context, "WHEN ", |
| 0, 0, 0); |
| |
| /* WHEN IS NOT DISTINCT FROM */ |
| if (is_notclause(w)) |
| { |
| Expr *arg = get_notclausearg((Expr *) w); |
| |
| if (IsA(arg, DistinctExpr)) |
| { |
| DistinctExpr *dexpr = (DistinctExpr *) arg; |
| Node *lhs = (Node *) linitial(dexpr->args); |
| Node *rhs = (Node *) lsecond(dexpr->args); |
| |
| /* |
| * If lhs contains CaseTestExpr node as placeholder, we should |
| * omit the lhs for dump |
| */ |
| if (!IsA(strip_implicit_coercions(lhs), CaseTestExpr)) |
| { |
| get_rule_expr(lhs, context, false); |
| appendStringInfoChar(buf, ' '); |
| } |
| appendStringInfoString(buf, "IS NOT DISTINCT FROM "); |
| get_rule_expr(rhs, context, false); |
| } |
| else |
| get_rule_expr(w, context, false); |
| } |
| else |
| get_rule_expr(w, context, false); |
| appendStringInfoString(buf, " THEN "); |
| get_rule_expr((Node *) when->result, context, true); |
| } |
| if (!PRETTY_INDENT(context)) |
| appendStringInfoChar(buf, ' '); |
| appendContextKeyword(context, "ELSE ", |
| 0, 0, 0); |
| get_rule_expr((Node *) caseexpr->defresult, context, true); |
| if (!PRETTY_INDENT(context)) |
| appendStringInfoChar(buf, ' '); |
| appendContextKeyword(context, "END", |
| -PRETTYINDENT_VAR, 0, 0); |
| } |
| break; |
| |
| case T_CaseTestExpr: |
| { |
| /* |
| * Normally we should never get here, since for expressions |
| * that can contain this node type we attempt to avoid |
| * recursing to it. But in an optimized expression we might |
| * be unable to avoid that (see comments for CaseExpr). If we |
| * do see one, print it as CASE_TEST_EXPR. |
| */ |
| appendStringInfoString(buf, "CASE_TEST_EXPR"); |
| } |
| break; |
| |
| case T_ArrayExpr: |
| { |
| ArrayExpr *arrayexpr = (ArrayExpr *) node; |
| |
| appendStringInfoString(buf, "ARRAY["); |
| get_rule_expr((Node *) arrayexpr->elements, context, true); |
| appendStringInfoChar(buf, ']'); |
| |
| /* |
| * If the array isn't empty, we assume its elements are |
| * coerced to the desired type. If it's empty, though, we |
| * need an explicit coercion to the array type. |
| */ |
| if (arrayexpr->elements == NIL) |
| appendStringInfo(buf, "::%s", |
| format_type_with_typemod(arrayexpr->array_typeid, -1)); |
| } |
| break; |
| |
| case T_TableValueExpr: |
| { |
| TableValueExpr *tabexpr = (TableValueExpr *) node; |
| Query *subquery = (Query*) tabexpr->subquery; |
| |
| appendStringInfo(buf, "TABLE("); |
| get_query_def(subquery, buf, context->namespaces, NULL, true, |
| context->prettyFlags, context->wrapColumn, |
| context->indentLevel); |
| appendStringInfoChar(buf, ')'); |
| } |
| break; |
| |
| case T_RowExpr: |
| { |
| RowExpr *rowexpr = (RowExpr *) node; |
| TupleDesc tupdesc = NULL; |
| ListCell *arg; |
| int i; |
| char *sep; |
| |
| /* |
| * If it's a named type and not RECORD, we may have to skip |
| * dropped columns and/or claim there are NULLs for added |
| * columns. |
| */ |
| if (rowexpr->row_typeid != RECORDOID) |
| { |
| tupdesc = lookup_rowtype_tupdesc(rowexpr->row_typeid, -1); |
| Assert(list_length(rowexpr->args) <= tupdesc->natts); |
| } |
| |
| /* |
| * SQL99 allows "ROW" to be omitted when there is more than |
| * one column, but for simplicity we always print it. |
| */ |
| appendStringInfoString(buf, "ROW("); |
| sep = ""; |
| i = 0; |
| foreach(arg, rowexpr->args) |
| { |
| Node *e = (Node *) lfirst(arg); |
| |
| if (tupdesc == NULL || |
| !TupleDescAttr(tupdesc, i)->attisdropped) |
| { |
| appendStringInfoString(buf, sep); |
| /* Whole-row Vars need special treatment here */ |
| get_rule_expr_toplevel(e, context, true); |
| sep = ", "; |
| } |
| i++; |
| } |
| if (tupdesc != NULL) |
| { |
| while (i < tupdesc->natts) |
| { |
| if (!TupleDescAttr(tupdesc, i)->attisdropped) |
| { |
| appendStringInfoString(buf, sep); |
| appendStringInfoString(buf, "NULL"); |
| sep = ", "; |
| } |
| i++; |
| } |
| |
| ReleaseTupleDesc(tupdesc); |
| } |
| appendStringInfoChar(buf, ')'); |
| if (rowexpr->row_format == COERCE_EXPLICIT_CAST) |
| appendStringInfo(buf, "::%s", |
| format_type_with_typemod(rowexpr->row_typeid, -1)); |
| } |
| break; |
| |
| case T_RowCompareExpr: |
| { |
| RowCompareExpr *rcexpr = (RowCompareExpr *) node; |
| |
| /* |
| * SQL99 allows "ROW" to be omitted when there is more than |
| * one column, but for simplicity we always print it. Within |
| * a ROW expression, whole-row Vars need special treatment, so |
| * use get_rule_list_toplevel. |
| */ |
| appendStringInfoString(buf, "(ROW("); |
| get_rule_list_toplevel(rcexpr->largs, context, true); |
| |
| /* |
| * We assume that the name of the first-column operator will |
| * do for all the rest too. This is definitely open to |
| * failure, eg if some but not all operators were renamed |
| * since the construct was parsed, but there seems no way to |
| * be perfect. |
| */ |
| appendStringInfo(buf, ") %s ROW(", |
| generate_operator_name(linitial_oid(rcexpr->opnos), |
| exprType(linitial(rcexpr->largs)), |
| exprType(linitial(rcexpr->rargs)))); |
| get_rule_list_toplevel(rcexpr->rargs, context, true); |
| appendStringInfoString(buf, "))"); |
| } |
| break; |
| |
| case T_CoalesceExpr: |
| { |
| CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; |
| |
| appendStringInfoString(buf, "COALESCE("); |
| get_rule_expr((Node *) coalesceexpr->args, context, true); |
| appendStringInfoChar(buf, ')'); |
| } |
| break; |
| |
| case T_MinMaxExpr: |
| { |
| MinMaxExpr *minmaxexpr = (MinMaxExpr *) node; |
| |
| switch (minmaxexpr->op) |
| { |
| case IS_GREATEST: |
| appendStringInfoString(buf, "GREATEST("); |
| break; |
| case IS_LEAST: |
| appendStringInfoString(buf, "LEAST("); |
| break; |
| } |
| get_rule_expr((Node *) minmaxexpr->args, context, true); |
| appendStringInfoChar(buf, ')'); |
| } |
| break; |
| |
| case T_SQLValueFunction: |
| { |
| SQLValueFunction *svf = (SQLValueFunction *) node; |
| |
| /* |
| * Note: this code knows that typmod for time, timestamp, and |
| * timestamptz just prints as integer. |
| */ |
| switch (svf->op) |
| { |
| case SVFOP_CURRENT_DATE: |
| appendStringInfoString(buf, "CURRENT_DATE"); |
| break; |
| case SVFOP_CURRENT_TIME: |
| appendStringInfoString(buf, "CURRENT_TIME"); |
| break; |
| case SVFOP_CURRENT_TIME_N: |
| appendStringInfo(buf, "CURRENT_TIME(%d)", svf->typmod); |
| break; |
| case SVFOP_CURRENT_TIMESTAMP: |
| appendStringInfoString(buf, "CURRENT_TIMESTAMP"); |
| break; |
| case SVFOP_CURRENT_TIMESTAMP_N: |
| appendStringInfo(buf, "CURRENT_TIMESTAMP(%d)", |
| svf->typmod); |
| break; |
| case SVFOP_LOCALTIME: |
| appendStringInfoString(buf, "LOCALTIME"); |
| break; |
| case SVFOP_LOCALTIME_N: |
| appendStringInfo(buf, "LOCALTIME(%d)", svf->typmod); |
| break; |
| case SVFOP_LOCALTIMESTAMP: |
| appendStringInfoString(buf, "LOCALTIMESTAMP"); |
| break; |
| case SVFOP_LOCALTIMESTAMP_N: |
| appendStringInfo(buf, "LOCALTIMESTAMP(%d)", |
| svf->typmod); |
| break; |
| case SVFOP_CURRENT_ROLE: |
| appendStringInfoString(buf, "CURRENT_ROLE"); |
| break; |
| case SVFOP_CURRENT_USER: |
| appendStringInfoString(buf, "CURRENT_USER"); |
| break; |
| case SVFOP_USER: |
| appendStringInfoString(buf, "USER"); |
| break; |
| case SVFOP_SESSION_USER: |
| appendStringInfoString(buf, "SESSION_USER"); |
| break; |
| case SVFOP_CURRENT_CATALOG: |
| appendStringInfoString(buf, "CURRENT_CATALOG"); |
| break; |
| case SVFOP_CURRENT_SCHEMA: |
| appendStringInfoString(buf, "CURRENT_SCHEMA"); |
| break; |
| } |
| } |
| break; |
| |
| case T_XmlExpr: |
| { |
| XmlExpr *xexpr = (XmlExpr *) node; |
| bool needcomma = false; |
| ListCell *arg; |
| ListCell *narg; |
| Const *con; |
| |
| switch (xexpr->op) |
| { |
| case IS_XMLCONCAT: |
| appendStringInfoString(buf, "XMLCONCAT("); |
| break; |
| case IS_XMLELEMENT: |
| appendStringInfoString(buf, "XMLELEMENT("); |
| break; |
| case IS_XMLFOREST: |
| appendStringInfoString(buf, "XMLFOREST("); |
| break; |
| case IS_XMLPARSE: |
| appendStringInfoString(buf, "XMLPARSE("); |
| break; |
| case IS_XMLPI: |
| appendStringInfoString(buf, "XMLPI("); |
| break; |
| case IS_XMLROOT: |
| appendStringInfoString(buf, "XMLROOT("); |
| break; |
| case IS_XMLSERIALIZE: |
| appendStringInfoString(buf, "XMLSERIALIZE("); |
| break; |
| case IS_DOCUMENT: |
| break; |
| } |
| if (xexpr->op == IS_XMLPARSE || xexpr->op == IS_XMLSERIALIZE) |
| { |
| if (xexpr->xmloption == XMLOPTION_DOCUMENT) |
| appendStringInfoString(buf, "DOCUMENT "); |
| else |
| appendStringInfoString(buf, "CONTENT "); |
| } |
| if (xexpr->name) |
| { |
| appendStringInfo(buf, "NAME %s", |
| quote_identifier(map_xml_name_to_sql_identifier(xexpr->name))); |
| needcomma = true; |
| } |
| if (xexpr->named_args) |
| { |
| if (xexpr->op != IS_XMLFOREST) |
| { |
| if (needcomma) |
| appendStringInfoString(buf, ", "); |
| appendStringInfoString(buf, "XMLATTRIBUTES("); |
| needcomma = false; |
| } |
| forboth(arg, xexpr->named_args, narg, xexpr->arg_names) |
| { |
| Node *e = (Node *) lfirst(arg); |
| char *argname = strVal(lfirst(narg)); |
| |
| if (needcomma) |
| appendStringInfoString(buf, ", "); |
| get_rule_expr((Node *) e, context, true); |
| appendStringInfo(buf, " AS %s", |
| quote_identifier(map_xml_name_to_sql_identifier(argname))); |
| needcomma = true; |
| } |
| if (xexpr->op != IS_XMLFOREST) |
| appendStringInfoChar(buf, ')'); |
| } |
| if (xexpr->args) |
| { |
| if (needcomma) |
| appendStringInfoString(buf, ", "); |
| switch (xexpr->op) |
| { |
| case IS_XMLCONCAT: |
| case IS_XMLELEMENT: |
| case IS_XMLFOREST: |
| case IS_XMLPI: |
| case IS_XMLSERIALIZE: |
| /* no extra decoration needed */ |
| get_rule_expr((Node *) xexpr->args, context, true); |
| break; |
| case IS_XMLPARSE: |
| Assert(list_length(xexpr->args) == 2); |
| |
| get_rule_expr((Node *) linitial(xexpr->args), |
| context, true); |
| |
| con = lsecond_node(Const, xexpr->args); |
| Assert(!con->constisnull); |
| if (DatumGetBool(con->constvalue)) |
| appendStringInfoString(buf, |
| " PRESERVE WHITESPACE"); |
| else |
| appendStringInfoString(buf, |
| " STRIP WHITESPACE"); |
| break; |
| case IS_XMLROOT: |
| Assert(list_length(xexpr->args) == 3); |
| |
| get_rule_expr((Node *) linitial(xexpr->args), |
| context, true); |
| |
| appendStringInfoString(buf, ", VERSION "); |
| con = (Const *) lsecond(xexpr->args); |
| if (IsA(con, Const) && |
| con->constisnull) |
| appendStringInfoString(buf, "NO VALUE"); |
| else |
| get_rule_expr((Node *) con, context, false); |
| |
| con = lthird_node(Const, xexpr->args); |
| if (con->constisnull) |
| /* suppress STANDALONE NO VALUE */ ; |
| else |
| { |
| switch (DatumGetInt32(con->constvalue)) |
| { |
| case XML_STANDALONE_YES: |
| appendStringInfoString(buf, |
| ", STANDALONE YES"); |
| break; |
| case XML_STANDALONE_NO: |
| appendStringInfoString(buf, |
| ", STANDALONE NO"); |
| break; |
| case XML_STANDALONE_NO_VALUE: |
| appendStringInfoString(buf, |
| ", STANDALONE NO VALUE"); |
| break; |
| default: |
| break; |
| } |
| } |
| break; |
| case IS_DOCUMENT: |
| get_rule_expr_paren((Node *) xexpr->args, context, false, node); |
| break; |
| } |
| |
| } |
| if (xexpr->op == IS_XMLSERIALIZE) |
| appendStringInfo(buf, " AS %s", |
| format_type_with_typemod(xexpr->type, |
| xexpr->typmod)); |
| if (xexpr->op == IS_DOCUMENT) |
| appendStringInfoString(buf, " IS DOCUMENT"); |
| else |
| appendStringInfoChar(buf, ')'); |
| } |
| break; |
| |
| case T_NullTest: |
| { |
| NullTest *ntest = (NullTest *) node; |
| |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, '('); |
| get_rule_expr_paren((Node *) ntest->arg, context, true, node); |
| |
| /* |
| * For scalar inputs, we prefer to print as IS [NOT] NULL, |
| * which is shorter and traditional. If it's a rowtype input |
| * but we're applying a scalar test, must print IS [NOT] |
| * DISTINCT FROM NULL to be semantically correct. |
| */ |
| if (ntest->argisrow || |
| !type_is_rowtype(exprType((Node *) ntest->arg))) |
| { |
| switch (ntest->nulltesttype) |
| { |
| case IS_NULL: |
| appendStringInfoString(buf, " IS NULL"); |
| break; |
| case IS_NOT_NULL: |
| appendStringInfoString(buf, " IS NOT NULL"); |
| break; |
| default: |
| elog(ERROR, "unrecognized nulltesttype: %d", |
| (int) ntest->nulltesttype); |
| } |
| } |
| else |
| { |
| switch (ntest->nulltesttype) |
| { |
| case IS_NULL: |
| appendStringInfoString(buf, " IS NOT DISTINCT FROM NULL"); |
| break; |
| case IS_NOT_NULL: |
| appendStringInfoString(buf, " IS DISTINCT FROM NULL"); |
| break; |
| default: |
| elog(ERROR, "unrecognized nulltesttype: %d", |
| (int) ntest->nulltesttype); |
| } |
| } |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, ')'); |
| } |
| break; |
| |
| case T_BooleanTest: |
| { |
| BooleanTest *btest = (BooleanTest *) node; |
| |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, '('); |
| get_rule_expr_paren((Node *) btest->arg, context, false, node); |
| switch (btest->booltesttype) |
| { |
| case IS_TRUE: |
| appendStringInfoString(buf, " IS TRUE"); |
| break; |
| case IS_NOT_TRUE: |
| appendStringInfoString(buf, " IS NOT TRUE"); |
| break; |
| case IS_FALSE: |
| appendStringInfoString(buf, " IS FALSE"); |
| break; |
| case IS_NOT_FALSE: |
| appendStringInfoString(buf, " IS NOT FALSE"); |
| break; |
| case IS_UNKNOWN: |
| appendStringInfoString(buf, " IS UNKNOWN"); |
| break; |
| case IS_NOT_UNKNOWN: |
| appendStringInfoString(buf, " IS NOT UNKNOWN"); |
| break; |
| default: |
| elog(ERROR, "unrecognized booltesttype: %d", |
| (int) btest->booltesttype); |
| } |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, ')'); |
| } |
| break; |
| |
| case T_CoerceToDomain: |
| { |
| CoerceToDomain *ctest = (CoerceToDomain *) node; |
| Node *arg = (Node *) ctest->arg; |
| |
| if (ctest->coercionformat == COERCE_IMPLICIT_CAST && |
| !showimplicit) |
| { |
| /* don't show the implicit cast */ |
| get_rule_expr(arg, context, false); |
| } |
| else |
| { |
| get_coercion_expr(arg, context, |
| ctest->resulttype, |
| ctest->resulttypmod, |
| node); |
| } |
| } |
| break; |
| |
| case T_CoerceToDomainValue: |
| appendStringInfoString(buf, "VALUE"); |
| break; |
| |
| case T_SetToDefault: |
| appendStringInfoString(buf, "DEFAULT"); |
| break; |
| |
| case T_CurrentOfExpr: |
| { |
| CurrentOfExpr *cexpr = (CurrentOfExpr *) node; |
| |
| if (cexpr->cursor_name) |
| appendStringInfo(buf, "CURRENT OF %s", |
| quote_identifier(cexpr->cursor_name)); |
| else |
| appendStringInfo(buf, "CURRENT OF $%d", |
| cexpr->cursor_param); |
| } |
| break; |
| |
| case T_NextValueExpr: |
| { |
| NextValueExpr *nvexpr = (NextValueExpr *) node; |
| |
| /* |
| * This isn't exactly nextval(), but that seems close enough |
| * for EXPLAIN's purposes. |
| */ |
| appendStringInfoString(buf, "nextval("); |
| simple_quote_literal(buf, |
| generate_relation_name(nvexpr->seqid, |
| NIL)); |
| appendStringInfoChar(buf, ')'); |
| } |
| break; |
| |
| case T_InferenceElem: |
| { |
| InferenceElem *iexpr = (InferenceElem *) node; |
| bool save_varprefix; |
| bool need_parens; |
| |
| /* |
| * InferenceElem can only refer to target relation, so a |
| * prefix is not useful, and indeed would cause parse errors. |
| */ |
| save_varprefix = context->varprefix; |
| context->varprefix = false; |
| |
| /* |
| * Parenthesize the element unless it's a simple Var or a bare |
| * function call. Follows pg_get_indexdef_worker(). |
| */ |
| need_parens = !IsA(iexpr->expr, Var); |
| if (IsA(iexpr->expr, FuncExpr) && |
| ((FuncExpr *) iexpr->expr)->funcformat == |
| COERCE_EXPLICIT_CALL) |
| need_parens = false; |
| |
| if (need_parens) |
| appendStringInfoChar(buf, '('); |
| get_rule_expr((Node *) iexpr->expr, |
| context, false); |
| if (need_parens) |
| appendStringInfoChar(buf, ')'); |
| |
| context->varprefix = save_varprefix; |
| |
| if (iexpr->infercollid) |
| appendStringInfo(buf, " COLLATE %s", |
| generate_collation_name(iexpr->infercollid)); |
| |
| /* Add the operator class name, if not default */ |
| if (iexpr->inferopclass) |
| { |
| Oid inferopclass = iexpr->inferopclass; |
| Oid inferopcinputtype = get_opclass_input_type(iexpr->inferopclass); |
| |
| get_opclass_name(inferopclass, inferopcinputtype, buf); |
| } |
| } |
| break; |
| |
| case T_PartitionBoundSpec: |
| { |
| PartitionBoundSpec *spec = (PartitionBoundSpec *) node; |
| ListCell *cell; |
| char *sep; |
| |
| if (spec->is_default) |
| { |
| appendStringInfoString(buf, "DEFAULT"); |
| break; |
| } |
| |
| switch (spec->strategy) |
| { |
| case PARTITION_STRATEGY_HASH: |
| Assert(spec->modulus > 0 && spec->remainder >= 0); |
| Assert(spec->modulus > spec->remainder); |
| |
| appendStringInfoString(buf, "FOR VALUES"); |
| appendStringInfo(buf, " WITH (modulus %d, remainder %d)", |
| spec->modulus, spec->remainder); |
| break; |
| |
| case PARTITION_STRATEGY_LIST: |
| Assert(spec->listdatums != NIL); |
| |
| appendStringInfoString(buf, "FOR VALUES IN ("); |
| sep = ""; |
| foreach(cell, spec->listdatums) |
| { |
| Const *val = castNode(Const, lfirst(cell)); |
| |
| appendStringInfoString(buf, sep); |
| get_const_expr(val, context, -1); |
| sep = ", "; |
| } |
| |
| appendStringInfoChar(buf, ')'); |
| break; |
| |
| case PARTITION_STRATEGY_RANGE: |
| Assert(spec->lowerdatums != NIL && |
| spec->upperdatums != NIL && |
| list_length(spec->lowerdatums) == |
| list_length(spec->upperdatums)); |
| |
| appendStringInfo(buf, "FOR VALUES FROM %s TO %s", |
| get_range_partbound_string(spec->lowerdatums), |
| get_range_partbound_string(spec->upperdatums)); |
| break; |
| |
| default: |
| elog(ERROR, "unrecognized partition strategy: %d", |
| (int) spec->strategy); |
| break; |
| } |
| } |
| break; |
| |
| case T_List: |
| { |
| char *sep; |
| ListCell *l; |
| |
| sep = ""; |
| foreach(l, (List *) node) |
| { |
| appendStringInfoString(buf, sep); |
| get_rule_expr((Node *) lfirst(l), context, showimplicit); |
| sep = ", "; |
| } |
| } |
| break; |
| |
| case T_TableFunc: |
| get_tablefunc((TableFunc *) node, context, showimplicit); |
| break; |
| |
| case T_DMLActionExpr: |
| appendStringInfo(buf, "DMLAction"); |
| break; |
| |
| case T_AggExprId: |
| appendStringInfo(buf, "AggExprId"); |
| break; |
| |
| case T_RowIdExpr: |
| { |
| appendStringInfo(buf, "RowIdExpr"); |
| } |
| break; |
| |
| case T_GpPartitionDefinition: |
| { |
| GpPartitionDefinition *def = (GpPartitionDefinition*)node; |
| ListCell *lc; |
| char *sep = ""; |
| ParseState *pstate = make_parsestate(NULL); |
| |
| if (def->isTemplate) |
| appendStringInfo(buf, "SUBPARTITION TEMPLATE("); |
| |
| foreach(lc, def->partDefElems) |
| { |
| if (IsA(lfirst(lc), GpPartDefElem)) |
| { |
| GpPartDefElem *elem = lfirst_node(GpPartDefElem, lc); |
| |
| if (elem->colencs) |
| elog(ERROR, "Partition specific ENCODING clause not supported yet"); |
| |
| appendStringInfoString(buf, sep); |
| sep = ", "; |
| if (elem->partName) |
| { |
| if (elem->isDefault) |
| appendStringInfo(buf, "DEFAULT SUBPARTITION %s", elem->partName); |
| else |
| appendStringInfo(buf, "SUBPARTITION %s", elem->partName); |
| } |
| |
| if (elem->boundSpec) |
| { |
| switch (nodeTag(elem->boundSpec)) |
| { |
| case T_GpPartitionRangeSpec: |
| { |
| GpPartitionRangeSpec *rspec = |
| (GpPartitionRangeSpec *) elem->boundSpec; |
| if (rspec->partStart) |
| { |
| Node *val = transformExpr(pstate, |
| (Node *)linitial(rspec->partStart->val), EXPR_KIND_VALUES); |
| Assert(rspec->partStart->edge == |
| PART_EDGE_INCLUSIVE); |
| appendStringInfo(buf, " START ("); |
| get_rule_expr(val, context, true); |
| appendStringInfo(buf, ")"); |
| } |
| if (rspec->partEnd) |
| { |
| Node *val = transformExpr(pstate, |
| (Node *)linitial(rspec->partEnd->val), EXPR_KIND_VALUES); |
| Assert(rspec->partEnd->edge == |
| PART_EDGE_EXCLUSIVE); |
| appendStringInfo(buf, " END ("); |
| get_rule_expr(val, context, true); |
| appendStringInfo(buf, ")"); |
| } |
| if (rspec->partEvery) |
| { |
| Node *val = transformExpr(pstate, |
| (Node *)linitial(rspec->partEvery), EXPR_KIND_VALUES); |
| |
| appendStringInfo(buf, " Every ("); |
| get_rule_expr(val, context, true); |
| appendStringInfo(buf, ")"); |
| } |
| } |
| break; |
| case T_GpPartitionListSpec: |
| { |
| GpPartitionListSpec *lspec = (GpPartitionListSpec *) elem->boundSpec; |
| ListCell *cell; |
| |
| appendStringInfoString(buf, " VALUES ("); |
| sep = ""; |
| foreach(cell, lspec->partValues) |
| { |
| Node *val = transformExpr(pstate, (Node *)linitial(lfirst(cell)), EXPR_KIND_VALUES); |
| |
| Assert(list_length(lfirst(cell)) == 1); |
| appendStringInfoString(buf, sep); |
| get_rule_expr(val, context, -1); |
| sep = ", "; |
| } |
| appendStringInfoChar(buf, ')'); |
| } |
| break; |
| default: |
| elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); |
| } |
| } |
| if (elem->options) |
| { |
| ListCell *cell; |
| |
| appendStringInfoString(buf, sep); |
| appendStringInfoString(buf, " WITH ("); |
| if (elem->accessMethod) |
| { |
| if (pg_strcasecmp(elem->accessMethod, "ao_row") != 0) |
| { |
| appendStringInfoString(buf, "appendonly=true, orientation=row"); |
| sep = ", "; |
| } |
| else if (pg_strcasecmp(elem->accessMethod, "ao_column") != 0) |
| { |
| appendStringInfoString(buf, "appendonly=true, orientation=column"); |
| sep = ", "; |
| } |
| } |
| foreach (cell, elem->options) |
| { |
| DefElem *el = lfirst_node(DefElem, cell); |
| char *arg; |
| |
| appendStringInfoString(buf, sep); |
| appendStringInfo(buf, "%s=", el->defname); |
| appendStringInfo(buf, "%s", arg = defGetString(el)); |
| sep = ", "; |
| } |
| appendStringInfoChar(buf, ')'); |
| } |
| } |
| else if (IsA(lfirst(lc), ColumnReferenceStorageDirective)) |
| { |
| ListCell *cell; |
| ColumnReferenceStorageDirective *crsd = lfirst_node(ColumnReferenceStorageDirective, lc); |
| |
| appendStringInfoString(buf, sep); |
| if (crsd->deflt) |
| appendStringInfo(buf, "DEFAULT COLUMN ENCODING ("); |
| else |
| appendStringInfo(buf, "COLUMN %s ENCODING (", crsd->column); |
| |
| sep = ""; |
| foreach(cell, crsd->encoding) |
| { |
| DefElem *el = lfirst_node(DefElem, cell); |
| char *arg; |
| |
| appendStringInfoString(buf, sep); |
| appendStringInfo(buf, "%s=", el->defname); |
| appendStringInfo(buf, "%s", arg = defGetString(el)); |
| sep = ", "; |
| } |
| appendStringInfo(buf, ")"); |
| } |
| } |
| if (def->isTemplate) |
| appendStringInfo(buf, ")"); |
| } |
| break; |
| |
| default: |
| elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); |
| break; |
| } |
| } |
| |
| /* |
| * get_rule_expr_toplevel - Parse back a toplevel expression |
| * |
| * Same as get_rule_expr(), except that if the expr is just a Var, we pass |
| * istoplevel = true not false to get_variable(). This causes whole-row Vars |
| * to get printed with decoration that will prevent expansion of "*". |
| * We need to use this in contexts such as ROW() and VALUES(), where the |
| * parser would expand "foo.*" appearing at top level. (In principle we'd |
| * use this in get_target_list() too, but that has additional worries about |
| * whether to print AS, so it needs to invoke get_variable() directly anyway.) |
| */ |
| static void |
| get_rule_expr_toplevel(Node *node, deparse_context *context, |
| bool showimplicit) |
| { |
| if (node && IsA(node, Var)) |
| (void) get_variable((Var *) node, 0, true, context); |
| else |
| get_rule_expr(node, context, showimplicit); |
| } |
| |
| /* |
| * get_rule_list_toplevel - Parse back a list of toplevel expressions |
| * |
| * Apply get_rule_expr_toplevel() to each element of a List. |
| * |
| * This adds commas between the expressions, but caller is responsible |
| * for printing surrounding decoration. |
| */ |
| static void |
| get_rule_list_toplevel(List *lst, deparse_context *context, |
| bool showimplicit) |
| { |
| const char *sep; |
| ListCell *lc; |
| |
| sep = ""; |
| foreach(lc, lst) |
| { |
| Node *e = (Node *) lfirst(lc); |
| |
| appendStringInfoString(context->buf, sep); |
| get_rule_expr_toplevel(e, context, showimplicit); |
| sep = ", "; |
| } |
| } |
| |
| /* |
| * get_rule_expr_funccall - Parse back a function-call expression |
| * |
| * Same as get_rule_expr(), except that we guarantee that the output will |
| * look like a function call, or like one of the things the grammar treats as |
| * equivalent to a function call (see the func_expr_windowless production). |
| * This is needed in places where the grammar uses func_expr_windowless and |
| * you can't substitute a parenthesized a_expr. If what we have isn't going |
| * to look like a function call, wrap it in a dummy CAST() expression, which |
| * will satisfy the grammar --- and, indeed, is likely what the user wrote to |
| * produce such a thing. |
| */ |
| static void |
| get_rule_expr_funccall(Node *node, deparse_context *context, |
| bool showimplicit) |
| { |
| if (looks_like_function(node)) |
| get_rule_expr(node, context, showimplicit); |
| else |
| { |
| StringInfo buf = context->buf; |
| |
| appendStringInfoString(buf, "CAST("); |
| /* no point in showing any top-level implicit cast */ |
| get_rule_expr(node, context, false); |
| appendStringInfo(buf, " AS %s)", |
| format_type_with_typemod(exprType(node), |
| exprTypmod(node))); |
| } |
| } |
| |
| /* |
| * Helper function to identify node types that satisfy func_expr_windowless. |
| * If in doubt, "false" is always a safe answer. |
| */ |
| static bool |
| looks_like_function(Node *node) |
| { |
| if (node == NULL) |
| return false; /* probably shouldn't happen */ |
| switch (nodeTag(node)) |
| { |
| case T_FuncExpr: |
| /* OK, unless it's going to deparse as a cast */ |
| return (((FuncExpr *) node)->funcformat == COERCE_EXPLICIT_CALL || |
| ((FuncExpr *) node)->funcformat == COERCE_SQL_SYNTAX); |
| case T_NullIfExpr: |
| case T_CoalesceExpr: |
| case T_MinMaxExpr: |
| case T_SQLValueFunction: |
| case T_XmlExpr: |
| /* these are all accepted by func_expr_common_subexpr */ |
| return true; |
| default: |
| break; |
| } |
| return false; |
| } |
| |
| |
| /* |
| * get_oper_expr - Parse back an OpExpr node |
| */ |
| static void |
| get_oper_expr(OpExpr *expr, deparse_context *context) |
| { |
| StringInfo buf = context->buf; |
| Oid opno = expr->opno; |
| List *args = expr->args; |
| |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, '('); |
| if (list_length(args) == 2) |
| { |
| /* binary operator */ |
| Node *arg1 = (Node *) linitial(args); |
| Node *arg2 = (Node *) lsecond(args); |
| |
| get_rule_expr_paren(arg1, context, true, (Node *) expr); |
| appendStringInfo(buf, " %s ", |
| generate_operator_name(opno, |
| exprType(arg1), |
| exprType(arg2))); |
| get_rule_expr_paren(arg2, context, true, (Node *) expr); |
| } |
| else |
| { |
| /* prefix operator */ |
| Node *arg = (Node *) linitial(args); |
| |
| appendStringInfo(buf, "%s ", |
| generate_operator_name(opno, |
| InvalidOid, |
| exprType(arg))); |
| get_rule_expr_paren(arg, context, true, (Node *) expr); |
| } |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, ')'); |
| } |
| |
| /* |
| * get_func_expr - Parse back a FuncExpr node |
| */ |
| static void |
| get_func_expr(FuncExpr *expr, deparse_context *context, |
| bool showimplicit) |
| { |
| StringInfo buf = context->buf; |
| Oid funcoid = expr->funcid; |
| Oid argtypes[FUNC_MAX_ARGS]; |
| int nargs; |
| List *argnames; |
| bool use_variadic; |
| ListCell *l; |
| |
| /* |
| * If the function call came from an implicit coercion, then just show the |
| * first argument --- unless caller wants to see implicit coercions. |
| */ |
| if (expr->funcformat == COERCE_IMPLICIT_CAST && !showimplicit) |
| { |
| get_rule_expr_paren((Node *) linitial(expr->args), context, |
| false, (Node *) expr); |
| return; |
| } |
| |
| /* |
| * If the function call came from a cast, then show the first argument |
| * plus an explicit cast operation. |
| */ |
| if (expr->funcformat == COERCE_EXPLICIT_CAST || |
| expr->funcformat == COERCE_IMPLICIT_CAST) |
| { |
| Node *arg = linitial(expr->args); |
| Oid rettype = expr->funcresulttype; |
| int32 coercedTypmod; |
| |
| /* Get the typmod if this is a length-coercion function */ |
| (void) exprIsLengthCoercion((Node *) expr, &coercedTypmod); |
| |
| get_coercion_expr(arg, context, |
| rettype, coercedTypmod, |
| (Node *) expr); |
| |
| return; |
| } |
| |
| /* |
| * If the function was called using one of the SQL spec's random special |
| * syntaxes, try to reproduce that. If we don't recognize the function, |
| * fall through. |
| */ |
| if (expr->funcformat == COERCE_SQL_SYNTAX) |
| { |
| if (get_func_sql_syntax(expr, context)) |
| return; |
| } |
| |
| /* |
| * Normal function: display as proname(args). First we need to extract |
| * the argument datatypes. |
| */ |
| if (list_length(expr->args) > FUNC_MAX_ARGS) |
| ereport(ERROR, |
| (errcode(ERRCODE_TOO_MANY_ARGUMENTS), |
| errmsg("too many arguments"))); |
| nargs = 0; |
| argnames = NIL; |
| foreach(l, expr->args) |
| { |
| Node *arg = (Node *) lfirst(l); |
| |
| if (IsA(arg, NamedArgExpr)) |
| argnames = lappend(argnames, ((NamedArgExpr *) arg)->name); |
| argtypes[nargs] = exprType(arg); |
| nargs++; |
| } |
| |
| appendStringInfo(buf, "%s(", |
| generate_function_name(funcoid, nargs, |
| argnames, argtypes, |
| expr->funcvariadic, |
| &use_variadic, |
| context->special_exprkind)); |
| nargs = 0; |
| foreach(l, expr->args) |
| { |
| if (nargs++ > 0) |
| appendStringInfoString(buf, ", "); |
| if (use_variadic && lnext(expr->args, l) == NULL) |
| appendStringInfoString(buf, "VARIADIC "); |
| get_rule_expr((Node *) lfirst(l), context, true); |
| } |
| appendStringInfoChar(buf, ')'); |
| } |
| |
| /* |
| * Deparse an Aggref as a special MEDIAN() construct, if it looks like |
| * one. |
| * |
| * Returns true if the reference was handled as MEDIAN, false otherwise. |
| */ |
| static bool |
| get_median_expr(Aggref *aggref, deparse_context *context) |
| { |
| StringInfo buf = context->buf; |
| TargetEntry *tle; |
| |
| if (!IS_MEDIAN_OID(aggref->aggfnoid)) |
| return false; |
| if (list_length(aggref->aggdirectargs) != 1) |
| return false; |
| if (list_length(aggref->args) != 1) |
| return false; |
| |
| tle = (TargetEntry *) linitial(aggref->args); |
| if (tle->resjunk) |
| return false; |
| |
| /* Ok, it looks like a MEDIAN */ |
| appendStringInfoString(buf, "MEDIAN("); |
| |
| get_rule_expr((Node *) tle->expr, context, false); |
| |
| /* |
| * MEDIAN (...) FILTER (...) isn't currently allowed by the grammar, |
| * but it's easy enough to handle here, so let's be prepared. |
| */ |
| if (aggref->aggfilter != NULL) |
| { |
| appendStringInfoString(buf, ") FILTER (WHERE "); |
| get_rule_expr((Node *) aggref->aggfilter, context, false); |
| } |
| appendStringInfoChar(buf, ')'); |
| |
| return true; |
| } |
| |
| static void |
| get_dqa_expr(DQAExpr *dqa_expr,deparse_context *context) |
| { |
| int id = -1; |
| StringInfo buf = context->buf; |
| Bitmapset *bm = dqa_expr->agg_args_id_bms; |
| deparse_namespace *dnps = (deparse_namespace *) linitial(context->namespaces); |
| |
| resetStringInfo(buf); |
| appendStringInfoChar(buf, '('); |
| while ((id = bms_next_member(bm, id)) >= 0) |
| { |
| TargetEntry *te = get_sortgroupref_tle((Index)id, dnps->plan->targetlist); |
| char *exprstr; |
| |
| if (!te) |
| elog(ERROR, "no tlist entry for sort key: %d", id); |
| /* Deparse the expression, showing any top-level cast */ |
| exprstr = deparse_expression((Node *) te->expr, context->namespaces, |
| context->varprefix, true); |
| |
| appendStringInfoString(buf, exprstr); |
| appendStringInfoChar(buf, ','); |
| } |
| buf->data[buf->len - 1] = ')'; |
| |
| if (dqa_expr->agg_filter != NULL) |
| { |
| appendStringInfoString(buf, " FILTER (WHERE "); |
| get_rule_expr((Node *) dqa_expr->agg_filter, context, false); |
| appendStringInfoChar(buf, ')'); |
| } |
| |
| } |
| |
| /* |
| * get_agg_expr - Parse back an Aggref node |
| */ |
| static void |
| get_agg_expr(Aggref *aggref, deparse_context *context, |
| Aggref *original_aggref) |
| { |
| StringInfo buf = context->buf; |
| Oid argtypes[FUNC_MAX_ARGS]; |
| int nargs; |
| bool use_variadic; |
| |
| /* Special handling of MEDIAN */ |
| if (get_median_expr(aggref, context)) |
| return; |
| |
| /* |
| * For a combining aggregate, we look up and deparse the corresponding |
| * partial aggregate instead. This is necessary because our input |
| * argument list has been replaced; the new argument list always has just |
| * one element, which will point to a partial Aggref that supplies us with |
| * transition states to combine. |
| */ |
| if (DO_AGGSPLIT_COMBINE(aggref->aggsplit)) |
| { |
| TargetEntry *tle; |
| |
| Assert(list_length(aggref->args) == 1); |
| tle = linitial_node(TargetEntry, aggref->args); |
| resolve_special_varno((Node *) tle->expr, context, |
| get_agg_combine_expr, original_aggref); |
| return; |
| } |
| |
| /* |
| * Mark as PARTIAL, if appropriate. We look to the original aggref so as |
| * to avoid printing this when recursing from the code just above. |
| */ |
| if (DO_AGGSPLIT_SKIPFINAL(original_aggref->aggsplit)) |
| appendStringInfoString(buf, "PARTIAL "); |
| |
| /* Extract the argument types as seen by the parser */ |
| nargs = get_aggregate_argtypes(aggref, argtypes); |
| |
| /* Print the aggregate name, schema-qualified if needed */ |
| appendStringInfo(buf, "%s(%s", |
| generate_function_name(aggref->aggfnoid, nargs, |
| NIL, argtypes, |
| aggref->aggvariadic, |
| &use_variadic, |
| context->special_exprkind), |
| (aggref->aggdistinct != NIL) ? "DISTINCT " : ""); |
| |
| if (AGGKIND_IS_ORDERED_SET(aggref->aggkind)) |
| { |
| /* |
| * Ordered-set aggregates do not use "*" syntax. Also, we needn't |
| * worry about inserting VARIADIC. So we can just dump the direct |
| * args as-is. |
| */ |
| Assert(!aggref->aggvariadic); |
| get_rule_expr((Node *) aggref->aggdirectargs, context, true); |
| Assert(aggref->aggorder != NIL); |
| appendStringInfoString(buf, ") WITHIN GROUP (ORDER BY "); |
| get_rule_orderby(aggref->aggorder, aggref->args, false, context); |
| } |
| else |
| { |
| /* aggstar can be set only in zero-argument aggregates */ |
| if (aggref->aggstar) |
| appendStringInfoChar(buf, '*'); |
| else |
| { |
| ListCell *l; |
| int i; |
| |
| i = 0; |
| foreach(l, aggref->args) |
| { |
| TargetEntry *tle = (TargetEntry *) lfirst(l); |
| Node *arg = (Node *) tle->expr; |
| |
| Assert(!IsA(arg, NamedArgExpr)); |
| if (tle->resjunk) |
| continue; |
| if (i++ > 0) |
| appendStringInfoString(buf, ", "); |
| if (use_variadic && i == nargs) |
| appendStringInfoString(buf, "VARIADIC "); |
| get_rule_expr(arg, context, true); |
| } |
| } |
| |
| if (aggref->aggorder != NIL) |
| { |
| appendStringInfoString(buf, " ORDER BY "); |
| get_rule_orderby(aggref->aggorder, aggref->args, false, context); |
| } |
| } |
| |
| if (aggref->aggfilter != NULL) |
| { |
| appendStringInfoString(buf, ") FILTER (WHERE "); |
| get_rule_expr((Node *) aggref->aggfilter, context, false); |
| } |
| |
| appendStringInfoChar(buf, ')'); |
| } |
| |
| /* |
| * This is a helper function for get_agg_expr(). It's used when we deparse |
| * a combining Aggref; resolve_special_varno locates the corresponding partial |
| * Aggref and then calls this. |
| */ |
| static void |
| get_agg_combine_expr(Node *node, deparse_context *context, void *callback_arg) |
| { |
| Aggref *aggref; |
| Aggref *original_aggref = callback_arg; |
| |
| if (!IsA(node, Aggref)) |
| elog(ERROR, "combining Aggref does not point to an Aggref"); |
| |
| aggref = (Aggref *) node; |
| get_agg_expr(aggref, context, original_aggref); |
| } |
| |
| /* |
| * get_windowfunc_expr - Parse back a WindowFunc node |
| */ |
| static void |
| get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context) |
| { |
| StringInfo buf = context->buf; |
| Oid argtypes[FUNC_MAX_ARGS]; |
| int nargs; |
| List *argnames; |
| ListCell *l; |
| |
| if (list_length(wfunc->args) >= FUNC_MAX_ARGS) |
| ereport(ERROR, |
| (errcode(ERRCODE_TOO_MANY_ARGUMENTS), |
| errmsg("too many arguments"))); |
| nargs = 0; |
| argnames = NIL; |
| foreach(l, wfunc->args) |
| { |
| Node *arg = (Node *) lfirst(l); |
| |
| if (IsA(arg, NamedArgExpr)) |
| argnames = lappend(argnames, ((NamedArgExpr *) arg)->name); |
| argtypes[nargs] = exprType(arg); |
| nargs++; |
| } |
| |
| appendStringInfo(buf, "%s(%s", |
| generate_function_name(wfunc->winfnoid, nargs, |
| argnames, argtypes, |
| false, NULL, |
| context->special_exprkind), |
| wfunc->windistinct ? "DISTINCT " : ""); |
| /* winstar can be set only in zero-argument aggregates */ |
| if (wfunc->winstar) |
| appendStringInfoChar(buf, '*'); |
| else |
| get_rule_expr((Node *) wfunc->args, context, true); |
| |
| if (wfunc->aggfilter != NULL) |
| { |
| appendStringInfoString(buf, ") FILTER (WHERE "); |
| get_rule_expr((Node *) wfunc->aggfilter, context, false); |
| } |
| |
| appendStringInfoString(buf, ") OVER "); |
| |
| foreach(l, context->windowClause) |
| { |
| WindowClause *wc = (WindowClause *) lfirst(l); |
| |
| if (wc->winref == wfunc->winref) |
| { |
| if (wc->name) |
| appendStringInfoString(buf, quote_identifier(wc->name)); |
| else |
| get_rule_windowspec(wc, context->windowTList, context); |
| break; |
| } |
| } |
| if (l == NULL) |
| { |
| if (context->windowClause) |
| elog(ERROR, "could not find window clause for winref %u", |
| wfunc->winref); |
| |
| /* |
| * In EXPLAIN, we don't have window context information available, so |
| * we have to settle for this: |
| */ |
| appendStringInfoString(buf, "(?)"); |
| } |
| } |
| |
| /* |
| * get_func_sql_syntax - Parse back a SQL-syntax function call |
| * |
| * Returns true if we successfully deparsed, false if we did not |
| * recognize the function. |
| */ |
| static bool |
| get_func_sql_syntax(FuncExpr *expr, deparse_context *context) |
| { |
| StringInfo buf = context->buf; |
| Oid funcoid = expr->funcid; |
| |
| switch (funcoid) |
| { |
| case F_TIMEZONE_INTERVAL_TIMESTAMP: |
| case F_TIMEZONE_INTERVAL_TIMESTAMPTZ: |
| case F_TIMEZONE_INTERVAL_TIMETZ: |
| case F_TIMEZONE_TEXT_TIMESTAMP: |
| case F_TIMEZONE_TEXT_TIMESTAMPTZ: |
| case F_TIMEZONE_TEXT_TIMETZ: |
| /* AT TIME ZONE ... note reversed argument order */ |
| appendStringInfoChar(buf, '('); |
| get_rule_expr((Node *) lsecond(expr->args), context, false); |
| appendStringInfoString(buf, " AT TIME ZONE "); |
| get_rule_expr((Node *) linitial(expr->args), context, false); |
| appendStringInfoChar(buf, ')'); |
| return true; |
| |
| case F_OVERLAPS_TIMESTAMPTZ_INTERVAL_TIMESTAMPTZ_INTERVAL: |
| case F_OVERLAPS_TIMESTAMPTZ_INTERVAL_TIMESTAMPTZ_TIMESTAMPTZ: |
| case F_OVERLAPS_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ_INTERVAL: |
| case F_OVERLAPS_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ: |
| case F_OVERLAPS_TIMESTAMP_INTERVAL_TIMESTAMP_INTERVAL: |
| case F_OVERLAPS_TIMESTAMP_INTERVAL_TIMESTAMP_TIMESTAMP: |
| case F_OVERLAPS_TIMESTAMP_TIMESTAMP_TIMESTAMP_INTERVAL: |
| case F_OVERLAPS_TIMESTAMP_TIMESTAMP_TIMESTAMP_TIMESTAMP: |
| case F_OVERLAPS_TIMETZ_TIMETZ_TIMETZ_TIMETZ: |
| case F_OVERLAPS_TIME_INTERVAL_TIME_INTERVAL: |
| case F_OVERLAPS_TIME_INTERVAL_TIME_TIME: |
| case F_OVERLAPS_TIME_TIME_TIME_INTERVAL: |
| case F_OVERLAPS_TIME_TIME_TIME_TIME: |
| /* (x1, x2) OVERLAPS (y1, y2) */ |
| appendStringInfoString(buf, "(("); |
| get_rule_expr((Node *) linitial(expr->args), context, false); |
| appendStringInfoString(buf, ", "); |
| get_rule_expr((Node *) lsecond(expr->args), context, false); |
| appendStringInfoString(buf, ") OVERLAPS ("); |
| get_rule_expr((Node *) lthird(expr->args), context, false); |
| appendStringInfoString(buf, ", "); |
| get_rule_expr((Node *) lfourth(expr->args), context, false); |
| appendStringInfoString(buf, "))"); |
| return true; |
| |
| case F_EXTRACT_TEXT_DATE: |
| case F_EXTRACT_TEXT_TIME: |
| case F_EXTRACT_TEXT_TIMETZ: |
| case F_EXTRACT_TEXT_TIMESTAMP: |
| case F_EXTRACT_TEXT_TIMESTAMPTZ: |
| case F_EXTRACT_TEXT_INTERVAL: |
| /* EXTRACT (x FROM y) */ |
| appendStringInfoString(buf, "EXTRACT("); |
| { |
| Const *con = (Const *) linitial(expr->args); |
| |
| Assert(IsA(con, Const) && |
| con->consttype == TEXTOID && |
| !con->constisnull); |
| appendStringInfoString(buf, TextDatumGetCString(con->constvalue)); |
| } |
| appendStringInfoString(buf, " FROM "); |
| get_rule_expr((Node *) lsecond(expr->args), context, false); |
| appendStringInfoChar(buf, ')'); |
| return true; |
| |
| case F_IS_NORMALIZED: |
| /* IS xxx NORMALIZED */ |
| appendStringInfoString(buf, "(("); |
| get_rule_expr((Node *) linitial(expr->args), context, false); |
| appendStringInfoString(buf, ") IS"); |
| if (list_length(expr->args) == 2) |
| { |
| Const *con = (Const *) lsecond(expr->args); |
| |
| Assert(IsA(con, Const) && |
| con->consttype == TEXTOID && |
| !con->constisnull); |
| appendStringInfo(buf, " %s", |
| TextDatumGetCString(con->constvalue)); |
| } |
| appendStringInfoString(buf, " NORMALIZED)"); |
| return true; |
| |
| case F_PG_COLLATION_FOR: |
| /* COLLATION FOR */ |
| appendStringInfoString(buf, "COLLATION FOR ("); |
| get_rule_expr((Node *) linitial(expr->args), context, false); |
| appendStringInfoChar(buf, ')'); |
| return true; |
| |
| /* |
| * XXX EXTRACT, a/k/a date_part(), is intentionally not covered |
| * yet. Add it after we change the return type to numeric. |
| */ |
| |
| case F_NORMALIZE: |
| /* NORMALIZE() */ |
| appendStringInfoString(buf, "NORMALIZE("); |
| get_rule_expr((Node *) linitial(expr->args), context, false); |
| if (list_length(expr->args) == 2) |
| { |
| Const *con = (Const *) lsecond(expr->args); |
| |
| Assert(IsA(con, Const) && |
| con->consttype == TEXTOID && |
| !con->constisnull); |
| appendStringInfo(buf, ", %s", |
| TextDatumGetCString(con->constvalue)); |
| } |
| appendStringInfoChar(buf, ')'); |
| return true; |
| |
| case F_OVERLAY_BIT_BIT_INT4: |
| case F_OVERLAY_BIT_BIT_INT4_INT4: |
| case F_OVERLAY_BYTEA_BYTEA_INT4: |
| case F_OVERLAY_BYTEA_BYTEA_INT4_INT4: |
| case F_OVERLAY_TEXT_TEXT_INT4: |
| case F_OVERLAY_TEXT_TEXT_INT4_INT4: |
| /* OVERLAY() */ |
| appendStringInfoString(buf, "OVERLAY("); |
| get_rule_expr((Node *) linitial(expr->args), context, false); |
| appendStringInfoString(buf, " PLACING "); |
| get_rule_expr((Node *) lsecond(expr->args), context, false); |
| appendStringInfoString(buf, " FROM "); |
| get_rule_expr((Node *) lthird(expr->args), context, false); |
| if (list_length(expr->args) == 4) |
| { |
| appendStringInfoString(buf, " FOR "); |
| get_rule_expr((Node *) lfourth(expr->args), context, false); |
| } |
| appendStringInfoChar(buf, ')'); |
| return true; |
| |
| case F_POSITION_BIT_BIT: |
| case F_POSITION_BYTEA_BYTEA: |
| case F_POSITION_TEXT_TEXT: |
| /* POSITION() ... extra parens since args are b_expr not a_expr */ |
| appendStringInfoString(buf, "POSITION(("); |
| get_rule_expr((Node *) lsecond(expr->args), context, false); |
| appendStringInfoString(buf, ") IN ("); |
| get_rule_expr((Node *) linitial(expr->args), context, false); |
| appendStringInfoString(buf, "))"); |
| return true; |
| |
| case F_SUBSTRING_BIT_INT4: |
| case F_SUBSTRING_BIT_INT4_INT4: |
| case F_SUBSTRING_BYTEA_INT4: |
| case F_SUBSTRING_BYTEA_INT4_INT4: |
| case F_SUBSTRING_TEXT_INT4: |
| case F_SUBSTRING_TEXT_INT4_INT4: |
| /* SUBSTRING FROM/FOR (i.e., integer-position variants) */ |
| appendStringInfoString(buf, "SUBSTRING("); |
| get_rule_expr((Node *) linitial(expr->args), context, false); |
| appendStringInfoString(buf, " FROM "); |
| get_rule_expr((Node *) lsecond(expr->args), context, false); |
| if (list_length(expr->args) == 3) |
| { |
| appendStringInfoString(buf, " FOR "); |
| get_rule_expr((Node *) lthird(expr->args), context, false); |
| } |
| appendStringInfoChar(buf, ')'); |
| return true; |
| |
| case F_SUBSTRING_TEXT_TEXT_TEXT: |
| /* SUBSTRING SIMILAR/ESCAPE */ |
| appendStringInfoString(buf, "SUBSTRING("); |
| get_rule_expr((Node *) linitial(expr->args), context, false); |
| appendStringInfoString(buf, " SIMILAR "); |
| get_rule_expr((Node *) lsecond(expr->args), context, false); |
| appendStringInfoString(buf, " ESCAPE "); |
| get_rule_expr((Node *) lthird(expr->args), context, false); |
| appendStringInfoChar(buf, ')'); |
| return true; |
| |
| case F_BTRIM_BYTEA_BYTEA: |
| case F_BTRIM_TEXT: |
| case F_BTRIM_TEXT_TEXT: |
| /* TRIM() */ |
| appendStringInfoString(buf, "TRIM(BOTH"); |
| if (list_length(expr->args) == 2) |
| { |
| appendStringInfoChar(buf, ' '); |
| get_rule_expr((Node *) lsecond(expr->args), context, false); |
| } |
| appendStringInfoString(buf, " FROM "); |
| get_rule_expr((Node *) linitial(expr->args), context, false); |
| appendStringInfoChar(buf, ')'); |
| return true; |
| |
| case F_LTRIM_BYTEA_BYTEA: |
| case F_LTRIM_TEXT: |
| case F_LTRIM_TEXT_TEXT: |
| /* TRIM() */ |
| appendStringInfoString(buf, "TRIM(LEADING"); |
| if (list_length(expr->args) == 2) |
| { |
| appendStringInfoChar(buf, ' '); |
| get_rule_expr((Node *) lsecond(expr->args), context, false); |
| } |
| appendStringInfoString(buf, " FROM "); |
| get_rule_expr((Node *) linitial(expr->args), context, false); |
| appendStringInfoChar(buf, ')'); |
| return true; |
| |
| case F_RTRIM_BYTEA_BYTEA: |
| case F_RTRIM_TEXT: |
| case F_RTRIM_TEXT_TEXT: |
| /* TRIM() */ |
| appendStringInfoString(buf, "TRIM(TRAILING"); |
| if (list_length(expr->args) == 2) |
| { |
| appendStringInfoChar(buf, ' '); |
| get_rule_expr((Node *) lsecond(expr->args), context, false); |
| } |
| appendStringInfoString(buf, " FROM "); |
| get_rule_expr((Node *) linitial(expr->args), context, false); |
| appendStringInfoChar(buf, ')'); |
| return true; |
| |
| case F_XMLEXISTS: |
| /* XMLEXISTS ... extra parens because args are c_expr */ |
| appendStringInfoString(buf, "XMLEXISTS(("); |
| get_rule_expr((Node *) linitial(expr->args), context, false); |
| appendStringInfoString(buf, ") PASSING ("); |
| get_rule_expr((Node *) lsecond(expr->args), context, false); |
| appendStringInfoString(buf, "))"); |
| return true; |
| } |
| return false; |
| } |
| |
| /* ---------- |
| * get_coercion_expr |
| * |
| * Make a string representation of a value coerced to a specific type |
| * ---------- |
| */ |
| static void |
| get_coercion_expr(Node *arg, deparse_context *context, |
| Oid resulttype, int32 resulttypmod, |
| Node *parentNode) |
| { |
| StringInfo buf = context->buf; |
| |
| /* |
| * Since parse_coerce.c doesn't immediately collapse application of |
| * length-coercion functions to constants, what we'll typically see in |
| * such cases is a Const with typmod -1 and a length-coercion function |
| * right above it. Avoid generating redundant output. However, beware of |
| * suppressing casts when the user actually wrote something like |
| * 'foo'::text::char(3). |
| * |
| * Note: it might seem that we are missing the possibility of needing to |
| * print a COLLATE clause for such a Const. However, a Const could only |
| * have nondefault collation in a post-constant-folding tree, in which the |
| * length coercion would have been folded too. See also the special |
| * handling of CollateExpr in coerce_to_target_type(): any collation |
| * marking will be above the coercion node, not below it. |
| */ |
| if (arg && IsA(arg, Const) && |
| ((Const *) arg)->consttype == resulttype && |
| ((Const *) arg)->consttypmod == -1) |
| { |
| /* Show the constant without normal ::typename decoration */ |
| get_const_expr((Const *) arg, context, -1); |
| } |
| else |
| { |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, '('); |
| get_rule_expr_paren(arg, context, false, parentNode); |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, ')'); |
| } |
| |
| /* |
| * Never emit resulttype(arg) functional notation. A pg_proc entry could |
| * take precedence, and a resulttype in pg_temp would require schema |
| * qualification that format_type_with_typemod() would usually omit. We've |
| * standardized on arg::resulttype, but CAST(arg AS resulttype) notation |
| * would work fine. |
| */ |
| appendStringInfo(buf, "::%s", |
| format_type_with_typemod(resulttype, resulttypmod)); |
| } |
| |
| /* ---------- |
| * get_const_expr |
| * |
| * Make a string representation of a Const |
| * |
| * showtype can be -1 to never show "::typename" decoration, or +1 to always |
| * show it, or 0 to show it only if the constant wouldn't be assumed to be |
| * the right type by default. |
| * |
| * If the Const's collation isn't default for its type, show that too. |
| * We mustn't do this when showtype is -1 (since that means the caller will |
| * print "::typename", and we can't put a COLLATE clause in between). It's |
| * caller's responsibility that collation isn't missed in such cases. |
| * ---------- |
| */ |
| static void |
| get_const_expr(Const *constval, deparse_context *context, int showtype) |
| { |
| StringInfo buf = context->buf; |
| Oid typoutput; |
| bool typIsVarlena; |
| char *extval; |
| bool needlabel = false; |
| |
| if (constval->constisnull) |
| { |
| /* |
| * Always label the type of a NULL constant to prevent misdecisions |
| * about type when reparsing. |
| */ |
| appendStringInfoString(buf, "NULL"); |
| if (showtype >= 0) |
| { |
| appendStringInfo(buf, "::%s", |
| format_type_with_typemod(constval->consttype, |
| constval->consttypmod)); |
| get_const_collation(constval, context); |
| } |
| return; |
| } |
| |
| getTypeOutputInfo(constval->consttype, |
| &typoutput, &typIsVarlena); |
| |
| extval = OidOutputFunctionCall(typoutput, constval->constvalue); |
| |
| switch (constval->consttype) |
| { |
| case INT4OID: |
| |
| /* |
| * INT4 can be printed without any decoration, unless it is |
| * negative; in that case print it as '-nnn'::integer to ensure |
| * that the output will re-parse as a constant, not as a constant |
| * plus operator. In most cases we could get away with printing |
| * (-nnn) instead, because of the way that gram.y handles negative |
| * literals; but that doesn't work for INT_MIN, and it doesn't |
| * seem that much prettier anyway. |
| */ |
| if (extval[0] != '-') |
| appendStringInfoString(buf, extval); |
| else |
| { |
| appendStringInfo(buf, "'%s'", extval); |
| needlabel = true; /* we must attach a cast */ |
| } |
| break; |
| |
| case NUMERICOID: |
| |
| /* |
| * NUMERIC can be printed without quotes if it looks like a float |
| * constant (not an integer, and not Infinity or NaN) and doesn't |
| * have a leading sign (for the same reason as for INT4). |
| */ |
| if (isdigit((unsigned char) extval[0]) && |
| strcspn(extval, "eE.") != strlen(extval)) |
| { |
| appendStringInfoString(buf, extval); |
| } |
| else |
| { |
| appendStringInfo(buf, "'%s'", extval); |
| needlabel = true; /* we must attach a cast */ |
| } |
| break; |
| |
| case BOOLOID: |
| if (strcmp(extval, "t") == 0) |
| appendStringInfoString(buf, "true"); |
| else |
| appendStringInfoString(buf, "false"); |
| break; |
| |
| default: |
| simple_quote_literal(buf, extval); |
| break; |
| } |
| |
| pfree(extval); |
| |
| if (showtype < 0) |
| return; |
| |
| /* |
| * For showtype == 0, append ::typename unless the constant will be |
| * implicitly typed as the right type when it is read in. |
| * |
| * XXX this code has to be kept in sync with the behavior of the parser, |
| * especially make_const. |
| */ |
| switch (constval->consttype) |
| { |
| case BOOLOID: |
| case UNKNOWNOID: |
| /* These types can be left unlabeled */ |
| needlabel = false; |
| break; |
| case INT4OID: |
| /* We determined above whether a label is needed */ |
| break; |
| case NUMERICOID: |
| |
| /* |
| * Float-looking constants will be typed as numeric, which we |
| * checked above; but if there's a nondefault typmod we need to |
| * show it. |
| */ |
| needlabel |= (constval->consttypmod >= 0); |
| break; |
| default: |
| needlabel = true; |
| break; |
| } |
| if (needlabel || showtype > 0) |
| appendStringInfo(buf, "::%s", |
| format_type_with_typemod(constval->consttype, |
| constval->consttypmod)); |
| |
| get_const_collation(constval, context); |
| } |
| |
| /* |
| * helper for get_const_expr: append COLLATE if needed |
| */ |
| static void |
| get_const_collation(Const *constval, deparse_context *context) |
| { |
| StringInfo buf = context->buf; |
| |
| if (OidIsValid(constval->constcollid)) |
| { |
| Oid typcollation = get_typcollation(constval->consttype); |
| |
| if (constval->constcollid != typcollation) |
| { |
| appendStringInfo(buf, " COLLATE %s", |
| generate_collation_name(constval->constcollid)); |
| } |
| } |
| } |
| |
| /* |
| * simple_quote_literal - Format a string as a SQL literal, append to buf |
| */ |
| static void |
| simple_quote_literal(StringInfo buf, const char *val) |
| { |
| const char *valptr; |
| |
| /* |
| * We form the string literal according to the prevailing setting of |
| * standard_conforming_strings; we never use E''. User is responsible for |
| * making sure result is used correctly. |
| */ |
| appendStringInfoChar(buf, '\''); |
| for (valptr = val; *valptr; valptr++) |
| { |
| char ch = *valptr; |
| |
| if (SQL_STR_DOUBLE(ch, !standard_conforming_strings)) |
| appendStringInfoChar(buf, ch); |
| appendStringInfoChar(buf, ch); |
| } |
| appendStringInfoChar(buf, '\''); |
| } |
| |
| |
| /* ---------- |
| * get_sublink_expr - Parse back a sublink |
| * ---------- |
| */ |
| static void |
| get_sublink_expr(SubLink *sublink, deparse_context *context) |
| { |
| StringInfo buf = context->buf; |
| Query *query = (Query *) (sublink->subselect); |
| char *opname = NULL; |
| bool need_paren; |
| |
| if (sublink->subLinkType == ARRAY_SUBLINK) |
| appendStringInfoString(buf, "ARRAY("); |
| else |
| appendStringInfoChar(buf, '('); |
| |
| /* |
| * Note that we print the name of only the first operator, when there are |
| * multiple combining operators. This is an approximation that could go |
| * wrong in various scenarios (operators in different schemas, renamed |
| * operators, etc) but there is not a whole lot we can do about it, since |
| * the syntax allows only one operator to be shown. |
| */ |
| if (sublink->testexpr) |
| { |
| if (IsA(sublink->testexpr, OpExpr)) |
| { |
| /* single combining operator */ |
| OpExpr *opexpr = (OpExpr *) sublink->testexpr; |
| |
| get_rule_expr(linitial(opexpr->args), context, true); |
| opname = generate_operator_name(opexpr->opno, |
| exprType(linitial(opexpr->args)), |
| exprType(lsecond(opexpr->args))); |
| } |
| else if (IsA(sublink->testexpr, BoolExpr)) |
| { |
| /* multiple combining operators, = or <> cases */ |
| char *sep; |
| ListCell *l; |
| |
| appendStringInfoChar(buf, '('); |
| sep = ""; |
| foreach(l, ((BoolExpr *) sublink->testexpr)->args) |
| { |
| OpExpr *opexpr = lfirst_node(OpExpr, l); |
| |
| appendStringInfoString(buf, sep); |
| get_rule_expr(linitial(opexpr->args), context, true); |
| if (!opname) |
| opname = generate_operator_name(opexpr->opno, |
| exprType(linitial(opexpr->args)), |
| exprType(lsecond(opexpr->args))); |
| sep = ", "; |
| } |
| appendStringInfoChar(buf, ')'); |
| } |
| else if (IsA(sublink->testexpr, RowCompareExpr)) |
| { |
| /* multiple combining operators, < <= > >= cases */ |
| RowCompareExpr *rcexpr = (RowCompareExpr *) sublink->testexpr; |
| |
| appendStringInfoChar(buf, '('); |
| get_rule_expr((Node *) rcexpr->largs, context, true); |
| opname = generate_operator_name(linitial_oid(rcexpr->opnos), |
| exprType(linitial(rcexpr->largs)), |
| exprType(linitial(rcexpr->rargs))); |
| appendStringInfoChar(buf, ')'); |
| } |
| else |
| elog(ERROR, "unrecognized testexpr type: %d", |
| (int) nodeTag(sublink->testexpr)); |
| } |
| |
| need_paren = true; |
| |
| switch (sublink->subLinkType) |
| { |
| case EXISTS_SUBLINK: |
| appendStringInfoString(buf, "EXISTS "); |
| break; |
| |
| case ANY_SUBLINK: |
| if (strcmp(opname, "=") == 0) /* Represent = ANY as IN */ |
| appendStringInfoString(buf, " IN "); |
| else |
| appendStringInfo(buf, " %s ANY ", opname); |
| break; |
| |
| case ALL_SUBLINK: |
| appendStringInfo(buf, " %s ALL ", opname); |
| break; |
| |
| case ROWCOMPARE_SUBLINK: |
| appendStringInfo(buf, " %s ", opname); |
| break; |
| |
| case EXPR_SUBLINK: |
| case MULTIEXPR_SUBLINK: |
| case ARRAY_SUBLINK: |
| need_paren = false; |
| break; |
| |
| case CTE_SUBLINK: /* shouldn't occur in a SubLink */ |
| default: |
| elog(ERROR, "unrecognized sublink type: %d", |
| (int) sublink->subLinkType); |
| break; |
| } |
| |
| if (need_paren) |
| appendStringInfoChar(buf, '('); |
| |
| get_query_def(query, buf, context->namespaces, NULL, false, |
| context->prettyFlags, context->wrapColumn, |
| context->indentLevel); |
| |
| if (need_paren) |
| appendStringInfoString(buf, "))"); |
| else |
| appendStringInfoChar(buf, ')'); |
| } |
| |
| |
| /* ---------- |
| * get_tablefunc - Parse back a table function |
| * ---------- |
| */ |
| static void |
| get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit) |
| { |
| StringInfo buf = context->buf; |
| |
| /* XMLTABLE is the only existing implementation. */ |
| |
| appendStringInfoString(buf, "XMLTABLE("); |
| |
| if (tf->ns_uris != NIL) |
| { |
| ListCell *lc1, |
| *lc2; |
| bool first = true; |
| |
| appendStringInfoString(buf, "XMLNAMESPACES ("); |
| forboth(lc1, tf->ns_uris, lc2, tf->ns_names) |
| { |
| Node *expr = (Node *) lfirst(lc1); |
| Value *ns_node = (Value *) lfirst(lc2); |
| |
| if (!first) |
| appendStringInfoString(buf, ", "); |
| else |
| first = false; |
| |
| if (ns_node != NULL) |
| { |
| get_rule_expr(expr, context, showimplicit); |
| appendStringInfo(buf, " AS %s", strVal(ns_node)); |
| } |
| else |
| { |
| appendStringInfoString(buf, "DEFAULT "); |
| get_rule_expr(expr, context, showimplicit); |
| } |
| } |
| appendStringInfoString(buf, "), "); |
| } |
| |
| appendStringInfoChar(buf, '('); |
| get_rule_expr((Node *) tf->rowexpr, context, showimplicit); |
| appendStringInfoString(buf, ") PASSING ("); |
| get_rule_expr((Node *) tf->docexpr, context, showimplicit); |
| appendStringInfoChar(buf, ')'); |
| |
| if (tf->colexprs != NIL) |
| { |
| ListCell *l1; |
| ListCell *l2; |
| ListCell *l3; |
| ListCell *l4; |
| ListCell *l5; |
| int colnum = 0; |
| |
| appendStringInfoString(buf, " COLUMNS "); |
| forfive(l1, tf->colnames, l2, tf->coltypes, l3, tf->coltypmods, |
| l4, tf->colexprs, l5, tf->coldefexprs) |
| { |
| char *colname = strVal(lfirst(l1)); |
| Oid typid = lfirst_oid(l2); |
| int32 typmod = lfirst_int(l3); |
| Node *colexpr = (Node *) lfirst(l4); |
| Node *coldefexpr = (Node *) lfirst(l5); |
| bool ordinality = (tf->ordinalitycol == colnum); |
| bool notnull = bms_is_member(colnum, tf->notnulls); |
| |
| if (colnum > 0) |
| appendStringInfoString(buf, ", "); |
| colnum++; |
| |
| appendStringInfo(buf, "%s %s", quote_identifier(colname), |
| ordinality ? "FOR ORDINALITY" : |
| format_type_with_typemod(typid, typmod)); |
| if (ordinality) |
| continue; |
| |
| if (coldefexpr != NULL) |
| { |
| appendStringInfoString(buf, " DEFAULT ("); |
| get_rule_expr((Node *) coldefexpr, context, showimplicit); |
| appendStringInfoChar(buf, ')'); |
| } |
| if (colexpr != NULL) |
| { |
| appendStringInfoString(buf, " PATH ("); |
| get_rule_expr((Node *) colexpr, context, showimplicit); |
| appendStringInfoChar(buf, ')'); |
| } |
| if (notnull) |
| appendStringInfoString(buf, " NOT NULL"); |
| } |
| } |
| |
| appendStringInfoChar(buf, ')'); |
| } |
| |
| /* ---------- |
| * get_from_clause - Parse back a FROM clause |
| * |
| * "prefix" is the keyword that denotes the start of the list of FROM |
| * elements. It is FROM when used to parse back SELECT and UPDATE, but |
| * is USING when parsing back DELETE. |
| * ---------- |
| */ |
| static void |
| get_from_clause(Query *query, const char *prefix, deparse_context *context) |
| { |
| StringInfo buf = context->buf; |
| bool first = true; |
| ListCell *l; |
| |
| /* |
| * We use the query's jointree as a guide to what to print. However, we |
| * must ignore auto-added RTEs that are marked not inFromCl. (These can |
| * only appear at the top level of the jointree, so it's sufficient to |
| * check here.) This check also ensures we ignore the rule pseudo-RTEs |
| * for NEW and OLD. |
| */ |
| foreach(l, query->jointree->fromlist) |
| { |
| Node *jtnode = (Node *) lfirst(l); |
| |
| if (IsA(jtnode, RangeTblRef)) |
| { |
| int varno = ((RangeTblRef *) jtnode)->rtindex; |
| RangeTblEntry *rte = rt_fetch(varno, query->rtable); |
| |
| if (!rte->inFromCl) |
| continue; |
| } |
| |
| if (first) |
| { |
| appendContextKeyword(context, prefix, |
| -PRETTYINDENT_STD, PRETTYINDENT_STD, 2); |
| first = false; |
| |
| get_from_clause_item(jtnode, query, context); |
| } |
| else |
| { |
| StringInfoData itembuf; |
| |
| appendStringInfoString(buf, ", "); |
| |
| /* |
| * Put the new FROM item's text into itembuf so we can decide |
| * after we've got it whether or not it needs to go on a new line. |
| */ |
| initStringInfo(&itembuf); |
| context->buf = &itembuf; |
| |
| get_from_clause_item(jtnode, query, context); |
| |
| /* Restore context's output buffer */ |
| context->buf = buf; |
| |
| /* Consider line-wrapping if enabled */ |
| if (PRETTY_INDENT(context) && context->wrapColumn >= 0) |
| { |
| /* Does the new item start with a new line? */ |
| if (itembuf.len > 0 && itembuf.data[0] == '\n') |
| { |
| /* If so, we shouldn't add anything */ |
| /* instead, remove any trailing spaces currently in buf */ |
| removeStringInfoSpaces(buf); |
| } |
| else |
| { |
| char *trailing_nl; |
| |
| /* Locate the start of the current line in the buffer */ |
| trailing_nl = strrchr(buf->data, '\n'); |
| if (trailing_nl == NULL) |
| trailing_nl = buf->data; |
| else |
| trailing_nl++; |
| |
| /* |
| * Add a newline, plus some indentation, if the new item |
| * would cause an overflow. |
| */ |
| if (strlen(trailing_nl) + itembuf.len > context->wrapColumn) |
| appendContextKeyword(context, "", -PRETTYINDENT_STD, |
| PRETTYINDENT_STD, |
| PRETTYINDENT_VAR); |
| } |
| } |
| |
| /* Add the new item */ |
| appendBinaryStringInfo(buf, itembuf.data, itembuf.len); |
| |
| /* clean up */ |
| pfree(itembuf.data); |
| } |
| } |
| } |
| |
| static void |
| get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) |
| { |
| StringInfo buf = context->buf; |
| deparse_namespace *dpns = (deparse_namespace *) linitial(context->namespaces); |
| |
| if (IsA(jtnode, RangeTblRef)) |
| { |
| int varno = ((RangeTblRef *) jtnode)->rtindex; |
| RangeTblEntry *rte = rt_fetch(varno, query->rtable); |
| char *refname = get_rtable_name(varno, context); |
| deparse_columns *colinfo = deparse_columns_fetch(varno, dpns); |
| RangeTblFunction *rtfunc1 = NULL; |
| bool printalias; |
| |
| if (rte->lateral) |
| appendStringInfoString(buf, "LATERAL "); |
| |
| /* Print the FROM item proper */ |
| switch (rte->rtekind) |
| { |
| case RTE_RELATION: |
| /* Normal relation RTE */ |
| if (rte->forceDistRandom) |
| { |
| char * relname = generate_relation_name(rte->relid, |
| context->namespaces); |
| appendStringInfo(buf, "gp_dist_random(%s)", |
| quote_literal_cstr(relname)); |
| } |
| else |
| appendStringInfo(buf, "%s%s", |
| only_marker(rte), |
| generate_relation_name(rte->relid, |
| context->namespaces)); |
| break; |
| case RTE_SUBQUERY: |
| /* Subquery RTE */ |
| appendStringInfoChar(buf, '('); |
| get_query_def(rte->subquery, buf, context->namespaces, NULL, |
| true, |
| context->prettyFlags, context->wrapColumn, |
| context->indentLevel); |
| appendStringInfoChar(buf, ')'); |
| break; |
| case RTE_TABLEFUNCTION: |
| /* Table Function RTE */ |
| /* fallthrough */ |
| case RTE_FUNCTION: |
| /* Function RTE */ |
| rtfunc1 = (RangeTblFunction *) linitial(rte->functions); |
| |
| /* |
| * Omit ROWS FROM() syntax for just one function, unless it |
| * has both a coldeflist and WITH ORDINALITY. If it has both, |
| * we must use ROWS FROM() syntax to avoid ambiguity about |
| * whether the coldeflist includes the ordinality column. |
| */ |
| if (list_length(rte->functions) == 1 && |
| (rtfunc1->funccolnames == NIL || !rte->funcordinality)) |
| { |
| get_rule_expr_funccall(rtfunc1->funcexpr, context, true); |
| /* we'll print the coldeflist below, if it has one */ |
| } |
| else |
| { |
| bool all_unnest; |
| ListCell *lc; |
| |
| /* |
| * If all the function calls in the list are to unnest, |
| * and none need a coldeflist, then collapse the list back |
| * down to UNNEST(args). (If we had more than one |
| * built-in unnest function, this would get more |
| * difficult.) |
| * |
| * XXX This is pretty ugly, since it makes not-terribly- |
| * future-proof assumptions about what the parser would do |
| * with the output; but the alternative is to emit our |
| * nonstandard ROWS FROM() notation for what might have |
| * been a perfectly spec-compliant multi-argument |
| * UNNEST(). |
| */ |
| all_unnest = true; |
| foreach(lc, rte->functions) |
| { |
| RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); |
| |
| if (!IsA(rtfunc->funcexpr, FuncExpr) || |
| ((FuncExpr *) rtfunc->funcexpr)->funcid != F_UNNEST_ANYARRAY || |
| rtfunc->funccolnames != NIL) |
| { |
| all_unnest = false; |
| break; |
| } |
| } |
| |
| if (all_unnest) |
| { |
| List *allargs = NIL; |
| |
| foreach(lc, rte->functions) |
| { |
| RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); |
| List *args = ((FuncExpr *) rtfunc->funcexpr)->args; |
| |
| allargs = list_concat(allargs, args); |
| } |
| |
| appendStringInfoString(buf, "UNNEST("); |
| get_rule_expr((Node *) allargs, context, true); |
| appendStringInfoChar(buf, ')'); |
| } |
| else |
| { |
| int funcno = 0; |
| |
| appendStringInfoString(buf, "ROWS FROM("); |
| foreach(lc, rte->functions) |
| { |
| RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); |
| |
| if (funcno > 0) |
| appendStringInfoString(buf, ", "); |
| get_rule_expr_funccall(rtfunc->funcexpr, context, true); |
| if (rtfunc->funccolnames != NIL) |
| { |
| /* Reconstruct the column definition list */ |
| appendStringInfoString(buf, " AS "); |
| get_from_clause_coldeflist(rtfunc, |
| NULL, |
| context); |
| } |
| funcno++; |
| } |
| appendStringInfoChar(buf, ')'); |
| } |
| /* prevent printing duplicate coldeflist below */ |
| rtfunc1 = NULL; |
| } |
| if (rte->funcordinality) |
| appendStringInfoString(buf, " WITH ORDINALITY"); |
| break; |
| case RTE_TABLEFUNC: |
| get_tablefunc(rte->tablefunc, context, true); |
| break; |
| case RTE_VALUES: |
| /* Values list RTE */ |
| appendStringInfoChar(buf, '('); |
| get_values_def(rte->values_lists, context); |
| appendStringInfoChar(buf, ')'); |
| break; |
| case RTE_CTE: |
| appendStringInfoString(buf, quote_identifier(rte->ctename)); |
| break; |
| default: |
| elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind); |
| break; |
| } |
| |
| /* Print the relation alias, if needed */ |
| printalias = false; |
| if (rte->alias != NULL) |
| { |
| /* Always print alias if user provided one */ |
| printalias = true; |
| } |
| else if (colinfo->printaliases) |
| { |
| /* Always print alias if we need to print column aliases */ |
| printalias = true; |
| } |
| else if (rte->rtekind == RTE_RELATION) |
| { |
| /* |
| * No need to print alias if it's same as relation name (this |
| * would normally be the case, but not if set_rtable_names had to |
| * resolve a conflict). |
| */ |
| if (strcmp(refname, get_relation_name(rte->relid)) != 0) |
| printalias = true; |
| } |
| else if (rte->rtekind == RTE_FUNCTION || rte->rtekind == RTE_TABLEFUNCTION) |
| { |
| /* |
| * For a function RTE, always print alias. This covers possible |
| * renaming of the function and/or instability of the |
| * FigureColname rules for things that aren't simple functions. |
| * Note we'd need to force it anyway for the columndef list case. |
| */ |
| printalias = true; |
| } |
| else if (rte->rtekind == RTE_VALUES) |
| { |
| /* Alias is syntactically required for VALUES */ |
| printalias = true; |
| } |
| else if (rte->rtekind == RTE_CTE) |
| { |
| /* |
| * No need to print alias if it's same as CTE name (this would |
| * normally be the case, but not if set_rtable_names had to |
| * resolve a conflict). |
| */ |
| if (strcmp(refname, rte->ctename) != 0) |
| printalias = true; |
| } |
| if (printalias) |
| appendStringInfo(buf, " %s", quote_identifier(refname)); |
| |
| /* Print the column definitions or aliases, if needed */ |
| if (rtfunc1 && rtfunc1->funccolnames != NIL) |
| { |
| /* Reconstruct the columndef list, which is also the aliases */ |
| get_from_clause_coldeflist(rtfunc1, colinfo, context); |
| } |
| else |
| { |
| /* Else print column aliases as needed */ |
| get_column_alias_list(colinfo, context); |
| } |
| |
| /* Tablesample clause must go after any alias */ |
| if (rte->rtekind == RTE_RELATION && rte->tablesample) |
| get_tablesample_def(rte->tablesample, context); |
| } |
| else if (IsA(jtnode, JoinExpr)) |
| { |
| JoinExpr *j = (JoinExpr *) jtnode; |
| deparse_columns *colinfo = deparse_columns_fetch(j->rtindex, dpns); |
| bool need_paren_on_right; |
| |
| need_paren_on_right = PRETTY_PAREN(context) && |
| !IsA(j->rarg, RangeTblRef) && |
| !(IsA(j->rarg, JoinExpr) && ((JoinExpr *) j->rarg)->alias != NULL); |
| |
| if (!PRETTY_PAREN(context) || j->alias != NULL) |
| appendStringInfoChar(buf, '('); |
| |
| get_from_clause_item(j->larg, query, context); |
| |
| switch (j->jointype) |
| { |
| case JOIN_INNER: |
| if (j->quals) |
| appendContextKeyword(context, " JOIN ", |
| -PRETTYINDENT_STD, |
| PRETTYINDENT_STD, |
| PRETTYINDENT_JOIN); |
| else |
| appendContextKeyword(context, " CROSS JOIN ", |
| -PRETTYINDENT_STD, |
| PRETTYINDENT_STD, |
| PRETTYINDENT_JOIN); |
| break; |
| case JOIN_LEFT: |
| appendContextKeyword(context, " LEFT JOIN ", |
| -PRETTYINDENT_STD, |
| PRETTYINDENT_STD, |
| PRETTYINDENT_JOIN); |
| break; |
| case JOIN_FULL: |
| appendContextKeyword(context, " FULL JOIN ", |
| -PRETTYINDENT_STD, |
| PRETTYINDENT_STD, |
| PRETTYINDENT_JOIN); |
| break; |
| case JOIN_RIGHT: |
| appendContextKeyword(context, " RIGHT JOIN ", |
| -PRETTYINDENT_STD, |
| PRETTYINDENT_STD, |
| PRETTYINDENT_JOIN); |
| break; |
| default: |
| elog(ERROR, "unrecognized join type: %d", |
| (int) j->jointype); |
| } |
| |
| if (need_paren_on_right) |
| appendStringInfoChar(buf, '('); |
| get_from_clause_item(j->rarg, query, context); |
| if (need_paren_on_right) |
| appendStringInfoChar(buf, ')'); |
| |
| if (j->usingClause) |
| { |
| ListCell *lc; |
| bool first = true; |
| |
| appendStringInfoString(buf, " USING ("); |
| /* Use the assigned names, not what's in usingClause */ |
| foreach(lc, colinfo->usingNames) |
| { |
| char *colname = (char *) lfirst(lc); |
| |
| if (first) |
| first = false; |
| else |
| appendStringInfoString(buf, ", "); |
| appendStringInfoString(buf, quote_identifier(colname)); |
| } |
| appendStringInfoChar(buf, ')'); |
| |
| if (j->join_using_alias) |
| appendStringInfo(buf, " AS %s", |
| quote_identifier(j->join_using_alias->aliasname)); |
| } |
| else if (j->quals) |
| { |
| appendStringInfoString(buf, " ON "); |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, '('); |
| get_rule_expr(j->quals, context, false); |
| if (!PRETTY_PAREN(context)) |
| appendStringInfoChar(buf, ')'); |
| } |
| else if (j->jointype != JOIN_INNER) |
| { |
| /* If we didn't say CROSS JOIN above, we must provide an ON */ |
| appendStringInfoString(buf, " ON TRUE"); |
| } |
| |
| if (!PRETTY_PAREN(context) || j->alias != NULL) |
| appendStringInfoChar(buf, ')'); |
| |
| /* Yes, it's correct to put alias after the right paren ... */ |
| if (j->alias != NULL) |
| { |
| /* |
| * Note that it's correct to emit an alias clause if and only if |
| * there was one originally. Otherwise we'd be converting a named |
| * join to unnamed or vice versa, which creates semantic |
| * subtleties we don't want. However, we might print a different |
| * alias name than was there originally. |
| */ |
| appendStringInfo(buf, " %s", |
| quote_identifier(get_rtable_name(j->rtindex, |
| context))); |
| get_column_alias_list(colinfo, context); |
| } |
| } |
| else |
| elog(ERROR, "unrecognized node type: %d", |
| (int) nodeTag(jtnode)); |
| } |
| |
| /* |
| * get_column_alias_list - print column alias list for an RTE |
| * |
| * Caller must already have printed the relation's alias name. |
| */ |
| static void |
| get_column_alias_list(deparse_columns *colinfo, deparse_context *context) |
| { |
| StringInfo buf = context->buf; |
| int i; |
| bool first = true; |
| |
| /* Don't print aliases if not needed */ |
| if (!colinfo->printaliases) |
| return; |
| |
| for (i = 0; i < colinfo->num_new_cols; i++) |
| { |
| char *colname = colinfo->new_colnames[i]; |
| |
| if (first) |
| { |
| appendStringInfoChar(buf, '('); |
| first = false; |
| } |
| else |
| appendStringInfoString(buf, ", "); |
| appendStringInfoString(buf, quote_identifier(colname)); |
| } |
| if (!first) |
| appendStringInfoChar(buf, ')'); |
| } |
| |
| /* |
| * get_from_clause_coldeflist - reproduce FROM clause coldeflist |
| * |
| * When printing a top-level coldeflist (which is syntactically also the |
| * relation's column alias list), use column names from colinfo. But when |
| * printing a coldeflist embedded inside ROWS FROM(), we prefer to use the |
| * original coldeflist's names, which are available in rtfunc->funccolnames. |
| * Pass NULL for colinfo to select the latter behavior. |
| * |
| * The coldeflist is appended immediately (no space) to buf. Caller is |
| * responsible for ensuring that an alias or AS is present before it. |
| */ |
| static void |
| get_from_clause_coldeflist(RangeTblFunction *rtfunc, |
| deparse_columns *colinfo, |
| deparse_context *context) |
| { |
| StringInfo buf = context->buf; |
| ListCell *l1; |
| ListCell *l2; |
| ListCell *l3; |
| ListCell *l4; |
| int i; |
| |
| appendStringInfoChar(buf, '('); |
| |
| i = 0; |
| forfour(l1, rtfunc->funccoltypes, |
| l2, rtfunc->funccoltypmods, |
| l3, rtfunc->funccolcollations, |
| l4, rtfunc->funccolnames) |
| { |
| Oid atttypid = lfirst_oid(l1); |
| int32 atttypmod = lfirst_int(l2); |
| Oid attcollation = lfirst_oid(l3); |
| char *attname; |
| |
| if (colinfo) |
| attname = colinfo->colnames[i]; |
| else |
| attname = strVal(lfirst(l4)); |
| |
| Assert(attname); /* shouldn't be any dropped columns here */ |
| |
| if (i > 0) |
| appendStringInfoString(buf, ", "); |
| appendStringInfo(buf, "%s %s", |
| quote_identifier(attname), |
| format_type_with_typemod(atttypid, atttypmod)); |
| if (OidIsValid(attcollation) && |
| attcollation != get_typcollation(atttypid)) |
| appendStringInfo(buf, " COLLATE %s", |
| generate_collation_name(attcollation)); |
| |
| i++; |
| } |
| |
| appendStringInfoChar(buf, ')'); |
| } |
| |
| /* |
| * get_tablesample_def - print a TableSampleClause |
| */ |
| static void |
| get_tablesample_def(TableSampleClause *tablesample, deparse_context *context) |
| { |
| StringInfo buf = context->buf; |
| Oid argtypes[1]; |
| int nargs; |
| ListCell *l; |
| |
| /* |
| * We should qualify the handler's function name if it wouldn't be |
| * resolved by lookup in the current search path. |
| */ |
| argtypes[0] = INTERNALOID; |
| appendStringInfo(buf, " TABLESAMPLE %s (", |
| generate_function_name(tablesample->tsmhandler, 1, |
| NIL, argtypes, |
| false, NULL, EXPR_KIND_NONE)); |
| |
| nargs = 0; |
| foreach(l, tablesample->args) |
| { |
| if (nargs++ > 0) |
| appendStringInfoString(buf, ", "); |
| get_rule_expr((Node *) lfirst(l), context, false); |
| } |
| appendStringInfoChar(buf, ')'); |
| |
| if (tablesample->repeatable != NULL) |
| { |
| appendStringInfoString(buf, " REPEATABLE ("); |
| get_rule_expr((Node *) tablesample->repeatable, context, false); |
| appendStringInfoChar(buf, ')'); |
| } |
| } |
| |
| /* |
| * get_opclass_name - fetch name of an index operator class |
| * |
| * The opclass name is appended (after a space) to buf. |
| * |
| * Output is suppressed if the opclass is the default for the given |
| * actual_datatype. (If you don't want this behavior, just pass |
| * InvalidOid for actual_datatype.) |
| */ |
| static void |
| get_opclass_name(Oid opclass, Oid actual_datatype, |
| StringInfo buf) |
| { |
| HeapTuple ht_opc; |
| Form_pg_opclass opcrec; |
| char *opcname; |
| char *nspname; |
| |
| ht_opc = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass)); |
| if (!HeapTupleIsValid(ht_opc)) |
| elog(ERROR, "cache lookup failed for opclass %u", opclass); |
| opcrec = (Form_pg_opclass) GETSTRUCT(ht_opc); |
| |
| if (!OidIsValid(actual_datatype) || |
| GetDefaultOpClass(actual_datatype, opcrec->opcmethod) != opclass) |
| { |
| /* Okay, we need the opclass name. Do we need to qualify it? */ |
| opcname = NameStr(opcrec->opcname); |
| if (OpclassIsVisible(opclass)) |
| appendStringInfo(buf, " %s", quote_identifier(opcname)); |
| else |
| { |
| nspname = get_namespace_name(opcrec->opcnamespace); |
| appendStringInfo(buf, " %s.%s", |
| quote_identifier(nspname), |
| quote_identifier(opcname)); |
| } |
| } |
| ReleaseSysCache(ht_opc); |
| } |
| |
| /* |
| * Like get_opclass_name(), but with special treatment for gp_use_legacy_hashops=on |
| */ |
| static void |
| get_opclass_name_for_distribution_key(Oid opclass, Oid actual_datatype, |
| StringInfo buf) |
| { |
| Oid legacy_opclass; |
| |
| Assert(OidIsValid(actual_datatype)); |
| |
| /* With gp_use_legacy_hashops=off, use the normal rules. */ |
| if (!gp_use_legacy_hashops) |
| { |
| get_opclass_name(opclass, actual_datatype, buf); |
| return; |
| } |
| |
| /* |
| * Otherwise, treat the legacy opclasses as the default, and refrain |
| * from outputting them. |
| */ |
| legacy_opclass = get_legacy_cdbhash_opclass_for_base_type(actual_datatype); |
| |
| if (legacy_opclass == InvalidOid) |
| { |
| /* |
| * No legacy opclass for this datatype. Then treat the usual default |
| * as the default like usual. |
| */ |
| get_opclass_name(opclass, actual_datatype, buf); |
| } |
| else if (opclass != legacy_opclass) |
| { |
| /* |
| * This is not the legacy opclass. Force printing it, by |
| * passing InvalidOid as the 'actual_datatype' to get_opclass_name. |
| */ |
| get_opclass_name(opclass, InvalidOid, buf); |
| } |
| else |
| { |
| /* it is the legacy opclass. Don't print it */ |
| } |
| } |
| |
| /* |
| * generate_opclass_name |
| * Compute the name to display for a opclass specified by OID |
| * |
| * The result includes all necessary quoting and schema-prefixing. |
| */ |
| char * |
| generate_opclass_name(Oid opclass) |
| { |
| StringInfoData buf; |
| |
| initStringInfo(&buf); |
| get_opclass_name(opclass, InvalidOid, &buf); |
| |
| return &buf.data[1]; /* get_opclass_name() prepends space */ |
| } |
| |
| /* |
| * processIndirection - take care of array and subfield assignment |
| * |
| * We strip any top-level FieldStore or assignment SubscriptingRef nodes that |
| * appear in the input, printing them as decoration for the base column |
| * name (which we assume the caller just printed). We might also need to |
| * strip CoerceToDomain nodes, but only ones that appear above assignment |
| * nodes. |
| * |
| * Returns the subexpression that's to be assigned. |
| */ |
| static Node * |
| processIndirection(Node *node, deparse_context *context) |
| { |
| StringInfo buf = context->buf; |
| CoerceToDomain *cdomain = NULL; |
| |
| for (;;) |
| { |
| if (node == NULL) |
| break; |
| if (IsA(node, FieldStore)) |
| { |
| FieldStore *fstore = (FieldStore *) node; |
| Oid typrelid; |
| char *fieldname; |
| |
| /* lookup tuple type */ |
| typrelid = get_typ_typrelid(fstore->resulttype); |
| if (!OidIsValid(typrelid)) |
| elog(ERROR, "argument type %s of FieldStore is not a tuple type", |
| format_type_be(fstore->resulttype)); |
| |
| /* |
| * Print the field name. There should only be one target field in |
| * stored rules. There could be more than that in executable |
| * target lists, but this function cannot be used for that case. |
| */ |
| Assert(list_length(fstore->fieldnums) == 1); |
| fieldname = get_attname(typrelid, |
| linitial_int(fstore->fieldnums), false); |
| appendStringInfo(buf, ".%s", quote_identifier(fieldname)); |
| |
| /* |
| * We ignore arg since it should be an uninteresting reference to |
| * the target column or subcolumn. |
| */ |
| node = (Node *) linitial(fstore->newvals); |
| } |
| else if (IsA(node, SubscriptingRef)) |
| { |
| SubscriptingRef *sbsref = (SubscriptingRef *) node; |
| |
| if (sbsref->refassgnexpr == NULL) |
| break; |
| |
| printSubscripts(sbsref, context); |
| |
| /* |
| * We ignore refexpr since it should be an uninteresting reference |
| * to the target column or subcolumn. |
| */ |
| node = (Node *) sbsref->refassgnexpr; |
| } |
| else if (IsA(node, CoerceToDomain)) |
| { |
| cdomain = (CoerceToDomain *) node; |
| /* If it's an explicit domain coercion, we're done */ |
| if (cdomain->coercionformat != COERCE_IMPLICIT_CAST) |
| break; |
| /* Tentatively descend past the CoerceToDomain */ |
| node = (Node *) cdomain->arg; |
| } |
| else if (IsA(node, CoerceToDomain)) |
| { |
| cdomain = (CoerceToDomain *) node; |
| /* If it's an explicit domain coercion, we're done */ |
| if (cdomain->coercionformat != COERCE_IMPLICIT_CAST) |
| break; |
| /* Tentatively descend past the CoerceToDomain */ |
| node = (Node *) cdomain->arg; |
| } |
| else |
| break; |
| } |
| |
| /* |
| * If we descended past a CoerceToDomain whose argument turned out not to |
| * be a FieldStore or array assignment, back up to the CoerceToDomain. |
| * (This is not enough to be fully correct if there are nested implicit |
| * CoerceToDomains, but such cases shouldn't ever occur.) |
| */ |
| if (cdomain && node == (Node *) cdomain->arg) |
| node = (Node *) cdomain; |
| |
| return node; |
| } |
| |
| static void |
| printSubscripts(SubscriptingRef *sbsref, deparse_context *context) |
| { |
| StringInfo buf = context->buf; |
| ListCell *lowlist_item; |
| ListCell *uplist_item; |
| |
| lowlist_item = list_head(sbsref->reflowerindexpr); /* could be NULL */ |
| foreach(uplist_item, sbsref->refupperindexpr) |
| { |
| appendStringInfoChar(buf, '['); |
| if (lowlist_item) |
| { |
| /* If subexpression is NULL, get_rule_expr prints nothing */ |
| get_rule_expr((Node *) lfirst(lowlist_item), context, false); |
| appendStringInfoChar(buf, ':'); |
| lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item); |
| } |
| /* If subexpression is NULL, get_rule_expr prints nothing */ |
| get_rule_expr((Node *) lfirst(uplist_item), context, false); |
| appendStringInfoChar(buf, ']'); |
| } |
| } |
| |
| /* |
| * quote_identifier - Quote an identifier only if needed |
| * |
| * When quotes are needed, we palloc the required space; slightly |
| * space-wasteful but well worth it for notational simplicity. |
| */ |
| const char * |
| quote_identifier(const char *ident) |
| { |
| /* |
| * Can avoid quoting if ident starts with a lowercase letter or underscore |
| * and contains only lowercase letters, digits, and underscores, *and* is |
| * not any SQL keyword. Otherwise, supply quotes. |
| */ |
| int nquotes = 0; |
| bool safe; |
| const char *ptr; |
| char *result; |
| char *optr; |
| |
| /* |
| * would like to use <ctype.h> macros here, but they might yield unwanted |
| * locale-specific results... |
| */ |
| safe = ((ident[0] >= 'a' && ident[0] <= 'z') || ident[0] == '_'); |
| |
| for (ptr = ident; *ptr; ptr++) |
| { |
| char ch = *ptr; |
| |
| if ((ch >= 'a' && ch <= 'z') || |
| (ch >= '0' && ch <= '9') || |
| (ch == '_')) |
| { |
| /* okay */ |
| } |
| else |
| { |
| safe = false; |
| if (ch == '"') |
| nquotes++; |
| } |
| } |
| |
| if (quote_all_identifiers) |
| safe = false; |
| |
| if (safe) |
| { |
| /* |
| * Check for keyword. We quote keywords except for unreserved ones. |
| * (In some cases we could avoid quoting a col_name or type_func_name |
| * keyword, but it seems much harder than it's worth to tell that.) |
| * |
| * Note: ScanKeywordLookup() does case-insensitive comparison, but |
| * that's fine, since we already know we have all-lower-case. |
| */ |
| int kwnum = ScanKeywordLookup(ident, &ScanKeywords); |
| |
| if (kwnum >= 0 && ScanKeywordCategories[kwnum] != UNRESERVED_KEYWORD) |
| safe = false; |
| } |
| |
| if (safe) |
| return ident; /* no change needed */ |
| |
| result = (char *) palloc(strlen(ident) + nquotes + 2 + 1); |
| |
| optr = result; |
| *optr++ = '"'; |
| for (ptr = ident; *ptr; ptr++) |
| { |
| char ch = *ptr; |
| |
| if (ch == '"') |
| *optr++ = '"'; |
| *optr++ = ch; |
| } |
| *optr++ = '"'; |
| *optr = '\0'; |
| |
| return result; |
| } |
| |
| /* |
| * quote_qualified_identifier - Quote a possibly-qualified identifier |
| * |
| * Return a name of the form qualifier.ident, or just ident if qualifier |
| * is NULL, quoting each component if necessary. The result is palloc'd. |
| */ |
| char * |
| quote_qualified_identifier(const char *qualifier, |
| const char *ident) |
| { |
| StringInfoData buf; |
| |
| initStringInfo(&buf); |
| if (qualifier) |
| appendStringInfo(&buf, "%s.", quote_identifier(qualifier)); |
| appendStringInfoString(&buf, quote_identifier(ident)); |
| return buf.data; |
| } |
| |
| /* |
| * get_relation_name |
| * Get the unqualified name of a relation specified by OID |
| * |
| * This differs from the underlying get_rel_name() function in that it will |
| * throw error instead of silently returning NULL if the OID is bad. |
| */ |
| static char * |
| get_relation_name(Oid relid) |
| { |
| char *relname = get_rel_name(relid); |
| |
| if (!relname) |
| elog(ERROR, "cache lookup failed for relation %u", relid); |
| return relname; |
| } |
| |
| /* |
| * generate_relation_name |
| * Compute the name to display for a relation specified by OID |
| * |
| * The result includes all necessary quoting and schema-prefixing. |
| * |
| * If namespaces isn't NIL, it must be a list of deparse_namespace nodes. |
| * We will forcibly qualify the relation name if it equals any CTE name |
| * visible in the namespace list. |
| */ |
| static char * |
| generate_relation_name(Oid relid, List *namespaces) |
| { |
| HeapTuple tp; |
| Form_pg_class reltup; |
| bool need_qual; |
| ListCell *nslist; |
| char *relname; |
| char *nspname; |
| char *result; |
| |
| tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); |
| if (!HeapTupleIsValid(tp)) |
| elog(ERROR, "cache lookup failed for relation %u", relid); |
| reltup = (Form_pg_class) GETSTRUCT(tp); |
| relname = NameStr(reltup->relname); |
| |
| /* Check for conflicting CTE name */ |
| need_qual = false; |
| foreach(nslist, namespaces) |
| { |
| deparse_namespace *dpns = (deparse_namespace *) lfirst(nslist); |
| ListCell *ctlist; |
| |
| foreach(ctlist, dpns->ctes) |
| { |
| CommonTableExpr *cte = (CommonTableExpr *) lfirst(ctlist); |
| |
| if (strcmp(cte->ctename, relname) == 0) |
| { |
| need_qual = true; |
| break; |
| } |
| } |
| if (need_qual) |
| break; |
| } |
| |
| /* Otherwise, qualify the name if not visible in search path */ |
| if (!need_qual) |
| need_qual = !RelationIsVisible(relid); |
| |
| if (need_qual) |
| nspname = get_namespace_name(reltup->relnamespace); |
| else |
| nspname = NULL; |
| |
| result = quote_qualified_identifier(nspname, relname); |
| |
| ReleaseSysCache(tp); |
| |
| return result; |
| } |
| |
| /* |
| * generate_qualified_relation_name |
| * Compute the name to display for a relation specified by OID |
| * |
| * As above, but unconditionally schema-qualify the name. |
| */ |
| static char * |
| generate_qualified_relation_name(Oid relid) |
| { |
| HeapTuple tp; |
| Form_pg_class reltup; |
| char *relname; |
| char *nspname; |
| char *result; |
| |
| tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); |
| if (!HeapTupleIsValid(tp)) |
| elog(ERROR, "cache lookup failed for relation %u", relid); |
| reltup = (Form_pg_class) GETSTRUCT(tp); |
| relname = NameStr(reltup->relname); |
| |
| nspname = get_namespace_name(reltup->relnamespace); |
| if (!nspname) |
| elog(ERROR, "cache lookup failed for namespace %u", |
| reltup->relnamespace); |
| |
| result = quote_qualified_identifier(nspname, relname); |
| |
| ReleaseSysCache(tp); |
| |
| return result; |
| } |
| |
| /* |
| * generate_function_name |
| * Compute the name to display for a function specified by OID, |
| * given that it is being called with the specified actual arg names and |
| * types. (Those matter because of ambiguous-function resolution rules.) |
| * |
| * If we're dealing with a potentially variadic function (in practice, this |
| * means a FuncExpr or Aggref, not some other way of calling a function), then |
| * has_variadic must specify whether variadic arguments have been merged, |
| * and *use_variadic_p will be set to indicate whether to print VARIADIC in |
| * the output. For non-FuncExpr cases, has_variadic should be false and |
| * use_variadic_p can be NULL. |
| * |
| * The result includes all necessary quoting and schema-prefixing. |
| */ |
| static char * |
| generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes, |
| bool has_variadic, bool *use_variadic_p, |
| ParseExprKind special_exprkind) |
| { |
| char *result; |
| HeapTuple proctup; |
| Form_pg_proc procform; |
| char *proname; |
| bool use_variadic; |
| char *nspname; |
| FuncDetailCode p_result; |
| Oid p_funcid; |
| Oid p_rettype; |
| bool p_retset; |
| int p_nvargs; |
| Oid p_vatype; |
| Oid *p_true_typeids; |
| bool force_qualify = false; |
| |
| proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); |
| if (!HeapTupleIsValid(proctup)) |
| elog(ERROR, "cache lookup failed for function %u", funcid); |
| procform = (Form_pg_proc) GETSTRUCT(proctup); |
| proname = NameStr(procform->proname); |
| |
| /* |
| * Due to parser hacks to avoid needing to reserve CUBE, we need to force |
| * qualification in some special cases. |
| */ |
| if (special_exprkind == EXPR_KIND_GROUP_BY) |
| { |
| if (strcmp(proname, "cube") == 0 || strcmp(proname, "rollup") == 0) |
| force_qualify = true; |
| } |
| |
| /* |
| * Determine whether VARIADIC should be printed. We must do this first |
| * since it affects the lookup rules in func_get_detail(). |
| * |
| * We always print VARIADIC if the function has a merged variadic-array |
| * argument. Note that this is always the case for functions taking a |
| * VARIADIC argument type other than VARIADIC ANY. If we omitted VARIADIC |
| * and printed the array elements as separate arguments, the call could |
| * match a newer non-VARIADIC function. |
| */ |
| if (use_variadic_p) |
| { |
| /* Parser should not have set funcvariadic unless fn is variadic */ |
| Assert(!has_variadic || OidIsValid(procform->provariadic)); |
| use_variadic = has_variadic; |
| *use_variadic_p = use_variadic; |
| } |
| else |
| { |
| Assert(!has_variadic); |
| use_variadic = false; |
| } |
| |
| /* |
| * The idea here is to schema-qualify only if the parser would fail to |
| * resolve the correct function given the unqualified func name with the |
| * specified argtypes and VARIADIC flag. But if we already decided to |
| * force qualification, then we can skip the lookup and pretend we didn't |
| * find it. |
| */ |
| if (!force_qualify) |
| p_result = func_get_detail(list_make1(makeString(proname)), |
| NIL, argnames, nargs, argtypes, |
| !use_variadic, true, false, |
| &p_funcid, &p_rettype, |
| &p_retset, &p_nvargs, &p_vatype, |
| &p_true_typeids, NULL); |
| else |
| { |
| p_result = FUNCDETAIL_NOTFOUND; |
| p_funcid = InvalidOid; |
| } |
| |
| if ((p_result == FUNCDETAIL_NORMAL || |
| p_result == FUNCDETAIL_AGGREGATE || |
| p_result == FUNCDETAIL_WINDOWFUNC) && |
| p_funcid == funcid) |
| nspname = NULL; |
| else |
| nspname = get_namespace_name(procform->pronamespace); |
| |
| result = quote_qualified_identifier(nspname, proname); |
| |
| ReleaseSysCache(proctup); |
| |
| return result; |
| } |
| |
| /* |
| * generate_operator_name |
| * Compute the name to display for an operator specified by OID, |
| * given that it is being called with the specified actual arg types. |
| * (Arg types matter because of ambiguous-operator resolution rules. |
| * Pass InvalidOid for unused arg of a unary operator.) |
| * |
| * The result includes all necessary quoting and schema-prefixing, |
| * plus the OPERATOR() decoration needed to use a qualified operator name |
| * in an expression. |
| */ |
| static char * |
| generate_operator_name(Oid operid, Oid arg1, Oid arg2) |
| { |
| StringInfoData buf; |
| HeapTuple opertup; |
| Form_pg_operator operform; |
| char *oprname; |
| char *nspname; |
| Operator p_result; |
| |
| initStringInfo(&buf); |
| |
| opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operid)); |
| if (!HeapTupleIsValid(opertup)) |
| elog(ERROR, "cache lookup failed for operator %u", operid); |
| operform = (Form_pg_operator) GETSTRUCT(opertup); |
| oprname = NameStr(operform->oprname); |
| |
| /* |
| * The idea here is to schema-qualify only if the parser would fail to |
| * resolve the correct operator given the unqualified op name with the |
| * specified argtypes. |
| */ |
| switch (operform->oprkind) |
| { |
| case 'b': |
| p_result = oper(NULL, list_make1(makeString(oprname)), arg1, arg2, |
| true, -1); |
| break; |
| case 'l': |
| p_result = left_oper(NULL, list_make1(makeString(oprname)), arg2, |
| true, -1); |
| break; |
| default: |
| elog(ERROR, "unrecognized oprkind: %d", operform->oprkind); |
| p_result = NULL; /* keep compiler quiet */ |
| break; |
| } |
| |
| if (p_result != NULL && oprid(p_result) == operid) |
| nspname = NULL; |
| else |
| { |
| nspname = get_namespace_name(operform->oprnamespace); |
| appendStringInfo(&buf, "OPERATOR(%s.", quote_identifier(nspname)); |
| } |
| |
| appendStringInfoString(&buf, oprname); |
| |
| if (nspname) |
| appendStringInfoChar(&buf, ')'); |
| |
| if (p_result != NULL) |
| ReleaseSysCache(p_result); |
| |
| ReleaseSysCache(opertup); |
| |
| return buf.data; |
| } |
| |
| /* |
| * generate_operator_clause --- generate a binary-operator WHERE clause |
| * |
| * This is used for internally-generated-and-executed SQL queries, where |
| * precision is essential and readability is secondary. The basic |
| * requirement is to append "leftop op rightop" to buf, where leftop and |
| * rightop are given as strings and are assumed to yield types leftoptype |
| * and rightoptype; the operator is identified by OID. The complexity |
| * comes from needing to be sure that the parser will select the desired |
| * operator when the query is parsed. We always name the operator using |
| * OPERATOR(schema.op) syntax, so as to avoid search-path uncertainties. |
| * We have to emit casts too, if either input isn't already the input type |
| * of the operator; else we are at the mercy of the parser's heuristics for |
| * ambiguous-operator resolution. The caller must ensure that leftop and |
| * rightop are suitable arguments for a cast operation; it's best to insert |
| * parentheses if they aren't just variables or parameters. |
| */ |
| void |
| generate_operator_clause(StringInfo buf, |
| const char *leftop, Oid leftoptype, |
| Oid opoid, |
| const char *rightop, Oid rightoptype) |
| { |
| HeapTuple opertup; |
| Form_pg_operator operform; |
| char *oprname; |
| char *nspname; |
| |
| opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(opoid)); |
| if (!HeapTupleIsValid(opertup)) |
| elog(ERROR, "cache lookup failed for operator %u", opoid); |
| operform = (Form_pg_operator) GETSTRUCT(opertup); |
| Assert(operform->oprkind == 'b'); |
| oprname = NameStr(operform->oprname); |
| |
| nspname = get_namespace_name(operform->oprnamespace); |
| |
| appendStringInfoString(buf, leftop); |
| if (leftoptype != operform->oprleft) |
| add_cast_to(buf, operform->oprleft); |
| appendStringInfo(buf, " OPERATOR(%s.", quote_identifier(nspname)); |
| appendStringInfoString(buf, oprname); |
| appendStringInfo(buf, ") %s", rightop); |
| if (rightoptype != operform->oprright) |
| add_cast_to(buf, operform->oprright); |
| |
| ReleaseSysCache(opertup); |
| } |
| |
| /* |
| * Add a cast specification to buf. We spell out the type name the hard way, |
| * intentionally not using format_type_be(). This is to avoid corner cases |
| * for CHARACTER, BIT, and perhaps other types, where specifying the type |
| * using SQL-standard syntax results in undesirable data truncation. By |
| * doing it this way we can be certain that the cast will have default (-1) |
| * target typmod. |
| */ |
| static void |
| add_cast_to(StringInfo buf, Oid typid) |
| { |
| HeapTuple typetup; |
| Form_pg_type typform; |
| char *typname; |
| char *nspname; |
| |
| typetup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); |
| if (!HeapTupleIsValid(typetup)) |
| elog(ERROR, "cache lookup failed for type %u", typid); |
| typform = (Form_pg_type) GETSTRUCT(typetup); |
| |
| typname = NameStr(typform->typname); |
| nspname = get_namespace_name(typform->typnamespace); |
| |
| appendStringInfo(buf, "::%s.%s", |
| quote_identifier(nspname), quote_identifier(typname)); |
| |
| ReleaseSysCache(typetup); |
| } |
| |
| /* |
| * generate_qualified_type_name |
| * Compute the name to display for a type specified by OID |
| * |
| * This is different from format_type_be() in that we unconditionally |
| * schema-qualify the name. That also means no special syntax for |
| * SQL-standard type names ... although in current usage, this should |
| * only get used for domains, so such cases wouldn't occur anyway. |
| */ |
| static char * |
| generate_qualified_type_name(Oid typid) |
| { |
| HeapTuple tp; |
| Form_pg_type typtup; |
| char *typname; |
| char *nspname; |
| char *result; |
| |
| tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); |
| if (!HeapTupleIsValid(tp)) |
| elog(ERROR, "cache lookup failed for type %u", typid); |
| typtup = (Form_pg_type) GETSTRUCT(tp); |
| typname = NameStr(typtup->typname); |
| |
| nspname = get_namespace_name(typtup->typnamespace); |
| if (!nspname) |
| elog(ERROR, "cache lookup failed for namespace %u", |
| typtup->typnamespace); |
| |
| result = quote_qualified_identifier(nspname, typname); |
| |
| ReleaseSysCache(tp); |
| |
| return result; |
| } |
| |
| /* |
| * generate_collation_name |
| * Compute the name to display for a collation specified by OID |
| * |
| * The result includes all necessary quoting and schema-prefixing. |
| */ |
| char * |
| generate_collation_name(Oid collid) |
| { |
| HeapTuple tp; |
| Form_pg_collation colltup; |
| char *collname; |
| char *nspname; |
| char *result; |
| |
| tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid)); |
| if (!HeapTupleIsValid(tp)) |
| elog(ERROR, "cache lookup failed for collation %u", collid); |
| colltup = (Form_pg_collation) GETSTRUCT(tp); |
| collname = NameStr(colltup->collname); |
| |
| if (!CollationIsVisible(collid)) |
| nspname = get_namespace_name(colltup->collnamespace); |
| else |
| nspname = NULL; |
| |
| result = quote_qualified_identifier(nspname, collname); |
| |
| ReleaseSysCache(tp); |
| |
| return result; |
| } |
| |
| /* |
| * Given a C string, produce a TEXT datum. |
| * |
| * We assume that the input was palloc'd and may be freed. |
| */ |
| static text * |
| string_to_text(char *str) |
| { |
| text *result; |
| |
| result = cstring_to_text(str); |
| pfree(str); |
| return result; |
| } |
| |
| /* |
| * Generate a C string representing a relation options from text[] datum. |
| */ |
| static void |
| get_reloptions(StringInfo buf, Datum reloptions) |
| { |
| Datum *options; |
| int noptions; |
| int i; |
| |
| deconstruct_array(DatumGetArrayTypeP(reloptions), |
| TEXTOID, -1, false, TYPALIGN_INT, |
| &options, NULL, &noptions); |
| |
| for (i = 0; i < noptions; i++) |
| { |
| char *option = TextDatumGetCString(options[i]); |
| char *name; |
| char *separator; |
| char *value; |
| |
| /* |
| * Each array element should have the form name=value. If the "=" is |
| * missing for some reason, treat it like an empty value. |
| */ |
| name = option; |
| separator = strchr(option, '='); |
| if (separator) |
| { |
| *separator = '\0'; |
| value = separator + 1; |
| } |
| else |
| value = ""; |
| |
| if (i > 0) |
| appendStringInfoString(buf, ", "); |
| appendStringInfo(buf, "%s=", quote_identifier(name)); |
| |
| /* |
| * In general we need to quote the value; but to avoid unnecessary |
| * clutter, do not quote if it is an identifier that would not need |
| * quoting. (We could also allow numbers, but that is a bit trickier |
| * than it looks --- for example, are leading zeroes significant? We |
| * don't want to assume very much here about what custom reloptions |
| * might mean.) |
| */ |
| if (quote_identifier(value) == value) |
| appendStringInfoString(buf, value); |
| else |
| simple_quote_literal(buf, value); |
| |
| pfree(option); |
| } |
| } |
| |
| /* |
| * Generate a C string representing a relation's reloptions, or NULL if none. |
| */ |
| static char * |
| flatten_reloptions(Oid relid) |
| { |
| char *result = NULL; |
| HeapTuple tuple; |
| Datum reloptions; |
| bool isnull; |
| |
| tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); |
| if (!HeapTupleIsValid(tuple)) |
| elog(ERROR, "cache lookup failed for relation %u", relid); |
| |
| reloptions = SysCacheGetAttr(RELOID, tuple, |
| Anum_pg_class_reloptions, &isnull); |
| if (!isnull) |
| { |
| StringInfoData buf; |
| |
| initStringInfo(&buf); |
| get_reloptions(&buf, reloptions); |
| |
| result = buf.data; |
| } |
| |
| ReleaseSysCache(tuple); |
| |
| return result; |
| } |
| |
| /* |
| * Get dynamic table's corresponding task SCHEDULE. |
| */ |
| Datum |
| pg_get_dynamic_table_schedule(PG_FUNCTION_ARGS) |
| { |
| Oid relid = PG_GETARG_OID(0); |
| Relation pg_task; |
| StringInfoData buf; |
| char *username; |
| SysScanDesc scanDescriptor = NULL; |
| ScanKeyData scanKey[2]; |
| HeapTuple heapTuple = NULL; |
| Form_pg_task task = NULL; |
| |
| if (!get_rel_relisdynamic(relid)) |
| { |
| ereport(WARNING, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("relation of oid \"%u\" is not dynamic table", relid))); |
| PG_RETURN_TEXT_P(cstring_to_text("")); |
| } |
| |
| pg_task = table_open(TaskRelationId, AccessShareLock); |
| if (!pg_task) |
| { |
| table_close(pg_task, AccessShareLock); |
| PG_RETURN_TEXT_P(cstring_to_text("")); |
| } |
| |
| initStringInfo(&buf); |
| appendStringInfo(&buf, "%s%u", DYNAMIC_TASK_PREFIX, relid); |
| |
| /* FIXME: is it possible that supersuers try to dump task? */ |
| username = GetUserNameFromId(GetUserId(), false); |
| |
| ScanKeyInit(&scanKey[0], Anum_pg_task_jobname, |
| BTEqualStrategyNumber, F_TEXTEQ, CStringGetTextDatum(buf.data)); |
| ScanKeyInit(&scanKey[1], Anum_pg_task_username, |
| BTEqualStrategyNumber, F_TEXTEQ, CStringGetTextDatum(username)); |
| |
| scanDescriptor = systable_beginscan(pg_task, TaskJobNameUserNameIndexId, false, |
| NULL, 2, scanKey); |
| |
| heapTuple = systable_getnext(scanDescriptor); |
| if (!HeapTupleIsValid(heapTuple)) |
| { |
| ereport(WARNING, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("task \"%s\" does not exist", buf.data))); |
| table_close(pg_task, AccessShareLock); |
| PG_RETURN_TEXT_P(cstring_to_text("")); |
| } |
| |
| task = (Form_pg_task) GETSTRUCT(heapTuple); |
| |
| resetStringInfo(&buf); |
| appendStringInfo(&buf, "%s", text_to_cstring(&task->schedule)); |
| |
| systable_endscan(scanDescriptor); |
| table_close(pg_task, AccessShareLock); |
| |
| PG_RETURN_TEXT_P(string_to_text(buf.data)); |
| } |
| |
| /* ---------- |
| * get_table_distributedby - Get the DISTRIBUTED BY definition of a table. |
| * ---------- |
| */ |
| Datum |
| pg_get_table_distributedby(PG_FUNCTION_ARGS) |
| { |
| Oid relid = PG_GETARG_OID(0); |
| HeapTuple gp_policy_tuple; |
| Form_gp_distribution_policy policyform; |
| StringInfoData buf; |
| |
| /* |
| * Fetch the policy tuple |
| */ |
| gp_policy_tuple = SearchSysCache1(GPPOLICYID, ObjectIdGetDatum(relid)); |
| if (!HeapTupleIsValid(gp_policy_tuple)) |
| { |
| /* not distributed */ |
| PG_RETURN_TEXT_P(cstring_to_text("")); |
| } |
| policyform = (Form_gp_distribution_policy) GETSTRUCT(gp_policy_tuple); |
| |
| initStringInfo(&buf); |
| |
| if (policyform->policytype == SYM_POLICYTYPE_REPLICATED) |
| { |
| appendStringInfo(&buf, "DISTRIBUTED REPLICATED"); |
| } |
| else if (policyform->policytype == SYM_POLICYTYPE_PARTITIONED) |
| { |
| int nkeys; |
| bool isNull; |
| int2vector *distkey; |
| oidvector *distclass; |
| |
| /* |
| * Get the attributes on which to partition. |
| */ |
| distkey = (int2vector *) DatumGetPointer( |
| SysCacheGetAttr(GPPOLICYID, gp_policy_tuple, |
| Anum_gp_distribution_policy_distkey, |
| &isNull)); |
| |
| nkeys = isNull ? 0 : distkey->dim1; |
| |
| if (nkeys == 0) |
| appendStringInfo(&buf, "DISTRIBUTED RANDOMLY"); |
| else |
| { |
| int keyno; |
| char *sep; |
| |
| distclass = (oidvector *) DatumGetPointer( |
| SysCacheGetAttr(GPPOLICYID, gp_policy_tuple, |
| Anum_gp_distribution_policy_distclass, |
| &isNull)); |
| Assert(!isNull); |
| Assert(distclass->dim1 == nkeys); |
| |
| appendStringInfoString(&buf, "DISTRIBUTED BY ("); |
| |
| sep = ""; |
| for (keyno = 0; keyno < nkeys; keyno++) |
| { |
| AttrNumber attnum = distkey->values[keyno]; |
| Oid opclass = distclass->values[keyno]; |
| Oid keycoltype; |
| char *attname; |
| |
| appendStringInfoString(&buf, sep); |
| sep = ", "; |
| |
| attname = get_attname(relid, attnum, false); |
| appendStringInfoString(&buf, quote_identifier(attname)); |
| |
| /* Add the operator class name, if not default */ |
| keycoltype = get_atttype(relid, attnum); |
| get_opclass_name_for_distribution_key(opclass, keycoltype, &buf); |
| } |
| appendStringInfoChar(&buf, ')'); |
| } |
| } |
| else |
| { |
| elog(ERROR, "unexpected policy type '%c'", policyform->policytype); |
| } |
| |
| /* Clean up */ |
| ReleaseSysCache(gp_policy_tuple); |
| |
| PG_RETURN_TEXT_P(string_to_text(buf.data)); |
| } |
| |
| /* |
| * get_range_partbound_string |
| * A C string representation of one range partition bound |
| */ |
| char * |
| get_range_partbound_string(List *bound_datums) |
| { |
| deparse_context context; |
| StringInfo buf = makeStringInfo(); |
| ListCell *cell; |
| char *sep; |
| |
| memset(&context, 0, sizeof(deparse_context)); |
| context.buf = buf; |
| |
| appendStringInfoChar(buf, '('); |
| sep = ""; |
| foreach(cell, bound_datums) |
| { |
| PartitionRangeDatum *datum = |
| castNode(PartitionRangeDatum, lfirst(cell)); |
| |
| appendStringInfoString(buf, sep); |
| if (datum->kind == PARTITION_RANGE_DATUM_MINVALUE) |
| appendStringInfoString(buf, "MINVALUE"); |
| else if (datum->kind == PARTITION_RANGE_DATUM_MAXVALUE) |
| appendStringInfoString(buf, "MAXVALUE"); |
| else |
| { |
| Const *val = castNode(Const, datum->value); |
| |
| get_const_expr(val, &context, -1); |
| } |
| sep = ", "; |
| } |
| appendStringInfoChar(buf, ')'); |
| |
| return buf->data; |
| } |