diff --git a/src/backend/distributed/planner/distributed_planner.c b/src/backend/distributed/planner/distributed_planner.c index 7ffe336b4..2f166e93c 100644 --- a/src/backend/distributed/planner/distributed_planner.c +++ b/src/backend/distributed/planner/distributed_planner.c @@ -37,6 +37,7 @@ #include "parser/parse_type.h" #if PG_VERSION_NUM >= 120000 #include "optimizer/optimizer.h" +#include "optimizer/plancat.h" #else #include "optimizer/cost.h" #endif @@ -1331,6 +1332,11 @@ AdjustReadIntermediateResultCost(RangeTblEntry *rangeTableEntry, RelOptInfo *rel double rowSizeEstimate = 0; double rowCountEstimate = 0.; double ioCost = 0.; +#if PG_VERSION_NUM >= 120000 + QualCost funcCost = { 0., 0. }; +#else + double funcCost = 0.; +#endif if (rangeTableEntry->rtekind != RTE_FUNCTION || list_length(rangeTableEntry->functions) != 1) @@ -1417,9 +1423,19 @@ AdjustReadIntermediateResultCost(RangeTblEntry *rangeTableEntry, RelOptInfo *rel rowSizeEstimate += 1; } + /* add the cost of parsing a column */ - rowCost += get_func_cost(inputFunctionId) * cpu_operator_cost; +#if PG_VERSION_NUM >= 120000 + add_function_cost(NULL, inputFunctionId, NULL, &funcCost); +#else + funcCost += get_func_cost(inputFunctionId); +#endif } +#if PG_VERSION_NUM >= 120000 + rowCost += funcCost.per_tuple * cpu_operator_cost; +#else + rowCost += funcCost * cpu_operator_cost; +#endif /* estimate the number of rows based on the file size and estimated row size */ rowCountEstimate = Max(1, (double) resultSize / rowSizeEstimate); diff --git a/src/backend/distributed/planner/fast_path_router_planner.c b/src/backend/distributed/planner/fast_path_router_planner.c index ebba58214..06d3f9d63 100644 --- a/src/backend/distributed/planner/fast_path_router_planner.c +++ b/src/backend/distributed/planner/fast_path_router_planner.c @@ -41,10 +41,17 @@ #include "distributed/pg_dist_partition.h" #include "distributed/shardinterval_utils.h" #include "distributed/shard_pruning.h" +#if PG_VERSION_NUM >= 120000 +#include "nodes/makefuncs.h" +#endif #include "nodes/nodeFuncs.h" #include "nodes/parsenodes.h" #include "nodes/pg_list.h" +#if PG_VERSION_NUM >= 120000 +#include "optimizer/optimizer.h" +#else #include "optimizer/clauses.h" +#endif bool EnableFastPathRouterPlanner = true; diff --git a/src/backend/distributed/planner/postgres_planning_functions.c b/src/backend/distributed/planner/postgres_planning_functions.c index 20b05f921..822d29fa1 100644 --- a/src/backend/distributed/planner/postgres_planning_functions.c +++ b/src/backend/distributed/planner/postgres_planning_functions.c @@ -15,7 +15,11 @@ #include "distributed/multi_master_planner.h" #include "nodes/plannodes.h" +#if PG_VERSION_NUM >= 120000 +#include "optimizer/optimizer.h" +#else #include "optimizer/tlist.h" +#endif /* diff --git a/src/backend/distributed/test/prune_shard_list.c b/src/backend/distributed/test/prune_shard_list.c index adfac2f41..44bd5c33e 100644 --- a/src/backend/distributed/test/prune_shard_list.c +++ b/src/backend/distributed/test/prune_shard_list.c @@ -25,9 +25,13 @@ #include "distributed/multi_physical_planner.h" #include "distributed/resource_lock.h" #include "distributed/shard_pruning.h" +#if PG_VERSION_NUM >= 120000 +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#endif +#include "nodes/nodes.h" #include "nodes/pg_list.h" #include "nodes/primnodes.h" -#include "nodes/nodes.h" #include "optimizer/clauses.h" #include "utils/array.h" #include "utils/palloc.h" diff --git a/src/backend/distributed/utils/citus_nodefuncs.c b/src/backend/distributed/utils/citus_nodefuncs.c index ce0aa9f56..6ad0f6cb2 100644 --- a/src/backend/distributed/utils/citus_nodefuncs.c +++ b/src/backend/distributed/utils/citus_nodefuncs.c @@ -309,10 +309,13 @@ GetRangeTblKind(RangeTblEntry *rte) case RTE_JOIN: case RTE_VALUES: case RTE_CTE: - { - rteKind = (CitusRTEKind) rte->rtekind; - break; - } +#if PG_VERSION_NUM >= 120000 + case RTE_RESULT: +#endif + { + rteKind = (CitusRTEKind) rte->rtekind; + break; + } case RTE_FUNCTION: { diff --git a/src/backend/distributed/utils/foreign_key_relationship.c b/src/backend/distributed/utils/foreign_key_relationship.c index 970c445c5..a4ce23458 100644 --- a/src/backend/distributed/utils/foreign_key_relationship.c +++ b/src/backend/distributed/utils/foreign_key_relationship.c @@ -17,6 +17,9 @@ #endif #include "access/htup_details.h" #include "access/stratnum.h" +#if PG_VERSION_NUM >= 120000 +#include "access/table.h" +#endif #include "catalog/pg_constraint.h" #include "distributed/foreign_key_relationship.h" #include "distributed/hash_helpers.h" diff --git a/src/backend/distributed/utils/ruleutils_12.c b/src/backend/distributed/utils/ruleutils_12.c index 8179c7184..78ae460e3 100644 --- a/src/backend/distributed/utils/ruleutils_12.c +++ b/src/backend/distributed/utils/ruleutils_12.c @@ -1,15 +1,15 @@ /*------------------------------------------------------------------------- * - * ruleutils.c + * ruleutils_11.c * Functions to convert stored expressions/querytrees back to * source text * - * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * src/backend/distributed/utils/ruleutils_12.c + * src/backend/distributed/utils/ruleutils_11.c * * This needs to be closely in sync with the core code. *------------------------------------------------------------------------- @@ -17,7 +17,7 @@ #include "postgres.h" -#if PG_VERSION_NUM >= 120000 && PG_VERSION_NUM < 130000 +#if (PG_VERSION_NUM >= 120000) && (PG_VERSION_NUM < 130000) #include #include @@ -36,6 +36,8 @@ #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" #include "catalog/pg_depend.h" +#include "catalog/pg_extension.h" +#include "catalog/pg_foreign_data_wrapper.h" #include "catalog/pg_language.h" #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" @@ -45,9 +47,13 @@ #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" #include "commands/defrem.h" +#include "commands/extension.h" #include "commands/tablespace.h" #include "common/keywords.h" +#include "distributed/citus_nodefuncs.h" +#include "distributed/citus_ruleutils.h" #include "executor/spi.h" +#include "foreign/foreign.h" #include "funcapi.h" #include "mb/pg_wchar.h" #include "miscadmin.h" @@ -57,6 +63,7 @@ #include "parser/parse_node.h" #include "parser/parse_agg.h" #include "parser/parse_func.h" +#include "parser/parse_node.h" #include "parser/parse_oper.h" #include "parser/parser.h" #include "parser/parsetree.h" @@ -66,10 +73,8 @@ #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" @@ -94,7 +99,6 @@ /* 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 @@ -102,7 +106,6 @@ /* 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) /* ---------- @@ -121,6 +124,8 @@ typedef struct int wrapColumn; /* max line length, or -1 for no limit */ int indentLevel; /* current indent level for prettyprint */ bool varprefix; /* true to print prefixes on Vars */ + Oid distrelid; /* the distributed table being modified, if valid */ + int64 shardid; /* a distributed table's shardid, if positive */ ParseExprKind special_exprkind; /* set only for exprkinds needing special * handling */ } deparse_context; @@ -293,19 +298,6 @@ typedef struct } NameHashEntry; -/* ---------- - * 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 * @@ -314,3081 +306,151 @@ bool quote_all_identifiers = false; * 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 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 set_rtable_names(deparse_namespace *dpns, List *parent_namespaces, - Bitmapset *rels_used); + 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); + List *parent_namespaces); static bool has_dangerous_join_using(deparse_namespace *dpns, Node *jtnode); static void set_using_names(deparse_namespace *dpns, Node *jtnode, - List *parentUsing); + List *parentUsing); static void set_relation_column_names(deparse_namespace *dpns, - RangeTblEntry *rte, - deparse_columns *colinfo); + RangeTblEntry *rte, + deparse_columns *colinfo); static void set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte, - deparse_columns *colinfo); + deparse_columns *colinfo); static bool colname_is_unique(const char *colname, deparse_namespace *dpns, - deparse_columns *colinfo); + deparse_columns *colinfo); static char *make_colname_unique(char *colname, deparse_namespace *dpns, - deparse_columns *colinfo); + 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); + deparse_columns *colinfo); static void flatten_join_using_qual(Node *qual, - List **leftvars, List **rightvars); + List **leftvars, List **rightvars); static char *get_rtable_name(int rtindex, deparse_context *context); static void set_deparse_planstate(deparse_namespace *dpns, PlanState *ps); static void push_child_plan(deparse_namespace *dpns, PlanState *ps, - deparse_namespace *save_dpns); + deparse_namespace *save_dpns); static void pop_child_plan(deparse_namespace *dpns, - deparse_namespace *save_dpns); + deparse_namespace *save_dpns); static void push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell, - deparse_namespace *save_dpns); + 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); + deparse_namespace *save_dpns); static void get_query_def(Query *query, StringInfo buf, List *parentnamespace, - TupleDesc resultDesc, - int prettyFlags, int wrapColumn, int startIndent); + TupleDesc resultDesc, + int prettyFlags, int wrapColumn, int startIndent); +static void get_query_def_extended(Query *query, StringInfo buf, + List *parentnamespace, Oid distrelid, int64 shardid, + TupleDesc resultDesc, 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); + TupleDesc resultDesc); static void get_insert_query_def(Query *query, deparse_context *context); static void get_update_query_def(Query *query, deparse_context *context); static void get_update_query_targetlist_def(Query *query, List *targetList, - deparse_context *context, - RangeTblEntry *rte); + deparse_context *context, + RangeTblEntry *rte); static void get_delete_query_def(Query *query, deparse_context *context); static void get_utility_query_def(Query *query, deparse_context *context); static void get_basic_select_query(Query *query, deparse_context *context, - TupleDesc resultDesc); + TupleDesc resultDesc); static void get_target_list(List *targetList, deparse_context *context, - TupleDesc resultDesc); + TupleDesc resultDesc); static void get_setop_query(Node *setOp, Query *query, - deparse_context *context, - TupleDesc resultDesc); + deparse_context *context, + TupleDesc resultDesc); static Node *get_rule_sortgroupclause(Index ref, List *tlist, - bool force_colno, - deparse_context *context); + bool force_colno, + deparse_context *context); static void get_rule_groupingset(GroupingSet *gset, List *targetlist, - bool omit_parens, deparse_context *context); + bool omit_parens, deparse_context *context); static void get_rule_orderby(List *orderList, List *targetList, - bool force_colno, deparse_context *context); + bool force_colno, deparse_context *context); static void get_rule_windowclause(Query *query, deparse_context *context); static void get_rule_windowspec(WindowClause *wc, List *targetList, - deparse_context *context); + deparse_context *context); static char *get_variable(Var *var, int levelsup, bool istoplevel, - deparse_context *context); + deparse_context *context); static void get_special_variable(Node *node, deparse_context *context, - void *private); + void *private); static void resolve_special_varno(Node *node, deparse_context *context, - void *private, - void (*callback) (Node *, deparse_context *, void *)); + void *private, + void (*callback) (Node *, deparse_context *, void *)); static Node *find_param_referent(Param *param, deparse_context *context, - deparse_namespace **dpns_p, ListCell **ancestor_cell_p); + 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); + int indentBefore, int indentAfter, int indentPlus); static void removeStringInfoSpaces(StringInfo str); static void get_rule_expr(Node *node, deparse_context *context, - bool showimplicit); + bool showimplicit); static void get_rule_expr_toplevel(Node *node, deparse_context *context, - bool showimplicit); + bool showimplicit); static void get_rule_expr_funccall(Node *node, deparse_context *context, - bool showimplicit); + 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); + bool showimplicit); static void get_agg_expr(Aggref *aggref, deparse_context *context, - Aggref *original_aggref); + Aggref *original_aggref); static void get_agg_combine_expr(Node *node, deparse_context *context, - void *private); + void *private); static void get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context); static void get_coercion_expr(Node *arg, deparse_context *context, - Oid resulttype, int32 resulttypmod, - Node *parentNode); + Oid resulttype, int32 resulttypmod, + Node *parentNode); static void get_const_expr(Const *constval, deparse_context *context, - int showtype); + 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); + bool showimplicit); static void get_from_clause(Query *query, const char *prefix, - deparse_context *context); + deparse_context *context); static void get_from_clause_item(Node *jtnode, Query *query, - deparse_context *context); + deparse_context *context); static void get_column_alias_list(deparse_columns *colinfo, - deparse_context *context); + deparse_context *context); static void get_from_clause_coldeflist(RangeTblFunction *rtfunc, - deparse_columns *colinfo, - deparse_context *context); + deparse_columns *colinfo, + deparse_context *context); static void get_tablesample_def(TableSampleClause *tablesample, - deparse_context *context); + deparse_context *context); static void get_opclass_name(Oid opclass, Oid actual_datatype, - StringInfo buf); + StringInfo buf); static Node *processIndirection(Node *node, deparse_context *context); -static void printSubscripts(SubscriptingRef *sbsref, deparse_context *context); +static void printSubscripts(SubscriptingRef *aref, 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_relation_or_shard_name(Oid relid, Oid distrelid, + int64 shardid, List *namespaces); +static char *generate_fragment_name(char *schemaName, char *tableName); static char *generate_function_name(Oid funcid, int nargs, - List *argnames, Oid *argtypes, - bool has_variadic, bool *use_variadic_p, - ParseExprKind special_exprkind); + 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); #define only_marker(rte) ((rte)->inh ? "" : "ONLY ") -/* ---------- - * 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; -} - - -/* ---------- - * 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 + * pg_get_query_def parses back one query tree, and outputs the resulting query + * string into given buffer. */ -static char * -pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn) +void +pg_get_query_def(Query *query, StringInfo buffer) { - 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; + get_query_def(query, buffer, NIL, NULL, 0, WRAP_COLUMN_DEFAULT, 0); } -/* ---------- - * 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; - Oid argtypes[1]; /* dummy */ - 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.ctes = NIL; - 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; - - get_rule_expr(qual, &context, false); - - appendStringInfoString(&buf, ") "); - } - - appendStringInfo(&buf, "EXECUTE FUNCTION %s(", - generate_function_name(trigrec->tgfoid, 0, - NIL, argtypes, - 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; -} - -/* ---------- - * 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(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]; - - /* 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], keycoltype, &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, 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 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; - - statexttup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statextid)); - - if (!HeapTupleIsValid(statexttup)) - { - if (missing_ok) - return NULL; - elog(ERROR, "cache lookup failed for statistics object %u", statextid); - } - - statextrec = (Form_pg_statistic_ext) GETSTRUCT(statexttup); - - initStringInfo(&buf); - - 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; - if (enabled[i] == STATS_EXT_DEPENDENCIES) - dependencies_enabled = true; - if (enabled[i] == STATS_EXT_MCV) - mcv_enabled = true; - } - - /* - * 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. - */ - if (!ndistinct_enabled || !dependencies_enabled || !mcv_enabled) - { - 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 "); - - 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)); - } - - appendStringInfo(&buf, " FROM %s", - generate_relation_name(statextrec->stxrelid, NIL)); - - ReleaseSysCache(statexttup); - - return buf.data; -} - -/* - * 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) - appendStringInfo(&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(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 ". - */ -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); -} - -/* - * 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 = get_constraint_index(constraintId); - - /* 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, 's', - &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, 'i', - &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, 's', - &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; -} - - -/* ---------- - * 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); -} - - -/* ---------- - * 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); - StrNCpy(NameStr(*result), NameStr(role_rec->rolname), NAMEDATALEN); - 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 */ , - 'i' /* 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(lc)) - appendStringInfoString(&buf, ", "); - } - } - else - simple_quote_literal(&buf, pos); - appendStringInfoChar(&buf, '\n'); - } - } - } - - /* And finally the function definition ... */ - 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, '$'); - - appendStringInfoString(&buf, dq.data); - appendStringInfoString(&buf, prosrc); - appendStringInfoString(&buf, dq.data); - - 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)); - } - - appendStringInfoString(buf, rbuf.data); -} - -/* - * 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; - 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; - List *argdefaults; - - 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: - 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(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, "\n TRANSFORM "); - for (i = 0; i < ntypes; i++) - { - if (i != 0) - appendStringInfoString(buf, ", "); - appendStringInfo(buf, "FOR TYPE %s", format_type_be(trftypes[i])); - } - } -} - -/* - * 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)); -} - - -/* - * 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.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; - - 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->ctes = NIL; - 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_rtable - Build deparse context for a plan's rtable - * - * 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_planstate() is applied to it.) - * - * In addition to the plan's rangetable list, pass the per-RTE alias names - * assigned by a previous call to select_rtable_names_for_explain. - */ -List * -deparse_context_for_plan_rtable(List *rtable, 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 = rtable; - dpns->rtable_names = rtable_names; - dpns->ctes = NIL; - - /* - * 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_planstate - 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 PlanState 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.) - * - * Note: planstate really ought to be declared as "PlanState *", but we use - * "Node *" to avoid having to include execnodes.h in ruleutils.h. - * - * The ancestors list is a list of the PlanState's parent PlanStates, the - * most-closely-nested first. This is needed to resolve PARAM_EXEC Params. - * Note we assume that all the PlanStates share the same rtable. - * - * Once this function has been called, deparse_expression() can be called on - * subsidiary expression(s) of the specified PlanState 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_planstate(List *dpcontext, - Node *planstate, 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 */ - set_deparse_planstate(dpns, (PlanState *) planstate); - dpns->ancestors = ancestors; - - 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.ctes = NIL; - 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 @@ -3608,36 +670,6 @@ set_deparse_for_query(deparse_namespace *dpns, Query *query, } } -/* - * 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 * @@ -4797,267 +1829,18 @@ pop_ancestor_plan(deparse_namespace *dpns, deparse_namespace *save_dpns) /* ---------- - * make_ruledef - reconstruct the CREATE RULE command - * for a given pg_rewrite tuple + * deparse_shard_query - Parse back a query for execution on a shard + * + * Builds an SQL string to perform the provided query on a specific shard and + * places this string into the provided buffer. * ---------- */ -static void -make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, - int prettyFlags) +void +deparse_shard_query(Query *query, Oid distrelid, int64 shardid, + StringInfo buffer) { - char *rulename; - char ev_type; - Oid ev_class; - bool is_instead; - char *ev_qual; - char *ev_action; - List *actions = NIL; - 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); - - /* these could be nulls */ - fno = SPI_fnumber(rulettc, "ev_qual"); - ev_qual = SPI_getvalue(ruletup, rulettc, fno); - - fno = SPI_fnumber(rulettc, "ev_action"); - ev_action = SPI_getvalue(ruletup, rulettc, fno); - if (ev_action != NULL) - actions = (List *) stringToNode(ev_action); - - 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 (ev_qual == NULL) - ev_qual = ""; - if (strlen(ev_qual) > 0 && 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.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; - - 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, - prettyFlags, WRAP_COLUMN_DEFAULT, 0); - if (prettyFlags) - appendStringInfoString(buf, ";\n"); - else - appendStringInfoString(buf, "; "); - } - appendStringInfoString(buf, ");"); - } - else if (list_length(actions) == 0) - { - appendStringInfoString(buf, "NOTHING;"); - } - else - { - Query *query; - - query = (Query *) linitial(actions); - get_query_def(query, buf, NIL, viewResultDesc, - 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 = NIL; - 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); - - /* these could be nulls */ - fno = SPI_fnumber(rulettc, "ev_qual"); - ev_qual = SPI_getvalue(ruletup, rulettc, fno); - - fno = SPI_fnumber(rulettc, "ev_action"); - ev_action = SPI_getvalue(ruletup, rulettc, fno); - if (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; - } - - ev_relation = table_open(ev_class, AccessShareLock); - - get_query_def(query, buf, NIL, RelationGetDescr(ev_relation), - prettyFlags, wrapColumn, 0); - appendStringInfoChar(buf, ';'); - - table_close(ev_relation, AccessShareLock); + get_query_def_extended(query, buffer, NIL, distrelid, shardid, NULL, 0, + WRAP_COLUMN_DEFAULT, 0); } @@ -5072,10 +1855,31 @@ static void get_query_def(Query *query, StringInfo buf, List *parentnamespace, TupleDesc resultDesc, int prettyFlags, int wrapColumn, int startIndent) +{ + get_query_def_extended(query, buf, parentnamespace, InvalidOid, 0, resultDesc, + prettyFlags, wrapColumn, startIndent); +} + + +/* ---------- + * get_query_def_extended - Parse back one query parsetree, optionally + * with extension using a shard identifier. + * + * If distrelid is valid and shardid is positive, the provided shardid is added + * any time the provided relid is deparsed, so that the query may be executed + * on a placement for the given shard. + * ---------- + */ +static void +get_query_def_extended(Query *query, StringInfo buf, List *parentnamespace, + Oid distrelid, int64 shardid, TupleDesc resultDesc, + int prettyFlags, int wrapColumn, int startIndent) { deparse_context context; deparse_namespace dpns; + OverrideSearchPath *overridePath = NULL; + /* Guard against excessively long or deeply-nested queries */ CHECK_FOR_INTERRUPTS(); check_stack_depth(); @@ -5091,6 +1895,16 @@ get_query_def(Query *query, StringInfo buf, List *parentnamespace, */ AcquireRewriteLocks(query, false, false); + /* + * Set search_path to NIL so that all objects outside of pg_catalog will be + * schema-prefixed. pg_catalog will be added automatically when we call + * PushOverrideSearchPath(), since we set addCatalog to true; + */ + overridePath = GetOverrideSearchPath(CurrentMemoryContext); + overridePath->schemas = NIL; + overridePath->addCatalog = true; + PushOverrideSearchPath(overridePath); + context.buf = buf; context.namespaces = lcons(&dpns, list_copy(parentnamespace)); context.windowClause = NIL; @@ -5101,6 +1915,8 @@ get_query_def(Query *query, StringInfo buf, List *parentnamespace, context.wrapColumn = wrapColumn; context.indentLevel = startIndent; context.special_exprkind = EXPR_KIND_NONE; + context.distrelid = distrelid; + context.shardid = shardid; set_deparse_for_query(&dpns, query, parentnamespace); @@ -5135,6 +1951,9 @@ get_query_def(Query *query, StringInfo buf, List *parentnamespace, query->commandType); break; } + + /* revert back to original search_path */ + PopOverrideSearchPath(); } /* ---------- @@ -6194,7 +3013,9 @@ get_insert_query_def(Query *query, deparse_context *context) appendStringInfoChar(buf, ' '); } appendStringInfo(buf, "INSERT INTO %s ", - generate_relation_name(rte->relid, NIL)); + generate_relation_or_shard_name(rte->relid, + context->distrelid, + context->shardid, NIL)); /* INSERT requires AS keyword for target alias */ if (rte->alias != NULL) appendStringInfo(buf, "AS %s ", @@ -6313,6 +3134,12 @@ get_insert_query_def(Query *query, deparse_context *context) else if (OidIsValid(confl->constraint)) { char *constraint = get_constraint_name(confl->constraint); + int64 shardId = context->shardid; + + if (shardId > 0) + { + AppendShardIdToName(&constraint, shardId); + } if (!constraint) elog(ERROR, "cache lookup failed for constraint %u", @@ -6369,18 +3196,43 @@ get_update_query_def(Query *query, deparse_context *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)); + + /* if it's a shard, do differently */ + if (GetRangeTblKind(rte) == CITUS_RTE_SHARD) + { + char *fragmentSchemaName = NULL; + char *fragmentTableName = NULL; + + ExtractRangeTblExtraData(rte, NULL, &fragmentSchemaName, &fragmentTableName, NULL); + + /* use schema and table name from the remote alias */ + appendStringInfo(buf, "UPDATE %s%s", + only_marker(rte), + generate_fragment_name(fragmentSchemaName, fragmentTableName)); + + if(rte->eref != NULL) + appendStringInfo(buf, " %s", + quote_identifier(rte->eref->aliasname)); + } + else + { + appendStringInfo(buf, "UPDATE %s%s", + only_marker(rte), + generate_relation_or_shard_name(rte->relid, + context->distrelid, + context->shardid, NIL)); + + if (rte->alias != NULL) + appendStringInfo(buf, " %s", + quote_identifier(rte->alias->aliasname)); + } + appendStringInfoString(buf, " SET "); /* Deparse targetlist */ @@ -6495,7 +3347,6 @@ get_update_query_targetlist_def(Query *query, List *targetList, if (sbsref->refassgnexpr == NULL) break; - expr = (Node *) sbsref->refassgnexpr; } else if (IsA(expr, CoerceToDomain)) @@ -6577,18 +3428,42 @@ get_delete_query_def(Query *query, deparse_context *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)); + + /* if it's a shard, do differently */ + if (GetRangeTblKind(rte) == CITUS_RTE_SHARD) + { + char *fragmentSchemaName = NULL; + char *fragmentTableName = NULL; + + ExtractRangeTblExtraData(rte, NULL, &fragmentSchemaName, &fragmentTableName, NULL); + + /* use schema and table name from the remote alias */ + appendStringInfo(buf, "DELETE FROM %s%s", + only_marker(rte), + generate_fragment_name(fragmentSchemaName, fragmentTableName)); + + if(rte->eref != NULL) + appendStringInfo(buf, " %s", + quote_identifier(rte->eref->aliasname)); + } + else + { + appendStringInfo(buf, "DELETE FROM %s%s", + only_marker(rte), + generate_relation_or_shard_name(rte->relid, + context->distrelid, + context->shardid, 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); @@ -6634,6 +3509,42 @@ get_utility_query_def(Query *query, deparse_context *context) simple_quote_literal(buf, stmt->payload); } } + else if (query->utilityStmt && IsA(query->utilityStmt, TruncateStmt)) + { + TruncateStmt *stmt = (TruncateStmt *) query->utilityStmt; + List *relationList = stmt->relations; + ListCell *relationCell = NULL; + + appendContextKeyword(context, "", + 0, PRETTYINDENT_STD, 1); + + appendStringInfo(buf, "TRUNCATE TABLE"); + + foreach(relationCell, relationList) + { + RangeVar *relationVar = (RangeVar *) lfirst(relationCell); + Oid relationId = RangeVarGetRelid(relationVar, NoLock, false); + char *relationName = generate_relation_or_shard_name(relationId, + context->distrelid, + context->shardid, NIL); + appendStringInfo(buf, " %s", relationName); + + if (lnext(relationCell) != NULL) + { + appendStringInfo(buf, ","); + } + } + + if (stmt->restart_seqs) + { + appendStringInfo(buf, " RESTART IDENTITY"); + } + + if (stmt->behavior == DROP_CASCADE) + { + appendStringInfo(buf, " CASCADE"); + } + } else { /* Currently only NOTIFY utility commands can appear in rules */ @@ -6790,6 +3701,11 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context) elog(ERROR, "invalid attnum %d for relation \"%s\"", attnum, rte->eref->aliasname); } + else if (GetRangeTblKind(rte) == CITUS_RTE_SHARD) + { + /* System column on a Citus shard */ + attname = get_attname(rte->relid, attnum, false); + } else { /* System column - name is fixed, get it from the catalog */ @@ -7088,8 +4004,8 @@ get_name_for_var_field(Var *var, int fieldno, 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 + * This case should not occur: a column of a table or values list + * shouldn't have type RECORD. Fall through and fail (most * likely) at the bottom. */ break; @@ -7648,7 +4564,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) return true; /* own parentheses */ } case T_BoolExpr: /* lower precedence */ - case T_SubscriptingRef: /* other separators */ + case T_SubscriptingRef: /* other separators */ case T_ArrayExpr: /* other separators */ case T_RowExpr: /* other separators */ case T_CoalesceExpr: /* own parentheses */ @@ -7698,7 +4614,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) return false; return true; /* own parentheses */ } - case T_SubscriptingRef: /* other separators */ + case T_SubscriptingRef: /* other separators */ case T_ArrayExpr: /* other separators */ case T_RowExpr: /* other separators */ case T_CoalesceExpr: /* own parentheses */ @@ -9572,6 +6488,11 @@ get_const_expr(Const *constval, deparse_context *context, int showtype) } break; + case BITOID: + case VARBITOID: + appendStringInfo(buf, "B'%s'", extval); + break; + case BOOLOID: if (strcmp(extval, "t") == 0) appendStringInfoString(buf, "true"); @@ -9821,17 +6742,17 @@ get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit) forboth(lc1, tf->ns_uris, lc2, tf->ns_names) { Node *expr = (Node *) lfirst(lc1); - Value *ns_node = (Value *) lfirst(lc2); + char *name = strVal(lfirst(lc2)); if (!first) appendStringInfoString(buf, ", "); else first = false; - if (ns_node != NULL) + if (name != NULL) { get_rule_expr(expr, context, showimplicit); - appendStringInfo(buf, " AS %s", strVal(ns_node)); + appendStringInfo(buf, " AS %s", name); } else { @@ -10015,6 +6936,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) deparse_columns *colinfo = deparse_columns_fetch(varno, dpns); RangeTblFunction *rtfunc1 = NULL; bool printalias; + CitusRTEKind rteKind = GetRangeTblKind(rte); if (rte->lateral) appendStringInfoString(buf, "LATERAL "); @@ -10026,8 +6948,10 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) /* Normal relation RTE */ appendStringInfo(buf, "%s%s", only_marker(rte), - generate_relation_name(rte->relid, - context->namespaces)); + generate_relation_or_shard_name(rte->relid, + context->distrelid, + context->shardid, + context->namespaces)); break; case RTE_SUBQUERY: /* Subquery RTE */ @@ -10038,6 +6962,21 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) appendStringInfoChar(buf, ')'); break; case RTE_FUNCTION: + /* if it's a shard, do differently */ + if (GetRangeTblKind(rte) == CITUS_RTE_SHARD) + { + char *fragmentSchemaName = NULL; + char *fragmentTableName = NULL; + + ExtractRangeTblExtraData(rte, NULL, &fragmentSchemaName, &fragmentTableName, NULL); + + /* use schema and table name from the remote alias */ + appendStringInfoString(buf, + generate_fragment_name(fragmentSchemaName, + fragmentTableName)); + break; + } + /* Function RTE */ rtfunc1 = (RangeTblFunction *) linitial(rte->functions); @@ -10196,6 +7135,11 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) if (strcmp(refname, rte->ctename) != 0) printalias = true; } + else if (rte->rtekind == RTE_SUBQUERY) + { + /* subquery requires alias too */ + printalias = true; + } if (printalias) appendStringInfo(buf, " %s", quote_identifier(refname)); @@ -10205,15 +7149,24 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) /* Reconstruct the columndef list, which is also the aliases */ get_from_clause_coldeflist(rtfunc1, colinfo, context); } - else + else if (GetRangeTblKind(rte) != CITUS_RTE_SHARD) { /* Else print column aliases as needed */ get_column_alias_list(colinfo, context); } + /* check if column's are given aliases in distributed tables */ + else if (colinfo->parentUsing != NIL) + { + Assert(colinfo->printaliases); + get_column_alias_list(colinfo, context); + } /* Tablesample clause must go after any alias */ - if (rte->rtekind == RTE_RELATION && rte->tablesample) + if ((rteKind == CITUS_RTE_RELATION || rteKind == CITUS_RTE_SHARD) && + rte->tablesample) + { get_tablesample_def(rte->tablesample, context); + } } else if (IsA(jtnode, JoinExpr)) { @@ -10554,11 +7507,10 @@ processIndirection(Node *node, deparse_context *context) } else if (IsA(node, SubscriptingRef)) { - SubscriptingRef *sbsref = (SubscriptingRef *) node; + SubscriptingRef *sbsref = (SubscriptingRef *) node; if (sbsref->refassgnexpr == NULL) break; - printSubscripts(sbsref, context); /* @@ -10616,109 +7568,6 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context) } } -/* - * 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 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 @@ -10736,6 +7585,42 @@ get_relation_name(Oid relid) return relname; } +/* + * generate_relation_or_shard_name + * Compute the name to display for a relation or shard + * + * If the provided relid is equal to the provided distrelid, this function + * returns a shard-extended relation name; otherwise, it falls through to a + * simple generate_relation_name call. + */ +static char * +generate_relation_or_shard_name(Oid relid, Oid distrelid, int64 shardid, + List *namespaces) +{ + char *relname = NULL; + + if (relid == distrelid) + { + relname = get_relation_name(relid); + + if (shardid > 0) + { + Oid schemaOid = get_rel_namespace(relid); + char *schemaName = get_namespace_name(schemaOid); + + AppendShardIdToName(&relname, shardid); + + relname = quote_qualified_identifier(schemaName, relname); + } + } + else + { + relname = generate_relation_name(relid, namespaces); + } + + return relname; +} + /* * generate_relation_name * Compute the name to display for a relation specified by OID @@ -10746,7 +7631,7 @@ get_relation_name(Oid relid) * We will forcibly qualify the relation name if it equals any CTE name * visible in the namespace list. */ -static char * +char * generate_relation_name(Oid relid, List *namespaces) { HeapTuple tp; @@ -10801,36 +7686,30 @@ generate_relation_name(Oid relid, List *namespaces) } /* - * generate_qualified_relation_name - * Compute the name to display for a relation specified by OID + * generate_fragment_name + * Compute the name to display for a shard or merged table * - * As above, but unconditionally schema-qualify the name. + * The result includes all necessary quoting and schema-prefixing. The schema + * name can be NULL for regular shards. For merged tables, they are always + * declared within a job-specific schema, and therefore can't have null schema + * names. */ static char * -generate_qualified_relation_name(Oid relid) +generate_fragment_name(char *schemaName, char *tableName) { - HeapTuple tp; - Form_pg_class reltup; - char *relname; - char *nspname; - char *result; + StringInfo fragmentNameString = makeStringInfo(); - 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); + if (schemaName != NULL) + { + appendStringInfo(fragmentNameString, "%s.%s", quote_identifier(schemaName), + quote_identifier(tableName)); + } + else + { + appendStringInfoString(fragmentNameString, quote_identifier(tableName)); + } - 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; + return fragmentNameString->data; } /* @@ -10888,11 +7767,16 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes, * 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. + * Currently, 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. + * + * In principle, if VARIADIC wasn't originally specified and the array + * actual argument is deconstructable, we could print the array elements + * separately and not print VARIADIC, thus more nearly reproducing the + * original input. For the moment that seems like too much complication + * for the benefit, and anyway we do not know whether VARIADIC was + * originally specified if it's a non-ANY type. */ if (use_variadic_p) { @@ -10961,7 +7845,6 @@ generate_operator_name(Oid operid, Oid arg1, Oid arg2) Form_pg_operator operform; char *oprname; char *nspname; - Operator p_result; initStringInfo(&buf); @@ -10972,293 +7855,21 @@ generate_operator_name(Oid operid, Oid arg1, Oid arg2) 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. + * Unlike generate_operator_name() in postgres/src/backend/utils/adt/ruleutils.c, + * we don't check if the operator is in current namespace or not. This is + * because this check is costly when the operator is not in current namespace. */ - 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; - case 'r': - p_result = right_oper(NULL, list_make1(makeString(oprname)), arg1, - 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)); - } - + nspname = get_namespace_name(operform->oprnamespace); + Assert(nspname != NULL); + appendStringInfo(&buf, "OPERATOR(%s.", quote_identifier(nspname)); appendStringInfoString(&buf, oprname); - - if (nspname) - appendStringInfoChar(&buf, ')'); - - if (p_result != NULL) - ReleaseSysCache(p_result); + appendStringInfoChar(&buf, ')'); 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'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; - Datum *options; - int noptions; - int i; - - initStringInfo(&buf); - - deconstruct_array(DatumGetArrayTypeP(reloptions), - TEXTOID, -1, false, 'i', - &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); - } - - result = buf.data; - } - - ReleaseSysCache(tuple); - - return result; -} - /* * get_one_range_partition_bound_string * A C string representation of one range partition bound @@ -11299,4 +7910,4 @@ get_range_partbound_string(List *bound_datums) return buf->data; } -#endif /* PG_VERSION_NUM >= 120000 && PG_VERSION_NUM < 130000 */ +#endif /* (PG_VERSION_NUM >= 110000) && (PG_VERSION_NUM < 120000) */ diff --git a/src/backend/distributed/worker/worker_merge_protocol.c b/src/backend/distributed/worker/worker_merge_protocol.c index 5e438a1b7..07b80c655 100644 --- a/src/backend/distributed/worker/worker_merge_protocol.c +++ b/src/backend/distributed/worker/worker_merge_protocol.c @@ -21,6 +21,13 @@ #include "access/genam.h" #endif #include "access/htup_details.h" +#if PG_VERSION_NUM >= 120000 +#include "access/table.h" +#define heap_beginscan_catalog table_beginscan_catalog +#define TableHeapScanDesc TableScanDesc +#else +#define TableHeapScanDesc HeapScanDesc +#endif #include "access/xact.h" #include "catalog/dependency.h" #include "catalog/pg_namespace.h" @@ -265,7 +272,7 @@ Datum worker_cleanup_job_schema_cache(PG_FUNCTION_ARGS) { Relation pgNamespace = NULL; - HeapScanDesc scanDescriptor = NULL; + TableHeapScanDesc scanDescriptor = NULL; ScanKey scanKey = NULL; int scanKeyCount = 0; HeapTuple heapTuple = NULL; diff --git a/src/include/distributed/multi_physical_planner.h b/src/include/distributed/multi_physical_planner.h index cabb46df0..ea6b0a5d8 100644 --- a/src/include/distributed/multi_physical_planner.h +++ b/src/include/distributed/multi_physical_planner.h @@ -51,13 +51,12 @@ typedef enum CitusRTEKind CITUS_RTE_SUBQUERY = RTE_SUBQUERY, /* subquery in FROM */ CITUS_RTE_JOIN = RTE_JOIN, /* join */ CITUS_RTE_FUNCTION = RTE_FUNCTION, /* function in FROM */ -#if (PG_VERSION_NUM >= 100000) CITUS_RTE_TABLEFUNC = RTE_TABLEFUNC, /* TableFunc(.., column list) */ -#endif CITUS_RTE_VALUES = RTE_VALUES, /* VALUES (), (), ... */ CITUS_RTE_CTE = RTE_CTE, /* common table expr (WITH list element) */ -#if (PG_VERSION_NUM >= 100000) CITUS_RTE_NAMEDTUPLESTORE = RTE_NAMEDTUPLESTORE, /* tuplestore, e.g. for triggers */ +#if (PG_VERSION_NUM >= 120000) + CITUS_RTE_RESULT = RTE_RESULT, /* RTE represents an empty FROM clause */ #endif CITUS_RTE_SHARD, CITUS_RTE_REMOTE_QUERY