From 1c3137dd85f9032658bc267d0d225542b3ef2295 Mon Sep 17 00:00:00 2001 From: Jason Petersen Date: Fri, 30 Sep 2016 14:43:16 -0600 Subject: [PATCH] Apply Citus changes to 9.6 ruleutils Keeping this in a separate commit for easier reference. --- src/backend/distributed/utils/ruleutils_96.c | 3122 ++---------------- 1 file changed, 206 insertions(+), 2916 deletions(-) diff --git a/src/backend/distributed/utils/ruleutils_96.c b/src/backend/distributed/utils/ruleutils_96.c index ec966c752..fd8e71ce2 100644 --- a/src/backend/distributed/utils/ruleutils_96.c +++ b/src/backend/distributed/utils/ruleutils_96.c @@ -1,20 +1,24 @@ /*------------------------------------------------------------------------- * - * ruleutils.c - * Functions to convert stored expressions/querytrees back to - * source text + * ruleutils_96.c + * Additional, non core exposed, functions to convert stored + * expressions/querytrees back to source text * * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * src/backend/utils/adt/ruleutils.c + * src/backend/distributed/utils/ruleutils_96.c * + * This needs to be closely in sync with the core code. *------------------------------------------------------------------------- */ + #include "postgres.h" +#if (PG_VERSION_NUM >= 90600 && PG_VERSION_NUM < 90700) + #include #include #include @@ -30,6 +34,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" @@ -37,18 +43,22 @@ #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" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/tlist.h" -#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" @@ -109,6 +119,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; @@ -281,19 +293,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 * @@ -302,32 +301,10 @@ 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 void 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 showTblSpc, - int prettyFlags, 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); static void set_deparse_for_query(deparse_namespace *dpns, Query *query, List *parent_namespaces); -static void set_simple_column_names(deparse_namespace *dpns); static bool has_dangerous_join_using(deparse_namespace *dpns, Node *jtnode); static void set_using_names(deparse_namespace *dpns, Node *jtnode, List *parentUsing); @@ -355,13 +332,13 @@ static void push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell, deparse_namespace *save_dpns); static void pop_ancestor_plan(deparse_namespace *dpns, deparse_namespace *save_dpns); -static void make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, - int prettyFlags); -static void make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, - int prettyFlags, int wrapColumn); static void get_query_def(Query *query, StringInfo buf, List *parentnamespace, TupleDesc resultDesc, 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, @@ -441,2361 +418,29 @@ static void get_opclass_name(Oid opclass, Oid actual_datatype, static Node *processIndirection(Node *node, deparse_context *context); static void printSubscripts(ArrayRef *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); static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2); -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_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_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; - - 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_P(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_P(0); - bool pretty = PG_GETARG_BOOL(1); - int prettyFlags; - RangeVar *viewrel; - Oid viewoid; - char *res; - - prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 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; - Oid argtypes[1]; /* dummy */ - Datum value; - bool isnull; - - /* - * Fetch the pg_trigger tuple by the Oid of the trigger - */ - tgrel = heap_open(TriggerRelationId, AccessShareLock); - - ScanKeyInit(&skey[0], - ObjectIdAttributeNumber, - 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); - heap_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_relid_attribute_name(trigrec->tgrelid, - trigrec->tgattr.values[i]); - appendStringInfoString(&buf, quote_identifier(attname)); - } - } - } - if (TRIGGER_FOR_TRUNCATE(trigrec->tgtype)) - { - if (findx > 0) - appendStringInfoString(&buf, " OR TRUNCATE"); - else - appendStringInfoString(&buf, " TRUNCATE"); - findx++; - } - appendStringInfo(&buf, " ON %s ", - generate_relation_name(trigrec->tgrelid, NIL)); - - 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 "); - } - - 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->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->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_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 PROCEDURE %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(DatumGetByteaP(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); - - heap_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 index tablespace if not default. - * ---------- - */ -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, - 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_INDENT; - - res = pg_get_indexdef_worker(indexrelid, colno, NULL, colno != 0, false, - prettyFlags, true); - - if (res == NULL) - PG_RETURN_NULL(); - - PG_RETURN_TEXT_P(string_to_text(res)); -} - -/* Internal version that 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, true, 0, false); -} - -/* Internal version that just reports the column definitions */ -char * -pg_get_indexdef_columns(Oid indexrelid, bool pretty) -{ - int prettyFlags; - - prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT; - return pg_get_indexdef_worker(indexrelid, 0, NULL, true, 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 showTblSpc, - 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)) - { - 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 USING %s (", - idxrec->indisunique ? "UNIQUE " : "", - quote_identifier(NameStr(idxrelrec->relname)), - generate_relation_name(indrelid, NIL), - 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]; - int16 opt = indoption->values[keyno]; - Oid keycoltype; - Oid keycolcollation; - - if (!colno) - appendStringInfoString(&buf, sep); - sep = ", "; - - if (attnum != 0) - { - /* Simple index column */ - char *attname; - int32 keycoltypmod; - - attname = get_relid_attribute_name(indrelid, attnum); - 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 (indexkey && IsA(indexkey, FuncExpr) && - ((FuncExpr *) indexkey)->funcformat == COERCE_EXPLICIT_CALL) - appendStringInfoString(&buf, str); - else - appendStringInfo(&buf, "(%s)", str); - } - keycoltype = exprType(indexkey); - keycolcollation = exprCollation(indexkey); - } - - if (!attrsOnly && (!colno || colno == keyno + 1)) - { - Oid indcoll; - - /* Add collation, if not default for column */ - indcoll = indcollation->values[keyno]; - 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); - } - - /* - * If it's in a nondefault tablespace, say so, 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)) - { - 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_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_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 = heap_open(ConstraintRelationId, AccessShareLock); - - ScanKeyInit(&scankey[0], - ObjectIdAttributeNumber, - 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); - heap_close(relation, AccessShareLock); - return NULL; - } - elog(ERROR, "cache lookup failed for constraint %u", constraintId); - } - - conForm = (Form_pg_constraint) GETSTRUCT(tup); - - initStringInfo(&buf); - - if (fullCommand) - { - /* - * 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))); - } - - 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; - - /* 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); - - decompile_column_index_array(val, conForm->conrelid, &buf); - - appendStringInfoChar(&buf, ')'); - - indexId = get_constraint_index(constraintId); - - /* 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); - } - - 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, - 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); - heap_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. - */ -static void -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_relid_attribute_name(relId, DatumGetInt16(keys[j])); - - if (j == 0) - appendStringInfoString(buf, quote_identifier(colName)); - else - appendStringInfo(buf, ", %s", quote_identifier(colName)); - } -} - - -/* ---------- - * 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_P(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_P(0); - Oid relid = PG_GETARG_OID(1); - bool pretty = PG_GETARG_BOOL(2); - int prettyFlags; - char *relname; - - prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 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 a 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_P(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 = heap_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); - - /* - * We assume any auto dependency of a sequence on a column must be - * what we are looking for. (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 && - get_rel_relkind(deprec->objid) == RELKIND_SEQUENCE) - { - sequenceId = deprec->objid; - break; - } - } - - systable_endscan(scan); - heap_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; - 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->proisagg) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is an aggregate function", name))); - - /* - * We always qualify the function name, to ensure the right function gets - * replaced. - */ - nsp = get_namespace_name(proc->pronamespace); - appendStringInfo(&buf, "CREATE OR REPLACE FUNCTION %s(", - quote_qualified_identifier(nsp, name)); - (void) print_function_arguments(&buf, proctup, false, true); - appendStringInfoString(&buf, ")\n RETURNS "); - print_function_rettype(&buf, proctup); - - print_function_trftypes(&buf, proctup); - - appendStringInfo(&buf, "\n LANGUAGE %s\n", - quote_identifier(get_language_name(proc->prolang, false))); - - /* Emit some miscellaneous options on one line */ - oldlen = buf.len; - - if (proc->proiswindow) - 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 (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)); - - /* - * Some GUC variable names are 'LIST' type and hence must not - * be quoted. - */ - if (pg_strcasecmp(configitem, "DateStyle") == 0 - || pg_strcasecmp(configitem, "search_path") == 0) - appendStringInfoString(&buf, pos); - 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$", but extend if needed. - */ - initStringInfo(&dq); - appendStringInfoString(&dq, "$function"); - 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(); - - 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 = (List *) stringToNode(str); - Assert(IsA(argdefaults, List)); - 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->proisagg) - { - HeapTuple aggtup; - Form_pg_aggregate agg; - - aggtup = SearchSysCache1(AGGFNOID, - ObjectIdGetDatum(HeapTupleGetOid(proctup))); - if (!HeapTupleIsValid(aggtup)) - elog(ERROR, "cache lookup failed for aggregate %u", - HeapTupleGetOid(proctup)); - 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 transformated 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 = (List *) stringToNode(str); - Assert(IsA(argdefaults, List)); - 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->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 @@ -3015,36 +660,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 * @@ -4199,249 +1814,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; - 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); - - /* - * 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"); - 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", generate_relation_name(ev_class, NIL)); - - /* 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, NULL, - 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, NULL, - prettyFlags, WRAP_COLUMN_DEFAULT, 0); - appendStringInfoChar(buf, ';'); - } -} - - -/* ---------- - * 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; - bool isnull; - - /* - * Get the attribute values from the rules tuple - */ - fno = SPI_fnumber(rulettc, "ev_type"); - ev_type = (char) SPI_getbinval(ruletup, rulettc, fno, &isnull); - - fno = SPI_fnumber(rulettc, "ev_class"); - ev_class = (Oid) SPI_getbinval(ruletup, rulettc, fno, &isnull); - - fno = SPI_fnumber(rulettc, "is_instead"); - is_instead = (bool) SPI_getbinval(ruletup, rulettc, fno, &isnull); - - 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 = heap_open(ev_class, AccessShareLock); - - get_query_def(query, buf, NIL, RelationGetDescr(ev_relation), - prettyFlags, wrapColumn, 0); - appendStringInfoChar(buf, ';'); - - heap_close(ev_relation, AccessShareLock); + get_query_def_extended(query, buffer, NIL, distrelid, shardid, NULL, 0, + WRAP_COLUMN_DEFAULT, 0); } @@ -4456,10 +1840,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(); @@ -4475,6 +1880,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; @@ -4485,6 +1900,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); @@ -4519,6 +1936,9 @@ get_query_def(Query *query, StringInfo buf, List *parentnamespace, query->commandType); break; } + + /* revert back to original search_path */ + PopOverrideSearchPath(); } /* ---------- @@ -5558,7 +2978,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 ", @@ -5668,6 +3090,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", @@ -5732,7 +3160,9 @@ get_update_query_def(Query *query, deparse_context *context) } appendStringInfo(buf, "UPDATE %s%s", only_marker(rte), - generate_relation_name(rte->relid, NIL)); + generate_relation_or_shard_name(rte->relid, + context->distrelid, + context->shardid, NIL)); if (rte->alias != NULL) appendStringInfo(buf, " %s", quote_identifier(rte->alias->aliasname)); @@ -5927,7 +3357,9 @@ get_delete_query_def(Query *query, deparse_context *context) } appendStringInfo(buf, "DELETE FROM %s%s", only_marker(rte), - generate_relation_name(rte->relid, NIL)); + generate_relation_or_shard_name(rte->relid, + context->distrelid, + context->shardid, NIL)); if (rte->alias != NULL) appendStringInfo(buf, " %s", quote_identifier(rte->alias->aliasname)); @@ -5976,6 +3408,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 */ @@ -6132,6 +3600,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_relid_attribute_name(rte->relid, attnum); + } else { /* System column - name is fixed, get it from the catalog */ @@ -9082,8 +6555,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 */ @@ -9094,6 +6569,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); @@ -9258,7 +6748,7 @@ 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); @@ -9635,111 +7125,6 @@ printSubscripts(ArrayRef *aref, 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. - */ - const ScanKeyword *keyword = ScanKeywordLookup(ident, - ScanKeywords, - NumScanKeywords); - - if (keyword != NULL && keyword->category != 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 @@ -9757,6 +7142,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 @@ -9767,7 +7188,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; @@ -9822,36 +7243,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; } /* @@ -10043,129 +7458,4 @@ generate_operator_name(Oid operid, Oid arg1, Oid arg2) return buf.data; } -/* - * 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; -} +#endif /* (PG_VERSION_NUM >= 90600 && PG_VERSION_NUM < 90700) */