From b072482d1ebafbc6d99ebd11001f1a7c66296487 Mon Sep 17 00:00:00 2001 From: Hou Zhijie Date: Thu, 6 Apr 2023 20:33:02 +0800 Subject: [PATCH 6/6] Deparser and DDL replication for the rest commands. --- contrib/test_decoding/test_decoding.c | 5 +- doc/src/sgml/logical-replication.sgml | 211 +- src/backend/catalog/aclchk.c | 9 +- src/backend/catalog/pg_publication.c | 1 + src/backend/commands/collationcmds.c | 21 +- src/backend/commands/ddl_deparse.c | 6641 +++++++++++++++++- src/backend/commands/event_trigger.c | 34 + src/backend/commands/publicationcmds.c | 63 +- src/backend/commands/seclabel.c | 6 + src/backend/replication/logical/ddltrigger.c | 88 +- src/backend/replication/pgoutput/pgoutput.c | 20 +- src/backend/tcop/cmdtag.c | 10 +- src/backend/tcop/utility.c | 4 +- src/backend/utils/adt/regproc.c | 51 + src/backend/utils/adt/ruleutils.c | 293 +- src/backend/utils/cache/relcache.c | 1 + src/bin/pg_dump/pg_dump.c | 23 +- src/bin/pg_dump/pg_dump.h | 1 + src/bin/psql/describe.c | 12 +- src/include/catalog/pg_publication.h | 4 + src/include/commands/collationcmds.h | 3 +- src/include/commands/event_trigger.h | 2 + src/include/tcop/cmdtag.h | 2 +- src/include/tcop/ddl_deparse.h | 2 + src/include/tcop/deparse_utility.h | 8 + src/include/utils/acl.h | 2 + src/include/utils/aclchk_internal.h | 1 + src/include/utils/builtins.h | 1 + src/include/utils/ruleutils.h | 10 + src/test/regress/expected/psql.out | 6 +- src/test/regress/expected/publication.out | 420 +- src/test/regress/expected/subscription.out | 144 +- 32 files changed, 7447 insertions(+), 652 deletions(-) diff --git a/contrib/test_decoding/test_decoding.c b/contrib/test_decoding/test_decoding.c index 763d5c007f..da5b41df59 100644 --- a/contrib/test_decoding/test_decoding.c +++ b/contrib/test_decoding/test_decoding.c @@ -774,9 +774,11 @@ pg_decode_ddl_message(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, appendStringInfo(ctx->out, "cmdtype: Simple, "); break; case DCT_TableDropStart: + case DCT_ObjectDropStart: appendStringInfo(ctx->out, "cmdtype: Drop start, "); break; case DCT_TableDropEnd: + case DCT_ObjectDropEnd: appendStringInfo(ctx->out, "cmdtype: Drop end, "); break; case DCT_TableAlter: @@ -785,9 +787,6 @@ pg_decode_ddl_message(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, case DCT_ObjectCreate: appendStringInfo(ctx->out, "cmdtype: Create, "); break; - case DCT_ObjectDrop: - appendStringInfo(ctx->out, "cmdtype: Drop, "); - break; default: appendStringInfo(ctx->out, "cmdtype: Invalid, "); break; diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml index 75b395222b..10b3d5f5f1 100644 --- a/doc/src/sgml/logical-replication.sgml +++ b/doc/src/sgml/logical-replication.sgml @@ -1480,6 +1480,11 @@ test_sub=# SELECT * FROM t1 ORDER BY id; + + + all: this option enables replication of all supported DDL commands. + + table: this option enables replication of Table DDL commands, @@ -1550,22 +1555,22 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); ALTER AGGREGATE - - + X ALTER CAST - - + X ALTER COLLATION - - + X ALTER CONVERSION - - + X @@ -1575,12 +1580,12 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); ALTER DEFAULT PRIVILEGES - - + X ALTER DOMAIN - - + X @@ -1590,22 +1595,22 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); ALTER EXTENSION - - + X ALTER FOREIGN DATA WRAPPER - - + X ALTER FOREIGN TABLE - - + X ALTER FUNCTION - - + X @@ -1615,7 +1620,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); ALTER LANGUAGE - - + X @@ -1625,32 +1630,32 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); ALTER MATERIALIZED VIEW - - + X ALTER OPERATOR - - + X ALTER OPERATOR CLASS - - + X ALTER OPERATOR FAMILY - - + X ALTER POLICY - - + X ALTER PROCEDURE - - + X @@ -1665,32 +1670,32 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); ALTER ROUTINE - - + X ALTER RULER - - + X ALTER SCHEMA - - + X ALTER SEQUENCE - - + X ALTER SERVER - - + X ALTER STATISTICS - - + X @@ -1715,22 +1720,22 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); ALTER TEXT SEARCH CONFIGURATION - - + X ALTER TEXT SEARCH DICTIONARY - - + X ALTER TEXT SEARCH PARSER - - + X ALTER TEXT SEARCH TEMPLATE - - + X @@ -1740,22 +1745,22 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); ALTER TRIGGER - - + X ALTER TYPE - - + X ALTER USER MAPPING - - + X ALTER VIEW - - + X @@ -1800,7 +1805,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); COMMENT - - + X @@ -1825,32 +1830,32 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); CREATE ACCESS METHOD - - + X CREATE AGGREGATE - - + X CREATE CAST - - + X CREATE COLLATION - - + X CREATE CONSTRAINT - - + X CREATE CONVERSION - - + X @@ -1860,7 +1865,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); CREATE DOMAIN - - + X @@ -1870,22 +1875,22 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); CREATE EXTENSION - - + X CREATE FOREIGN DATA WRAPPER - - + X CREATE FOREIGN TABLE - - + X CREATE FUNCTION - - + X @@ -1895,37 +1900,37 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); CREATE LANGUAGE - - + X CREATE MATERIALIZED VIEW - - + X CREATE OPERATOR - - + X CREATE OPERATOR CLASS - - + X CREATE OPERATOR FAMILY - - + X CREATE POLICY - - + X CREATE PROCEDURE - - + X @@ -1940,27 +1945,27 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); CREATE RULE - - + X CREATE SCHEMA - - + X CREATE SEQUENCE - - + X CREATE SERVER - - + X CREATE STATISTICS - - + X @@ -1985,47 +1990,47 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); CREATE TEXT SEARCH CONFIGURATION - - + X CREATE TEXT SEARCH DICTIONARY - - + X CREATE TEXT SEARCH PARSER - - + X CREATE TEXT SEARCH TEMPLATE - - + X CREATE TRANSFORM - - + X CREATE TRIGGER - - + X CREATE TYPE - - + X CREATE USER MAPPING - - + X CREATE VIEW - - + X @@ -2055,7 +2060,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); DISCARD ALL - - + X @@ -2080,32 +2085,32 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); DROP ACCESS METHOD - - + X DROP AGGREGATE - - + X DROP CAST - - + X DROP COLLATION - - + X DROP CONSTRAINT - - + X DROP CONVERSION - - + X @@ -2115,7 +2120,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); DROP DOMAIN - - + X @@ -2125,22 +2130,22 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); DROP EXTENSION - - + X DROP FOREIGN DATA WRAPPER - - + X DROP FOREIGN TABLE - - + X DROP FUNCTION - - + X @@ -2150,27 +2155,27 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); DROP LANGUAGE - - + X DROP MATERIALIZED VIEW - - + X DROP OPERATOR - - + X DROP OPERATOR CLASS - - + X DROP OPERATOR FAMILY - - + X @@ -2180,12 +2185,12 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); DROP POLICY - - + X DROP PROCEDURE - - + X @@ -2200,32 +2205,32 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); DROP ROUTINE - - + X DROP RULE - - + X DROP SCHEMA - - + X DROP SEQUENCE - - + X DROP SERVER - - + X DROP STATISTICS - - + X @@ -2245,47 +2250,47 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); DROP TEXT SEARCH CONFIGURATION - - + X DROP TEXT SEARCH DICTIONARY - - + X DROP TEXT SEARCH PARSER - - + X DROP TEXT SEARCH TEMPLATE - - + X DROP TRANSFORM - - + X DROP TRIGGER - - + X DROP TYPE - - + X DROP USER MAPPING - - + X DROP VIEW - - + X @@ -2305,7 +2310,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); GRANT - - + X @@ -2315,7 +2320,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); IMPORT FOREIGN SCHEMA - - + X @@ -2370,7 +2375,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); REFRESH MATERIALIZED VIEW - - + X @@ -2390,7 +2395,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); REVOKE - - + X @@ -2415,7 +2420,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table'); SECURITY LABEL - - + X diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 45cdcd3dc6..4b5a3ba950 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -129,7 +129,6 @@ static void expand_all_col_privileges(Oid table_oid, Form_pg_class classForm, AclMode *col_privileges, int num_col_privileges); static AclMode string_to_privilege(const char *privname); -static const char *privilege_to_string(AclMode privilege); static AclMode restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs, AclMode privileges, Oid objectId, Oid grantorId, @@ -385,11 +384,10 @@ ExecuteGrantStmt(GrantStmt *stmt) ListCell *cell; const char *errormsg; AclMode all_privileges; + Oid grantor = InvalidOid; if (stmt->grantor) { - Oid grantor; - grantor = get_rolespec_oid(stmt->grantor, false); /* @@ -408,6 +406,9 @@ ExecuteGrantStmt(GrantStmt *stmt) istmt.is_grant = stmt->is_grant; istmt.objtype = stmt->objtype; + /* Copy the grantor id needed for DDL deparsing of Grant */ + istmt.grantor_uid = grantor; + /* Collect the OIDs of the target objects */ switch (stmt->targtype) { @@ -2622,7 +2623,7 @@ string_to_privilege(const char *privname) return 0; /* appease compiler */ } -static const char * +const char * privilege_to_string(AclMode privilege) { switch (privilege) diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c index 1a3105e025..bb8ee605bd 100644 --- a/src/backend/catalog/pg_publication.c +++ b/src/backend/catalog/pg_publication.c @@ -1028,6 +1028,7 @@ GetPublication(Oid pubid) pub->pubactions.pubupdate = pubform->pubupdate; pub->pubactions.pubdelete = pubform->pubdelete; pub->pubactions.pubtruncate = pubform->pubtruncate; + pub->pubactions.pubddl_all = pubform->pubddl_all; pub->pubactions.pubddl_table = pubform->pubddl_table; pub->pubactions.pubddl_index = pubform->pubddl_index; pub->pubviaroot = pubform->pubviaroot; diff --git a/src/backend/commands/collationcmds.c b/src/backend/commands/collationcmds.c index c91fe66d9b..285a5dcf8a 100644 --- a/src/backend/commands/collationcmds.c +++ b/src/backend/commands/collationcmds.c @@ -50,9 +50,21 @@ typedef struct /* * CREATE COLLATION + * + * pstate: parse state. + * names: qualified collation names (a list of String). + * parameters: collation attributes (a list of DefElem). + * if_not_exists: if true, don't fail on duplicate name, just print a notice + * and return InvalidOid. + * from_existing_collid: output argument which, if not NULL, is set to the + * address of existing collation that was used to create in case of + * CREATE COLLATION any_name FROM existing_collation. + * + * If successful, returns the address of the new collation. */ ObjectAddress -DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_exists) +DefineCollation(ParseState *pstate, List *names, List *parameters, + bool if_not_exists, ObjectAddress *from_existing_collid) { char *collName; Oid collNamespace; @@ -143,6 +155,13 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e if (!HeapTupleIsValid(tp)) elog(ERROR, "cache lookup failed for collation %u", collid); + /* + * Make from existing collationid available to callers for statements + * such as CREATE COLLATION any_name FROM existing_collation. + */ + if (from_existing_collid && OidIsValid(collid)) + ObjectAddressSet(*from_existing_collid, CollationRelationId, collid); + collprovider = ((Form_pg_collation) GETSTRUCT(tp))->collprovider; collisdeterministic = ((Form_pg_collation) GETSTRUCT(tp))->collisdeterministic; collencoding = ((Form_pg_collation) GETSTRUCT(tp))->collencoding; diff --git a/src/backend/commands/ddl_deparse.c b/src/backend/commands/ddl_deparse.c index 5d85070a85..fa4ef7b971 100644 --- a/src/backend/commands/ddl_deparse.c +++ b/src/backend/commands/ddl_deparse.c @@ -59,6 +59,8 @@ #include "catalog/pg_opfamily.h" #include "catalog/pg_policy.h" #include "catalog/pg_proc.h" +#include "catalog/pg_publication_rel.h" +#include "catalog/pg_publication_namespace.h" #include "catalog/pg_range.h" #include "catalog/pg_rewrite.h" #include "catalog/pg_sequence.h" @@ -71,6 +73,7 @@ #include "catalog/pg_type.h" #include "catalog/pg_user_mapping.h" #include "commands/defrem.h" +#include "commands/publicationcmds.h" #include "commands/sequence.h" #include "commands/tablespace.h" #include "foreign/foreign.h" @@ -159,6 +162,7 @@ bool verbose = true; static void append_array_object(ObjTree *tree, char *sub_fmt, List *array); static void append_bool_object(ObjTree *tree, char *sub_fmt, bool value); +static void append_float_object(ObjTree *tree, char *sub_fmt, float8 value); static void append_null_object(ObjTree *tree, char *sub_fmt); static void append_object_object(ObjTree *tree, char *sub_fmt, ObjTree *value); static char *append_object_to_format_string(ObjTree *tree, char *sub_fmt); @@ -168,11 +172,21 @@ static void append_string_object(ObjTree *tree, char *sub_fmt, char *name, static void format_type_detailed(Oid type_oid, int32 typemod, Oid *nspid, char **typname, char **typemodstr, bool *typarray); +static List *FunctionGetDefaults(text *proargdefaults); static ObjElem *new_object(ObjType type, char *name); static ObjTree *new_objtree_for_qualname_id(Oid classId, Oid objectId); +static ObjTree *new_objtree_for_rolespec(RoleSpec *spec); static ObjElem *new_object_object(ObjTree *value); static ObjTree *new_objtree_VA(char *fmt, int numobjs,...); +static ObjTree *new_objtree(char *fmt); +static ObjElem *new_string_object(char *value); static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state, char *owner); +static void pg_get_indexdef_detailed(Oid indexrelid, + char **index_am, + char **definition, + char **reloptions, + char **tablespace, + char **whereClause); static char *RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext, List **exprs); @@ -180,8 +194,18 @@ static ObjTree *deparse_ColumnDef(Relation relation, List *dpcontext, bool compo ColumnDef *coldef, bool is_alter, List **exprs); static ObjTree *deparse_ColumnIdentity(Oid seqrelid, char identity, bool alter_table); static ObjTree *deparse_ColumnSetOptions(AlterTableCmd *subcmd); +static ObjTree *deparse_DefineStmt_Aggregate(Oid objectId, DefineStmt *define); +static ObjTree *deparse_DefineStmt_Collation(Oid objectId, DefineStmt *define, + ObjectAddress fromCollid); +static ObjTree *deparse_DefineStmt_Operator(Oid objectId, DefineStmt *define); +static ObjTree *deparse_DefineStmt_Type(Oid objectId, DefineStmt *define); +static ObjTree *deparse_DefineStmt_TSConfig(Oid objectId, DefineStmt *define, ObjectAddress copied); +static ObjTree *deparse_DefineStmt_TSParser(Oid objectId, DefineStmt *define); +static ObjTree *deparse_DefineStmt_TSDictionary(Oid objectId, DefineStmt *define); +static ObjTree *deparse_DefineStmt_TSTemplate(Oid objectId, DefineStmt *define); static ObjTree *deparse_DefElem(DefElem *elem, bool is_reset); +static ObjTree *deparse_FunctionSet(VariableSetKind kind, char *name, char *value); static ObjTree *deparse_OnCommitClause(OnCommitAction option); static ObjTree *deparse_RelSetOptions(AlterTableCmd *subcmd); @@ -190,6 +214,7 @@ static inline ObjElem *deparse_Seq_Cycle(Form_pg_sequence seqdata, bool alter_ta static inline ObjElem *deparse_Seq_IncrementBy(Form_pg_sequence seqdata, bool alter_table); static inline ObjElem *deparse_Seq_Minvalue(Form_pg_sequence seqdata, bool alter_table); static inline ObjElem *deparse_Seq_Maxvalue(Form_pg_sequence seqdata, bool alter_table); +static ObjElem *deparse_Seq_OwnedBy(Oid sequenceId, bool alter_table); static inline ObjElem *deparse_Seq_Restart(int64 last_value); static inline ObjElem *deparse_Seq_Startwith(Form_pg_sequence seqdata, bool alter_table); static inline ObjElem *deparse_Seq_As(Form_pg_sequence seqdata); @@ -201,10 +226,15 @@ static inline ObjElem *deparse_Type_Typmod_Out(Form_pg_type typForm); static inline ObjElem *deparse_Type_Analyze(Form_pg_type typForm); static inline ObjElem *deparse_Type_Subscript(Form_pg_type typForm); +static ObjTree *deparse_FdwOptions(List *options, char *colname, + bool altercoloptions); + static List *deparse_InhRelations(Oid objectId); static List *deparse_TableElements(Relation relation, List *tableElements, List *dpcontext, bool typed, bool composite); +static char *DomainGetDefault(HeapTuple domTup, bool missing_ok); + /* * Append present as false to a tree. */ @@ -214,6 +244,42 @@ append_not_present(ObjTree *tree) append_bool_object(tree, "present", false); } +/* + * Append an int32 parameter to a tree. + */ +static void +append_int_object(ObjTree *tree, char *sub_fmt, int32 value) +{ + ObjElem *param; + char *object_name; + + Assert(sub_fmt); + + object_name = append_object_to_format_string(tree, sub_fmt); + + param = new_object(ObjTypeInteger, object_name); + param->value.flt = value; + append_premade_object(tree, param); +} + +/* + * Append a float8 parameter to a tree. + */ +static void +append_float_object(ObjTree *tree, char *sub_fmt, float8 value) +{ + ObjElem *param; + char *object_name; + + Assert(sub_fmt); + + object_name = append_object_to_format_string(tree, sub_fmt); + + param = new_object(ObjTypeFloat, object_name); + param->value.flt = value; + append_premade_object(tree, param); +} + /* * Append an array parameter to a tree. */ @@ -420,6 +486,34 @@ append_string_object(ObjTree *tree, char *sub_fmt, char * object_name, append_premade_object(tree, param); } +/* + * Append a NULL-or-quoted-literal clause. Useful for COMMENT and SECURITY + * LABEL. + * + * Verbose syntax + * %{null}s %{literal}s + */ +static void +append_literal_or_null(ObjTree *parent, char *elemname, char *value) +{ + ObjTree *top; + ObjTree *part; + + top = new_objtree(""); + part = new_objtree_VA("NULL", 1, + "present", ObjTypeBool, !value); + append_object_object(top, "%{null}s", part); + + part = new_objtree_VA("", 1, + "present", ObjTypeBool, value != NULL); + + if (value) + append_string_object(part, "%{value}L", "value", value); + append_object_object(top, "%{literal}s", part); + + append_object_object(parent, elemname, top); +} + /* * Similar to format_type_extended, except we return each bit of information * separately: @@ -522,6 +616,31 @@ format_type_detailed(Oid type_oid, int32 typemod, ReleaseSysCache(tuple); } +/* + * Return the default values of arguments to a function, as a list of + * deparsed expressions. + */ +static List * +FunctionGetDefaults(text *proargdefaults) +{ + List *nodedefs; + List *strdefs = NIL; + ListCell *cell; + + nodedefs = (List *) stringToNode(text_to_cstring(proargdefaults)); + if (!IsA(nodedefs, List)) + elog(ERROR, "proargdefaults is not a list"); + + foreach(cell, nodedefs) + { + Node *onedef = lfirst(cell); + + strdefs = lappend(strdefs, deparse_expression(onedef, NIL, false, false)); + } + + return strdefs; +} + /* * Return the string representation of the given RELPERSISTENCE value. */ @@ -711,6 +830,57 @@ new_objtree_for_type(Oid typeId, int32 typmod) "typarray", ObjTypeBool, type_array); } +/* + * Helper routine for %{}R objects, with role specified by RoleSpec node. + * Special values such as ROLESPEC_CURRENT_USER are expanded to their final + * names. + */ +static ObjTree * +new_objtree_for_rolespec(RoleSpec *spec) +{ + char *roletype; + + if (spec->roletype != ROLESPEC_PUBLIC) + roletype = get_rolespec_name(spec); + else + roletype = pstrdup(""); + + return new_objtree_VA(NULL, 2, + "is_public", ObjTypeBool, spec->roletype == ROLESPEC_PUBLIC, + "rolename", ObjTypeString, roletype); +} + +/* + * Helper routine for %{}R objects, with role specified by OID. (ACL_ID_PUBLIC + * means to use "public"). + */ +static ObjTree * +new_objtree_for_role_id(Oid roleoid) +{ + ObjTree *role; + + if (roleoid != ACL_ID_PUBLIC) + { + HeapTuple roltup; + char *role_name; + + roltup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleoid)); + if (!HeapTupleIsValid(roltup)) + elog(ERROR, "cache lookup failed for role with OID %u", roleoid); + + role_name = NameStr(((Form_pg_authid) GETSTRUCT(roltup))->rolname); + role = new_objtree_VA("%{rolename}I", 1, + "rolename", ObjTypeString, pstrdup(role_name)); + + ReleaseSysCache(roltup); + } + else + role = new_objtree_VA("%{rolename}I", 1, + "rolename", ObjTypeString, "public"); + + return role; +} + /* * Allocate a new object tree to store parameter values -- varargs version. * @@ -788,6 +958,22 @@ new_objtree_VA(char *fmt, int numobjs,...) return tree; } +/* + * Allocate a new string object. + */ +static ObjElem * +new_string_object(char *value) +{ + ObjElem *param; + + Assert(value); + + param = new_object(ObjTypeString, NULL); + param->value.string = value; + + return param; +} + /* * Process the pre-built format string from the ObjTree into the output parse * state. @@ -1950,6 +2136,83 @@ deparse_Seq_Minvalue(Form_pg_sequence seqdata, bool alter_table) return new_object_object(ret); } +/* + * Deparse the sequence OWNED BY command. + * + * Verbose syntax + * OWNED BY %{owner}D + */ +static ObjElem * +deparse_Seq_OwnedBy(Oid sequenceId, bool alter_table) +{ + ObjTree *ret = NULL; + Relation depRel; + SysScanDesc scan; + ScanKeyData keys[3]; + HeapTuple tuple; + + depRel = table_open(DependRelationId, AccessShareLock); + ScanKeyInit(&keys[0], + Anum_pg_depend_classid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationRelationId)); + ScanKeyInit(&keys[1], + Anum_pg_depend_objid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(sequenceId)); + ScanKeyInit(&keys[2], + Anum_pg_depend_objsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(0)); + + scan = systable_beginscan(depRel, DependDependerIndexId, true, + NULL, 3, keys); + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + Oid ownerId; + Form_pg_depend depform; + ObjTree *tmp_obj; + char *colname; + + depform = (Form_pg_depend) GETSTRUCT(tuple); + + /* Only consider AUTO dependencies on pg_class */ + if (depform->deptype != DEPENDENCY_AUTO) + continue; + if (depform->refclassid != RelationRelationId) + continue; + if (depform->refobjsubid <= 0) + continue; + + ownerId = depform->refobjid; + colname = get_attname(ownerId, depform->refobjsubid, false); + if (colname == NULL) + continue; + + tmp_obj = new_objtree_for_qualname_id(RelationRelationId, ownerId); + append_string_object(tmp_obj, "attrname", "attrname", colname); + ret = new_objtree_VA("OWNED BY %{owner}D", 2, + "clause", ObjTypeString, "owned", + "owner", ObjTypeObject, tmp_obj); + } + + systable_endscan(scan); + relation_close(depRel, AccessShareLock); + + /* + * If there's no owner column, emit an empty OWNED BY element, set up so + * that it won't print anything. + */ + if (!ret) + /* XXX this shouldn't happen ... */ + ret = new_objtree_VA("OWNED BY %{owner}D", 3, + "clause", ObjTypeString, "owned", + "owner", ObjTypeNull, + "present", ObjTypeBool, false); + + return new_object_object(ret); +} + /* * Deparse the sequence RESTART option. * @@ -2214,6 +2477,64 @@ deparse_TableElements(Relation relation, List *tableElements, List *dpcontext, return elements; } +/* + * Deparse a CreateSeqStmt. + * + * Given a sequence OID and the parse tree that created it, return an ObjTree + * representing the creation command. + * + * Verbose syntax + * CREATE %{persistence}s SEQUENCE %{identity}D %{definition: }s + */ +static ObjTree * +deparse_CreateSeqStmt(Oid objectId, Node *parsetree) +{ + ObjTree *ret; + Relation relation; + List *elems = NIL; + Form_pg_sequence seqform; + Sequence_values *seqvalues; + CreateSeqStmt *createSeqStmt = (CreateSeqStmt *) parsetree; + + /* + * Sequence for IDENTITY COLUMN output separately (via CREATE TABLE or + * ALTER TABLE); return empty here. + */ + if (createSeqStmt->for_identity) + return NULL; + + seqvalues = get_sequence_values(objectId); + seqform = seqvalues->seqform; + + /* Definition elements */ + elems = lappend(elems, deparse_Seq_Cache(seqform, false)); + elems = lappend(elems, deparse_Seq_Cycle(seqform, false)); + elems = lappend(elems, deparse_Seq_IncrementBy(seqform, false)); + elems = lappend(elems, deparse_Seq_Minvalue(seqform, false)); + elems = lappend(elems, deparse_Seq_Maxvalue(seqform, false)); + elems = lappend(elems, deparse_Seq_Startwith(seqform, false)); + elems = lappend(elems, deparse_Seq_Restart(seqvalues->last_value)); + elems = lappend(elems, deparse_Seq_As(seqform)); + + /* We purposefully do not emit OWNED BY here */ + + relation = relation_open(objectId, AccessShareLock); + + ret = new_objtree_VA("CREATE %{persistence}s SEQUENCE %{if_not_exists}s %{identity}D %{definition: }s", 4, + "persistence", ObjTypeString, + get_persistence_str(relation->rd_rel->relpersistence), + "if_not_exists", ObjTypeString, + createSeqStmt->if_not_exists ? "IF NOT EXISTS" : "", + "identity", ObjTypeObject, + new_objtree_for_qualname(relation->rd_rel->relnamespace, + RelationGetRelationName(relation)), + "definition", ObjTypeArray, elems); + + relation_close(relation, AccessShareLock); + + return ret; +} + /* * Deparse an IndexStmt. * @@ -2559,7 +2880,7 @@ deparse_AlterRelation(CollectedCommand *cmd) Assert(cmd->type == SCT_AlterTable); stmt = (AlterTableStmt *) cmd->parsetree; - Assert(IsA(stmt, AlterTableStmt)); + Assert(IsA(stmt, AlterTableStmt) || IsA(stmt, ViewStmt)); /* * ALTER TABLE subcommands generated for TableLikeClause is processed in @@ -2602,8 +2923,10 @@ deparse_AlterRelation(CollectedCommand *cmd) elog(ERROR, "unexpected relkind %d", rel->rd_rel->relkind); } - ret = new_objtree_VA("ALTER %{objtype}s %{identity}D", 2, + ret = new_objtree_VA("ALTER %{objtype}s %{identity}D", 3, "objtype", ObjTypeString, reltype, + "only", ObjTypeString, + stmt->relation->inh ? "" : "ONLY", "identity", ObjTypeObject, new_objtree_for_qualname(rel->rd_rel->relnamespace, RelationGetRelationName(rel))); @@ -3035,13 +3358,12 @@ deparse_AlterRelation(CollectedCommand *cmd) } break; -#ifdef TODOLIST case AT_AlterColumnGenericOptions: tmp_obj = deparse_FdwOptions((List *) subcmd->def, - subcmd->name); + subcmd->name, true); subcmds = lappend(subcmds, new_object_object(tmp_obj)); break; -#endif + case AT_ChangeOwner: tmp_obj = new_objtree_VA("OWNER TO %{owner}I", 2, "type", ObjTypeString, "change owner", @@ -3432,6 +3754,200 @@ deparse_drop_command(const char *objidentity, const char *objecttype, return command; } +/* + * Deparse an AlterCollationStmt (ALTER COLLATION) + * + * Given a collation OID and the parse tree that created it, return an ObjTree + * representing the alter command. + * + * Verbose syntax: + * ALTER COLLATION %{identity}O REFRESH VERSION + */ +static ObjTree * +deparse_AlterCollation(Oid objectId, Node *parsetree) +{ + ObjTree *ret; + HeapTuple colTup; + Form_pg_collation colForm; + + colTup = SearchSysCache1(COLLOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(colTup)) + elog(ERROR, "cache lookup failed for collation with OID %u", objectId); + colForm = (Form_pg_collation) GETSTRUCT(colTup); + + ret = new_objtree_VA("ALTER COLLATION %{identity}O REFRESH VERSION", 1, + "identity", ObjTypeObject, + new_objtree_for_qualname(colForm->collnamespace, + NameStr(colForm->collname))); + + ReleaseSysCache(colTup); + + return ret; +} + +/* + * Handle deparsing setting of Function + * + * Verbose syntax + * RESET ALL + * OR + * SET %{set_name}I TO %{set_value}s + * OR + * RESET %{set_name}I + */ +static ObjTree * +deparse_FunctionSet(VariableSetKind kind, char *name, char *value) +{ + ObjTree *ret; + + if (kind == VAR_RESET_ALL) + ret = new_objtree("RESET ALL"); + else if (kind == VAR_SET_VALUE) + { + ret = new_objtree_VA("SET %{set_name}I", 1, + "set_name", ObjTypeString, name); + + /* + * Some GUC variable names are 'LIST' type and hence must not be + * quoted. + */ + if (GetConfigOptionFlags(name, true) & GUC_LIST_QUOTE) + append_string_object(ret, "TO %{set_value}s", "set_value", value); + else + append_string_object(ret, "TO %{set_value}L", "set_value", value); + } + else + ret = new_objtree_VA("RESET %{set_name}I", 1, + "set_name", ObjTypeString, name); + + return ret; +} + +/* + * Deparse an AlterFunctionStmt (ALTER FUNCTION/ROUTINE/PROCEDURE) + * + * Given a function OID and the parse tree that created it, return an ObjTree + * representing the alter command. + * + * Verbose syntax: + * ALTER FUNCTION/ROUTINE/PROCEDURE %{signature}s %{definition: }s + */ +static ObjTree * +deparse_AlterFunction(Oid objectId, Node *parsetree) +{ + AlterFunctionStmt *node = (AlterFunctionStmt *) parsetree; + ObjTree *ret; + ObjTree *sign; + HeapTuple procTup; + Form_pg_proc procForm; + List *params = NIL; + List *elems = NIL; + ListCell *cell; + int i; + + /* Get the pg_proc tuple */ + procTup = SearchSysCache1(PROCOID, objectId); + if (!HeapTupleIsValid(procTup)) + elog(ERROR, "cache lookup failed for function with OID %u", objectId); + procForm = (Form_pg_proc) GETSTRUCT(procTup); + + if (procForm->prokind == PROKIND_PROCEDURE) + ret = new_objtree("ALTER PROCEDURE"); + else + ret = new_objtree(node->objtype == OBJECT_ROUTINE ? + "ALTER ROUTINE" : "ALTER FUNCTION"); + + /* + * ALTER FUNCTION does not change signature so we can use catalog to get + * input type Oids. + */ + for (i = 0; i < procForm->pronargs; i++) + { + ObjTree *tmp_obj; + + tmp_obj = new_objtree_VA("%{type}T", 1, + "type", ObjTypeObject, + new_objtree_for_type(procForm->proargtypes.values[i], -1)); + params = lappend(params, new_object_object(tmp_obj)); + } + + sign = new_objtree_VA("%{identity}D (%{arguments:, }s)", 2, + "identity", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, objectId), + "arguments", ObjTypeArray, params); + + append_object_object(ret, "%{signature}s", sign); + + foreach(cell, node->actions) + { + DefElem *defel = (DefElem *) lfirst(cell); + ObjTree *tmp_obj = NULL; + + if (strcmp(defel->defname, "volatility") == 0) + tmp_obj = new_objtree(strVal(defel->arg)); + else if (strcmp(defel->defname, "strict") == 0) + tmp_obj = new_objtree(boolVal(defel->arg) ? + "RETURNS NULL ON NULL INPUT" : + "CALLED ON NULL INPUT"); + else if (strcmp(defel->defname, "security") == 0) + tmp_obj = new_objtree(boolVal(defel->arg) ? + "SECURITY DEFINER" : "SECURITY INVOKER"); + else if (strcmp(defel->defname, "leakproof") == 0) + tmp_obj = new_objtree(boolVal(defel->arg) ? + "LEAKPROOF" : "NOT LEAKPROOF"); + else if (strcmp(defel->defname, "cost") == 0) + tmp_obj = new_objtree_VA("COST %{cost}n", 1, + "cost", ObjTypeFloat, + defGetNumeric(defel)); + else if (strcmp(defel->defname, "rows") == 0) + { + tmp_obj = new_objtree("ROWS"); + if (defGetNumeric(defel) == 0) + append_not_present(tmp_obj); + else + append_float_object(tmp_obj, "%{rows}n", + defGetNumeric(defel)); + } + else if (strcmp(defel->defname, "set") == 0) + { + VariableSetStmt *sstmt = (VariableSetStmt *) defel->arg; + char *value = ExtractSetVariableArgs(sstmt); + + tmp_obj = deparse_FunctionSet(sstmt->kind, sstmt->name, value); + } + else if (strcmp(defel->defname, "support") == 0) + { + Oid argtypes[1]; + + tmp_obj = new_objtree("SUPPORT"); + + Assert(procForm->prosupport); + + /* + * We should qualify the support function's name if it wouldn't be + * resolved by lookup in the current search path. + */ + argtypes[0] = INTERNALOID; + append_string_object(tmp_obj, "%{name}s", "name", + generate_function_name(procForm->prosupport, 1, + NIL, argtypes, + false, NULL, + EXPR_KIND_NONE)); + } + else if (strcmp(defel->defname, "parallel") == 0) + tmp_obj = new_objtree_VA("PARALLEL %{value}s", 1, + "value", ObjTypeString, strVal(defel->arg)); + + elems = lappend(elems, new_object_object(tmp_obj)); + } + + append_array_object(ret, "%{definition: }s", elems); + + ReleaseSysCache(procTup); + + return ret; +} + /* * Deparse an AlterObjectSchemaStmt (ALTER ... SET SCHEMA command) * @@ -3477,22 +3993,499 @@ deparse_AlterObjectSchemaStmt(ObjectAddress address, Node *parsetree, } /* - * Deparse an AlterOwnerStmt (ALTER ... OWNER TO ...). - * - * Given the object address and the parse tree that created it, return an - * ObjTree representing the alter command. + * Deparse a GRANT/REVOKE command. * * Verbose syntax - * ALTER %{objtype}s %{identity}s OWNER TO %{newowner}I + * GRANT %{privileges:, }s ON" %{objtype}s %{privtarget:, }s TO %{grantees:, }s + * %{grant_option}s GRANTED BY %{rolename}s + * OR + * REVOKE %{privileges:, }s ON" %{objtype}s %{privtarget:, }s + * FROM %{grantees:, }s %{grant_option}s %{cascade}s */ static ObjTree * -deparse_AlterOwnerStmt(ObjectAddress address, Node *parsetree) +deparse_GrantStmt(CollectedCommand *cmd) { - AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree; + InternalGrant *istmt; + ObjTree *ret; + List *list = NIL; + ListCell *cell; + Oid classId; + ObjTree *tmp; - return new_objtree_VA("ALTER %{objtype}s %{identity}s OWNER TO %{newowner}I", 3, - "objtype", ObjTypeString, - stringify_objtype(node->objectType, false), + /* Don't deparse SQL commands generated while creating extension */ + if (cmd->in_extension) + return NULL; + + istmt = cmd->d.grant.istmt; + + /* + * If there are no objects from "ALL ... IN SCHEMA" to be granted, then + * nothing to do. + */ + if (istmt->objects == NIL) + return NULL; + + switch (istmt->objtype) + { + case OBJECT_COLUMN: + case OBJECT_TABLE: + case OBJECT_SEQUENCE: + classId = RelationRelationId; + break; + case OBJECT_DOMAIN: + case OBJECT_TYPE: + classId = TypeRelationId; + break; + case OBJECT_FDW: + classId = ForeignDataWrapperRelationId; + break; + case OBJECT_FOREIGN_SERVER: + classId = ForeignServerRelationId; + break; + case OBJECT_FUNCTION: + case OBJECT_PROCEDURE: + case OBJECT_ROUTINE: + classId = ProcedureRelationId; + break; + case OBJECT_LANGUAGE: + classId = LanguageRelationId; + break; + case OBJECT_LARGEOBJECT: + classId = LargeObjectRelationId; + break; + case OBJECT_SCHEMA: + classId = NamespaceRelationId; + break; + case OBJECT_DATABASE: + case OBJECT_TABLESPACE: + classId = InvalidOid; + elog(ERROR, "global objects not supported"); + break; + default: + elog(ERROR, "invalid OBJECT value %d", istmt->objtype); + } + + /* GRANT TO or REVOKE FROM */ + ret = new_objtree(istmt->is_grant ? "GRANT" : "REVOKE"); + + /* Build a list of privileges to grant/revoke */ + if (istmt->all_privs) + { + tmp = new_objtree("ALL PRIVILEGES"); + list = list_make1(new_object_object(tmp)); + } + else + { + char *priv; + if (istmt->privileges & ACL_INSERT) + { + priv = (char *)privilege_to_string(ACL_INSERT); + list = lappend(list, new_string_object(priv)); + } + if (istmt->privileges & ACL_SELECT) + { + priv = (char *)privilege_to_string(ACL_SELECT); + list = lappend(list, new_string_object(priv)); + } + if (istmt->privileges & ACL_UPDATE) + { + priv = (char *)privilege_to_string(ACL_UPDATE); + list = lappend(list, new_string_object(priv)); + } + if (istmt->privileges & ACL_DELETE) + { + priv = (char *)privilege_to_string(ACL_DELETE); + list = lappend(list, new_string_object(priv)); + } + if (istmt->privileges & ACL_TRUNCATE) + { + priv = (char *)privilege_to_string(ACL_TRUNCATE); + list = lappend(list, new_string_object(priv)); + } + if (istmt->privileges & ACL_REFERENCES) + { + priv = (char *)privilege_to_string(ACL_REFERENCES); + list = lappend(list, new_string_object(priv)); + } + if (istmt->privileges & ACL_TRIGGER) + { + priv = (char *)privilege_to_string(ACL_TRIGGER); + list = lappend(list, new_string_object(priv)); + } + if (istmt->privileges & ACL_EXECUTE) + { + priv = (char *)privilege_to_string(ACL_EXECUTE); + list = lappend(list, new_string_object(priv)); + } + if (istmt->privileges & ACL_USAGE) + { + priv = (char *)privilege_to_string(ACL_USAGE); + list = lappend(list, new_string_object(priv)); + } + if (istmt->privileges & ACL_CREATE) + { + priv = (char *)privilege_to_string(ACL_CREATE); + list = lappend(list, new_string_object(priv)); + } + if (istmt->privileges & ACL_CREATE_TEMP) + { + priv = (char *)privilege_to_string(ACL_CREATE_TEMP); + list = lappend(list, new_string_object(priv)); + } + if (istmt->privileges & ACL_CONNECT) + { + priv = (char *)privilege_to_string(ACL_CONNECT); + list = lappend(list, new_string_object(priv)); + } + if (istmt->privileges & ACL_MAINTAIN) + { + priv = (char *)privilege_to_string(ACL_MAINTAIN); + list = lappend(list, new_string_object(priv)); + } + + if (!istmt->is_grant && istmt->grant_option) + append_string_object(ret, "%{grant_option}s", "grant_option", + istmt->grant_option ? "GRANT OPTION FOR" : ""); + + if (istmt->col_privs != NIL) + { + ListCell *ocell; + + foreach(ocell, istmt->col_privs) + { + AccessPriv *priv = lfirst(ocell); + List *cols = NIL; + + foreach(cell, priv->cols) + { + String *colname = lfirst_node(String, cell); + + cols = lappend(cols, + new_string_object(strVal(colname))); + } + + tmp = new_objtree_VA("(%{cols:, }s) %{priv}s", 2, + "cols", ObjTypeArray, cols, + "priv", ObjTypeString, + priv->priv_name ? priv->priv_name : "ALL PRIVILEGES"); + + list = lappend(list, new_object_object(tmp)); + } + } + } + append_array_object(ret, "%{privileges:, }s ON", list); + + append_string_object(ret, "%{objtype}s", "objtype", + (char *)stringify_objtype(istmt->objtype, true)); + + /* Target objects. We use object identities here */ + list = NIL; + foreach(cell, istmt->objects) + { + Oid objid = lfirst_oid(cell); + ObjectAddress addr; + + addr.classId = classId; + addr.objectId = objid; + addr.objectSubId = 0; + + tmp = new_objtree_VA("%{identity}s", 1, + "identity", ObjTypeString, + getObjectIdentity(&addr, false)); + + list = lappend(list, new_object_object(tmp)); + } + append_array_object(ret, "%{privtarget:, }s", list); + + append_format_string(ret, istmt->is_grant ? "TO" : "FROM"); + + /* List of grantees */ + list = NIL; + foreach(cell, istmt->grantees) + { + Oid grantee = lfirst_oid(cell); + + tmp = new_objtree_for_role_id(grantee); + list = lappend(list, new_object_object(tmp)); + } + + append_array_object(ret, "%{grantees:, }s", list); + + /* The wording of the grant option is variable ... */ + if (istmt->is_grant) + append_string_object(ret, "%{grant_option}s", "grant_option", + istmt->grant_option ? "WITH GRANT OPTION" : ""); + + if (istmt->grantor_uid) + { + HeapTuple roltup; + char *rolename; + + roltup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(istmt->grantor_uid)); + if (!HeapTupleIsValid(roltup)) + elog(ERROR, "cache lookup failed for role with OID %u", + istmt->grantor_uid); + + rolename = NameStr(((Form_pg_authid) GETSTRUCT(roltup))->rolname); + append_string_object(ret, "GRANTED BY %{rolename}s", + "rolename", rolename); + ReleaseSysCache(roltup); + } + + if (!istmt->is_grant) + append_string_object(ret, "%{cascade}s", "cascade", + istmt->behavior == DROP_CASCADE ? "CASCADE" : ""); + + return ret; +} + +/* + * Deparse an AlterOperatorStmt (ALTER OPERATOR ... SET ...). + * + * Given an operator OID and the parse tree that created it, return an ObjTree + * representing the alter command. + * + * Verbose syntax + * ALTER OPERATOR %{identity}O (%{left_type}T, %{right_type}T) + * SET (%{elems:, }s) + */ +static ObjTree * +deparse_AlterOperatorStmt(Oid objectId, Node *parsetree) +{ + HeapTuple oprTup; + AlterOperatorStmt *node = (AlterOperatorStmt *) parsetree; + ObjTree *ret; + Form_pg_operator oprForm; + ListCell *cell; + List *list = NIL; + + oprTup = SearchSysCache1(OPEROID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(oprTup)) + elog(ERROR, "cache lookup failed for operator with OID %u", objectId); + oprForm = (Form_pg_operator) GETSTRUCT(oprTup); + + ret = new_objtree_VA("ALTER OPERATOR %{identity}O", 1, + "identity", ObjTypeObject, + new_objtree_for_qualname(oprForm->oprnamespace, + NameStr(oprForm->oprname))); + + /* LEFTARG */ + if (OidIsValid(oprForm->oprleft)) + append_object_object(ret, "(%{left_type}T", + new_objtree_for_type(oprForm->oprleft, -1)); + else + append_string_object(ret, "(%{left_type}s", "left_type", "NONE"); + + /* RIGHTARG */ + Assert(OidIsValid(oprForm->oprleft)); + append_object_object(ret, ", %{right_type}T)", + new_objtree_for_type(oprForm->oprright, -1)); + + foreach(cell, node->options) + { + DefElem *elem = (DefElem *) lfirst(cell); + ObjTree *tmp_obj = NULL; + + if (strcmp(elem->defname, "restrict") == 0) + { + tmp_obj = new_objtree_VA("RESTRICT=", 1, + "clause", ObjTypeString, "restrict"); + if (OidIsValid(oprForm->oprrest)) + append_object_object(tmp_obj, "%{procedure}D", + new_objtree_for_qualname_id(ProcedureRelationId, + oprForm->oprrest)); + else + append_string_object(tmp_obj, "%{procedure}s", "procedure", + "NONE"); + } + else if (strcmp(elem->defname, "join") == 0) + { + tmp_obj = new_objtree_VA("JOIN=", 1, + "clause", ObjTypeString, "join"); + if (OidIsValid(oprForm->oprjoin)) + append_object_object(tmp_obj, "%{procedure}D", + new_objtree_for_qualname_id(ProcedureRelationId, + oprForm->oprjoin)); + else + append_string_object(tmp_obj, "%{procedure}s", "procedure", + "NONE"); + } + + Assert(tmp_obj); + list = lappend(list, new_object_object(tmp_obj)); + } + + append_array_object(ret, "SET (%{elems:, }s)", list); + + ReleaseSysCache(oprTup); + + return ret; +} + +/* + * Deparse an ALTER OPERATOR FAMILY ADD/DROP command. + * + * Given the CollectedCommand, return an ObjTree representing the Alter + * Operator command. + * + * Verbose syntax + * ALTER OPERATOR FAMILY %{identity}D USING %{amname}I ADD/DROP %{items:,}s + */ +static ObjTree * +deparse_AlterOpFamily(CollectedCommand *cmd) +{ + ObjTree *ret; + AlterOpFamilyStmt *stmt = (AlterOpFamilyStmt *) cmd->parsetree; + HeapTuple ftp; + Form_pg_opfamily opfForm; + List *list = NIL; + ListCell *cell; + + /* Don't deparse SQL commands generated while creating extension */ + if (cmd->in_extension) + return NULL; + + ftp = SearchSysCache1(OPFAMILYOID, + ObjectIdGetDatum(cmd->d.opfam.address.objectId)); + if (!HeapTupleIsValid(ftp)) + elog(ERROR, "cache lookup failed for operator family with OID %u", + cmd->d.opfam.address.objectId); + opfForm = (Form_pg_opfamily) GETSTRUCT(ftp); + + ret = new_objtree_VA("ALTER OPERATOR FAMILY %{identity}D USING %{amname}I", 2, + "identity", ObjTypeObject, + new_objtree_for_qualname(opfForm->opfnamespace, + NameStr(opfForm->opfname)), + "amname", ObjTypeString, stmt->amname); + + foreach(cell, cmd->d.opfam.operators) + { + OpFamilyMember *oper = lfirst(cell); + ObjTree *tmp_obj; + + /* + * Verbose syntax + * + * OPERATOR %{num}n %{operator}O(%{ltype}T, %{rtype}T) %{purpose}s + */ + tmp_obj = new_objtree_VA("OPERATOR %{num}n", 1, + "num", ObjTypeInteger, oper->number); + + /* Add the operator name; the DROP case doesn't have this */ + if (!stmt->isDrop) + append_object_object(tmp_obj, "%{operator}O", + new_objtree_for_qualname_id(OperatorRelationId, + oper->object)); + + /* Add the types */ + append_object_object(tmp_obj, "(%{ltype}T,", + new_objtree_for_type(oper->lefttype, -1)); + append_object_object(tmp_obj, "%{rtype}T)", + new_objtree_for_type(oper->righttype, -1)); + + /* Add the FOR SEARCH / FOR ORDER BY clause; not in the DROP case */ + if (!stmt->isDrop) + { + if (oper->sortfamily == InvalidOid) + append_string_object(tmp_obj, "%{purpose}s", "purpose", + "FOR SEARCH"); + else + { + ObjTree *orderby_obj; + + orderby_obj = new_objtree_VA("FOR ORDER BY %{opfamily}D", 1, + "opfamily", ObjTypeObject, + new_objtree_for_qualname_id(OperatorFamilyRelationId, + oper->sortfamily)); + append_object_object(tmp_obj, "%{purpose}s", orderby_obj); + } + } + + list = lappend(list, new_object_object(tmp_obj)); + } + + foreach(cell, cmd->d.opfam.procedures) + { + OpFamilyMember *proc = lfirst(cell); + ObjTree *tmp_obj; + + tmp_obj = new_objtree_VA("FUNCTION %{num}n (%{ltype}T, %{rtype}T)", 3, + "num", ObjTypeInteger, proc->number, + "ltype", ObjTypeObject, + new_objtree_for_type(proc->lefttype, -1), + "rtype", ObjTypeObject, + new_objtree_for_type(proc->righttype, -1)); + + /* + * Add the function name and arg types; the DROP case doesn't have + * this + */ + if (!stmt->isDrop) + { + HeapTuple procTup; + Form_pg_proc procForm; + Oid *proargtypes; + List *arglist = NIL; + int i; + + procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(proc->object)); + if (!HeapTupleIsValid(procTup)) + elog(ERROR, "cache lookup failed for procedure with OID %u", + proc->object); + procForm = (Form_pg_proc) GETSTRUCT(procTup); + + proargtypes = procForm->proargtypes.values; + for (i = 0; i < procForm->pronargs; i++) + { + ObjTree *arg; + + arg = new_objtree_for_type(proargtypes[i], -1); + arglist = lappend(arglist, new_object_object(arg)); + } + + append_object_object(tmp_obj, "%{function}D", + new_objtree_for_qualname(procForm->pronamespace, + NameStr(procForm->proname))); + + append_format_string(tmp_obj, "("); + append_array_object(tmp_obj, "%{argtypes:, }T", arglist); + append_format_string(tmp_obj, ")"); + + ReleaseSysCache(procTup); + } + + list = lappend(list, new_object_object(tmp_obj)); + } + + if (stmt->isDrop) + append_format_string(ret, "DROP"); + else + append_format_string(ret, "ADD"); + + append_array_object(ret, "%{items:, }s", list); + + ReleaseSysCache(ftp); + + return ret; +} + +/* + * Deparse an AlterOwnerStmt (ALTER ... OWNER TO ...). + * + * Given the object address and the parse tree that created it, return an + * ObjTree representing the alter command. + * + * Verbose syntax + * ALTER %{objtype}s %{identity}s OWNER TO %{newowner}I + */ +static ObjTree * +deparse_AlterOwnerStmt(ObjectAddress address, Node *parsetree) +{ + AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree; + + return new_objtree_VA("ALTER %{objtype}s %{identity}s OWNER TO %{newowner}I", 3, + "objtype", ObjTypeString, + stringify_objtype(node->objectType, false), "identity", ObjTypeString, getObjectIdentity(&address, false), "newowner", ObjTypeString, @@ -3500,148 +4493,5385 @@ deparse_AlterOwnerStmt(ObjectAddress address, Node *parsetree) } /* - * Deparse a RenameStmt. + * Deparse an AlterSeqStmt. + * + * Given a sequence OID and a parse tree that modified it, return an ObjTree + * representing the alter command. + * + * Verbose syntax + * ALTER SEQUENCE %{identity}D %{definition: }s + */ +static ObjTree * +deparse_AlterSeqStmt(Oid objectId, Node *parsetree) +{ + ObjTree *ret; + Relation relation; + List *elems = NIL; + ListCell *cell; + Form_pg_sequence seqform; + Sequence_values *seqvalues; + AlterSeqStmt *alterSeqStmt = (AlterSeqStmt *) parsetree; + + /* + * Sequence for IDENTITY COLUMN output separately (via CREATE TABLE or + * ALTER TABLE); return empty here. + */ + if (alterSeqStmt->for_identity) + return NULL; + + seqvalues = get_sequence_values(objectId); + seqform = seqvalues->seqform; + + foreach(cell, ((AlterSeqStmt *) parsetree)->options) + { + DefElem *elem = (DefElem *) lfirst(cell); + ObjElem *newelm; + + if (strcmp(elem->defname, "cache") == 0) + newelm = deparse_Seq_Cache(seqform, false); + else if (strcmp(elem->defname, "cycle") == 0) + newelm = deparse_Seq_Cycle(seqform, false); + else if (strcmp(elem->defname, "increment") == 0) + newelm = deparse_Seq_IncrementBy(seqform, false); + else if (strcmp(elem->defname, "minvalue") == 0) + newelm = deparse_Seq_Minvalue(seqform, false); + else if (strcmp(elem->defname, "maxvalue") == 0) + newelm = deparse_Seq_Maxvalue(seqform, false); + else if (strcmp(elem->defname, "start") == 0) + newelm = deparse_Seq_Startwith(seqform, false); + else if (strcmp(elem->defname, "restart") == 0) + newelm = deparse_Seq_Restart(seqvalues->last_value); + else if (strcmp(elem->defname, "owned_by") == 0) + newelm = deparse_Seq_OwnedBy(objectId, false); + else if (strcmp(elem->defname, "as") == 0) + newelm = deparse_Seq_As(seqform); + else + elog(ERROR, "invalid sequence option %s", elem->defname); + + elems = lappend(elems, newelm); + } + + relation = relation_open(objectId, AccessShareLock); + + ret = new_objtree_VA("ALTER SEQUENCE %{identity}D %{definition: }s", 2, + "identity", ObjTypeObject, + new_objtree_for_qualname(relation->rd_rel->relnamespace, + RelationGetRelationName(relation)), + "definition", ObjTypeArray, elems); + + relation_close(relation, AccessShareLock); + + return ret; +} + +/* + * Deparse an AlterTypeStmt. + * + * Given a type OID and a parse tree that modified it, return an ObjTree + * representing the alter type. + * + * Verbose syntax + * ALTER TYPE %{identity}D SET (%{definition: }s) + */ +static ObjTree * +deparse_AlterTypeSetStmt(Oid objectId, Node *cmd) +{ + AlterTypeStmt *alterTypeSetStmt = (AlterTypeStmt *) cmd; + ListCell *pl; + List *elems = NIL; + HeapTuple typTup; + Form_pg_type typForm; + + typTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(typTup)) + elog(ERROR, "cache lookup failed for type with OID %u", objectId); + typForm = (Form_pg_type) GETSTRUCT(typTup); + + foreach(pl, alterTypeSetStmt->options) + { + DefElem *defel = (DefElem *) lfirst(pl); + ObjElem *newelm; + + if (strcmp(defel->defname, "storage") == 0) + newelm = deparse_Type_Storage(typForm); + else if (strcmp(defel->defname, "receive") == 0) + newelm = deparse_Type_Receive(typForm); + else if (strcmp(defel->defname, "send") == 0) + newelm = deparse_Type_Send(typForm); + else if (strcmp(defel->defname, "typmod_in") == 0) + newelm = deparse_Type_Typmod_In(typForm); + else if (strcmp(defel->defname, "typmod_out") == 0) + newelm = deparse_Type_Typmod_Out(typForm); + else if (strcmp(defel->defname, "analyze") == 0) + newelm = deparse_Type_Analyze(typForm); + else if (strcmp(defel->defname, "subscript") == 0) + newelm = deparse_Type_Subscript(typForm); + else + elog(ERROR, "invalid type option %s", defel->defname); + + elems = lappend(elems, newelm); + } + + ReleaseSysCache(typTup); + + return new_objtree_VA("ALTER TYPE %{identity}D SET (%{definition:, }s)", 2, + "identity", ObjTypeObject, + new_objtree_for_qualname_id(TypeRelationId, + objectId), + "definition", ObjTypeArray, elems); +} + +/* + * Deparse a CompositeTypeStmt (CREATE TYPE AS) + * + * Given a Composite type OID and the parse tree that created it, return an + * ObjTree representing the creation command. + * + * Verbose syntax + * CREATE TYPE %{identity}D AS (%{columns:, }s) + */ +static ObjTree * +deparse_CompositeTypeStmt(Oid objectId, Node *parsetree) +{ + CompositeTypeStmt *node = (CompositeTypeStmt *) parsetree; + HeapTuple typtup; + Form_pg_type typform; + Relation typerel; + List *dpcontext; + List *tableelts = NIL; + + /* Find the pg_type entry and open the corresponding relation */ + typtup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(typtup)) + elog(ERROR, "cache lookup failed for type with OID %u", objectId); + typform = (Form_pg_type) GETSTRUCT(typtup); + typerel = relation_open(typform->typrelid, AccessShareLock); + + dpcontext = deparse_context_for(RelationGetRelationName(typerel), + RelationGetRelid(typerel)); + + tableelts = deparse_TableElements(typerel, node->coldeflist, dpcontext, + false, /* not typed */ + true); /* composite type */ + + table_close(typerel, AccessShareLock); + ReleaseSysCache(typtup); + + return new_objtree_VA("CREATE TYPE %{identity}D AS (%{columns:, }s)", 2, + "identity", ObjTypeObject, + new_objtree_for_qualname_id(TypeRelationId, objectId), + "columns", ObjTypeArray, tableelts); +} + +/* + * Deparse a CreateConversionStmt + * + * Given a conversion OID and the parse tree that created it, return an ObjTree + * representing the creation command. + * + * Verbose syntax + * CREATE %{default}s CONVERSION %{identity}D FOR %{source}L TO %{dest}L + * FROM %{function}D + */ +static ObjTree * +deparse_CreateConversion(Oid objectId, Node *parsetree) +{ + HeapTuple conTup; + Relation convrel; + Form_pg_conversion conForm; + ObjTree *ret; + + convrel = table_open(ConversionRelationId, AccessShareLock); + conTup = get_catalog_object_by_oid(convrel, Anum_pg_conversion_oid, objectId); + if (!HeapTupleIsValid(conTup)) + elog(ERROR, "cache lookup failed for conversion with OID %u", objectId); + conForm = (Form_pg_conversion) GETSTRUCT(conTup); + + ret = new_objtree_VA("CREATE %{default}s CONVERSION %{identity}D FOR %{source}L TO %{dest}L FROM %{function}D", 5, + "default", ObjTypeString, + conForm->condefault ? "DEFAULT" : "", + "identity", ObjTypeObject, + new_objtree_for_qualname(conForm->connamespace, + NameStr(conForm->conname)), + "source", ObjTypeString, + (char *)pg_encoding_to_char(conForm->conforencoding), + "dest", ObjTypeString, + (char *)pg_encoding_to_char(conForm->contoencoding), + "function", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, + conForm->conproc)); + + table_close(convrel, AccessShareLock); + + return ret; +} + +/* + * Deparse a CreateEnumStmt (CREATE TYPE AS ENUM) + * + * Given a Enum type OID and the parse tree that created it, return an ObjTree + * representing the creation command. + * + * Verbose syntax + * CREATE TYPE %{identity}D AS ENUM (%{values:, }L) + */ +static ObjTree * +deparse_CreateEnumStmt(Oid objectId, Node *parsetree) +{ + CreateEnumStmt *node = (CreateEnumStmt *) parsetree; + List *values = NIL; + ListCell *cell; + + foreach(cell, node->vals) + values = lappend(values, new_string_object(strVal(lfirst_node(String, cell)))); + + return new_objtree_VA("CREATE TYPE %{identity}D AS ENUM (%{values:, }L)", 2, + "identity", ObjTypeObject, + new_objtree_for_qualname_id(TypeRelationId, objectId), + "values", ObjTypeArray, values); +} + +/* + * Deparse a CreateExtensionStmt + * + * Given an extension OID and the parse tree that created it, return an ObjTree + * representing the creation command. + * + * Verbose syntax + * CREATE EXTENSION %{if_not_exists}s %{name}I %{options: }s + */ +static ObjTree * +deparse_CreateExtensionStmt(Oid objectId, Node *parsetree) +{ + CreateExtensionStmt *node = (CreateExtensionStmt *) parsetree; + Relation pg_extension; + HeapTuple extTup; + Form_pg_extension extForm; + ObjTree *tmp; + List *list = NIL; + ListCell *cell; + + pg_extension = table_open(ExtensionRelationId, AccessShareLock); + extTup = get_catalog_object_by_oid(pg_extension, Anum_pg_extension_oid, objectId); + if (!HeapTupleIsValid(extTup)) + elog(ERROR, "cache lookup failed for extension with OID %u", objectId); + extForm = (Form_pg_extension) GETSTRUCT(extTup); + + /* List of options */ + foreach(cell, node->options) + { + DefElem *opt = (DefElem *) lfirst(cell); + + if (strcmp(opt->defname, "schema") == 0) + { + /* Skip this one; we add one unconditionally below */ + continue; + } + else if (strcmp(opt->defname, "new_version") == 0) + { + tmp = new_objtree_VA("VERSION %{version}L", 2, + "type", ObjTypeString, "version", + "version", ObjTypeString, defGetString(opt)); + list = lappend(list, new_object_object(tmp)); + } + else if (strcmp(opt->defname, "old_version") == 0) + { + tmp = new_objtree_VA("FROM %{version}L", 2, + "type", ObjTypeString, "from", + "version", ObjTypeString, defGetString(opt)); + list = lappend(list, new_object_object(tmp)); + } + else + elog(ERROR, "unsupported option %s", opt->defname); + } + + /* Add the SCHEMA option */ + tmp = new_objtree_VA("SCHEMA %{schema}I", 2, + "type", ObjTypeString, "schema", + "schema", ObjTypeString, + get_namespace_name(extForm->extnamespace)); + list = lappend(list, new_object_object(tmp)); + table_close(pg_extension, AccessShareLock); + + return new_objtree_VA("CREATE EXTENSION %{if_not_exists}s %{name}I %{options: }s", 3, + "if_not_exists", ObjTypeString, + node->if_not_exists ? "IF NOT EXISTS" : "", + "name", ObjTypeString, node->extname, + "options", ObjTypeArray, list); +} + +/* + * If a column name is specified, add an element for it; otherwise it's a + * table-level option. + */ +static ObjTree * +deparse_FdwOptions(List *options, char *colname, bool altercoloptions) +{ + ObjTree *ret; + + if (colname) + ret = new_objtree_VA("ALTER COLUMN %{column}I %{options}s", 2, + "column", ObjTypeString, colname, + "options", ObjTypeString, + altercoloptions ? "OPTIONS" : ""); + else + ret = new_objtree("OPTIONS"); + + if (options != NIL) + { + List *optout = NIL; + ListCell *cell; + + foreach(cell, options) + { + DefElem *elem; + ObjTree *opt; + + elem = (DefElem *) lfirst(cell); + + switch (elem->defaction) + { + case DEFELEM_UNSPEC: + opt = new_objtree_VA("%{label}I %{value}L", 2, + "label", ObjTypeString, elem->defname, + "value", ObjTypeString, + elem->arg ? defGetString(elem) : + defGetBoolean(elem) ? "TRUE" : "FALSE"); + break; + case DEFELEM_SET: + opt = new_objtree_VA("SET %{label}I %{value}L", 2, + "label", ObjTypeString, elem->defname, + "value", ObjTypeString, + elem->arg ? defGetString(elem) : + defGetBoolean(elem) ? "TRUE" : "FALSE"); + break; + case DEFELEM_ADD: + opt = new_objtree_VA("ADD %{label}I %{value}L", 2, + "label", ObjTypeString, elem->defname, + "value", ObjTypeString, + elem->arg ? defGetString(elem) : + defGetBoolean(elem) ? "TRUE" : "FALSE"); + break; + case DEFELEM_DROP: + opt = new_objtree_VA("DROP %{label}I", 1, + "label", ObjTypeString, elem->defname); + break; + default: + elog(ERROR, "invalid def action %d", elem->defaction); + opt = NULL; + } + + optout = lappend(optout, new_object_object(opt)); + } + + append_array_object(ret, "(%{option: ,}s)", optout); + } + else + append_not_present(ret); + + return ret; +} + +/* + * Deparse a CreateFdwStmt (CREATE FOREIGN DATA WRAPPER) + * + * Given an FDW OID and the parse tree that created it, return an ObjTree + * representing the creation command. + * + * Verbose syntax + * CREATE FOREIGN DATA WRAPPER %{identity}I %{handler}s %{validator}s %{generic_options}s + */ +static ObjTree * +deparse_CreateFdwStmt(Oid objectId, Node *parsetree) +{ + CreateFdwStmt *node = (CreateFdwStmt *) parsetree; + HeapTuple fdwTup; + Form_pg_foreign_data_wrapper fdwForm; + Relation rel; + ObjTree *ret; + ObjTree *tmp; + + rel = table_open(ForeignDataWrapperRelationId, RowExclusiveLock); + + fdwTup = SearchSysCache1(FOREIGNDATAWRAPPEROID, + ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(fdwTup)) + elog(ERROR, "cache lookup failed for foreign-data wrapper with OID %u", + objectId); + + fdwForm = (Form_pg_foreign_data_wrapper) GETSTRUCT(fdwTup); + + ret = new_objtree_VA("CREATE FOREIGN DATA WRAPPER %{identity}I", 1, + "identity", ObjTypeString, NameStr(fdwForm->fdwname)); + + /* Add HANDLER clause */ + if (!OidIsValid(fdwForm->fdwhandler)) + tmp = new_objtree("NO HANDLER"); + else + { + tmp = new_objtree_VA("HANDLER %{procedure}D", 1, + "procedure", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, + fdwForm->fdwhandler)); + } + append_object_object(ret, "%{handler}s", tmp); + + /* Add VALIDATOR clause */ + if (!OidIsValid(fdwForm->fdwvalidator)) + tmp = new_objtree("NO VALIDATOR"); + else + { + tmp = new_objtree_VA("VALIDATOR %{procedure}D", 1, + "procedure", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, + fdwForm->fdwvalidator)); + } + append_object_object(ret, "%{validator}s", tmp); + + /* Add an OPTIONS clause, if any */ + append_object_object(ret, "%{generic_options}s", + deparse_FdwOptions(node->options, NULL, false)); + + ReleaseSysCache(fdwTup); + table_close(rel, RowExclusiveLock); + + return ret; +} + +/* + * Deparse an AlterFdwStmt (ALTER FOREIGN DATA WRAPPER) + * + * Given an FDW OID and the parse tree that created it, return an ObjTree + * representing the alter command. + * + * Verbose syntax + * ALTER FOREIGN DATA WRAPPER %{identity}I %{fdw_options: }s %{generic_options}D + */ +static ObjTree * +deparse_AlterFdwStmt(Oid objectId, Node *parsetree) +{ + AlterFdwStmt *node = (AlterFdwStmt *) parsetree; + HeapTuple fdwTup; + Form_pg_foreign_data_wrapper fdwForm; + Relation rel; + ObjTree *ret; + List *fdw_options = NIL; + ListCell *cell; + + rel = table_open(ForeignDataWrapperRelationId, RowExclusiveLock); + + fdwTup = SearchSysCache1(FOREIGNDATAWRAPPEROID, + ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(fdwTup)) + elog(ERROR, "cache lookup failed for foreign-data wrapper with OID %u", + objectId); + + fdwForm = (Form_pg_foreign_data_wrapper) GETSTRUCT(fdwTup); + + ret = new_objtree_VA("ALTER FOREIGN DATA WRAPPER %{identity}I", 1, + "identity", ObjTypeString, NameStr(fdwForm->fdwname)); + + /* + * Iterate through options, to see what changed, but use catalog as a + * basis for new values. + */ + foreach(cell, node->func_options) + { + DefElem *elem; + ObjTree *tmp; + + elem = lfirst(cell); + + if (pg_strcasecmp(elem->defname, "handler") == 0) + { + /* add HANDLER clause */ + if (!OidIsValid(fdwForm->fdwhandler)) + tmp = new_objtree("NO HANDLER"); + else + { + tmp = new_objtree_VA("HANDLER %{procedure}D", 1, + "procedure", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, + fdwForm->fdwhandler)); + } + fdw_options = lappend(fdw_options, new_object_object(tmp)); + } + else if (pg_strcasecmp(elem->defname, "validator") == 0) + { + /* add VALIDATOR clause */ + if (!OidIsValid(fdwForm->fdwvalidator)) + tmp = new_objtree("NO VALIDATOR"); + else + { + tmp = new_objtree_VA("VALIDATOR %{procedure}D", 1, + "procedure", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, + fdwForm->fdwvalidator)); + } + fdw_options = lappend(fdw_options, new_object_object(tmp)); + } + } + + /* Add HANDLER/VALIDATOR if specified */ + append_array_object(ret, "%{fdw_options: }s", fdw_options); + + /* Add an OPTIONS clause, if any */ + append_object_object(ret, "%{generic_options}D", + deparse_FdwOptions(node->options, NULL, false)); + + ReleaseSysCache(fdwTup); + table_close(rel, RowExclusiveLock); + + return ret; +} + +/* + * Deparse a CREATE TYPE AS RANGE statement + * + * Given a Range type OID and the parse tree that created it, return an ObjTree + * representing the creation command. + * + * Verbose syntax + * CREATE TYPE %{identity}D AS RANGE (%{definition:, }s) + */ +static ObjTree * +deparse_CreateRangeStmt(Oid objectId, Node *parsetree) +{ + ObjTree *tmp; + List *definition = NIL; + Relation pg_range; + HeapTuple rangeTup; + Form_pg_range rangeForm; + ScanKeyData key[1]; + SysScanDesc scan; + + pg_range = table_open(RangeRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + Anum_pg_range_rngtypid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(objectId)); + + scan = systable_beginscan(pg_range, RangeTypidIndexId, true, + NULL, 1, key); + + rangeTup = systable_getnext(scan); + if (!HeapTupleIsValid(rangeTup)) + elog(ERROR, "cache lookup failed for range with type OID %u", + objectId); + + rangeForm = (Form_pg_range) GETSTRUCT(rangeTup); + + /* SUBTYPE */ + tmp = new_objtree_VA("SUBTYPE = %{type}D", 2, + "clause", ObjTypeString, "subtype", + "type", ObjTypeObject, + new_objtree_for_qualname_id(TypeRelationId, rangeForm->rngsubtype)); + definition = lappend(definition, new_object_object(tmp)); + + /* SUBTYPE_OPCLASS */ + if (OidIsValid(rangeForm->rngsubopc)) + { + tmp = new_objtree_VA("SUBTYPE_OPCLASS = %{opclass}D", 2, + "clause", ObjTypeString, "opclass", + "opclass", ObjTypeObject, + new_objtree_for_qualname_id(OperatorClassRelationId, + rangeForm->rngsubopc)); + definition = lappend(definition, new_object_object(tmp)); + } + + /* COLLATION */ + if (OidIsValid(rangeForm->rngcollation)) + { + tmp = new_objtree_VA("COLLATION = %{collation}D", 2, + "clause", ObjTypeString, "collation", + "collation", ObjTypeObject, + new_objtree_for_qualname_id(CollationRelationId, + rangeForm->rngcollation)); + definition = lappend(definition, new_object_object(tmp)); + } + + /* CANONICAL */ + if (OidIsValid(rangeForm->rngcanonical)) + { + tmp = new_objtree_VA("CANONICAL = %{canonical}D", 2, + "clause", ObjTypeString, "canonical", + "canonical", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, + rangeForm->rngcanonical)); + definition = lappend(definition, new_object_object(tmp)); + } + + /* SUBTYPE_DIFF */ + if (OidIsValid(rangeForm->rngsubdiff)) + { + tmp = new_objtree_VA("SUBTYPE_DIFF = %{subtype_diff}D", 2, + "clause", ObjTypeString, "subtype_diff", + "subtype_diff", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, + rangeForm->rngsubdiff)); + definition = lappend(definition, new_object_object(tmp)); + } + + systable_endscan(scan); + table_close(pg_range, RowExclusiveLock); + + return new_objtree_VA("CREATE TYPE %{identity}D AS RANGE (%{definition:, }s)", 2, + "identity", ObjTypeObject, + new_objtree_for_qualname_id(TypeRelationId, objectId), + "definition", ObjTypeArray, definition); +} + +/* + * Deparse an AlterEnumStmt. + * + * Given an enum OID and a parse tree that modified it, return an ObjTree + * representing the alter type. + * + * Verbose syntax + * ALTER TYPE %{identity}D ADD VALUE %{if_not_exists}s %{value}L + * %{after_or_before}s %{neighbor}L + * OR + * ALTER TYPE %{identity}D RENAME VALUE %{value}L TO %{newvalue}L + */ +static ObjTree * +deparse_AlterEnumStmt(Oid objectId, Node *parsetree) +{ + AlterEnumStmt *node = (AlterEnumStmt *) parsetree; + ObjTree *ret; + + ret = new_objtree_VA("ALTER TYPE %{identity}D", 1, + "identity", ObjTypeObject, + new_objtree_for_qualname_id(TypeRelationId, + objectId)); + + if (!node->oldVal) + { + append_format_string(ret, "ADD VALUE"); + append_string_object(ret, "%{if_not_exists}s", "if_not_exists", + node->skipIfNewValExists ? "IF NOT EXISTS" : ""); + + append_string_object(ret, "%{value}L", "value", node->newVal); + + if (node->newValNeighbor) + { + append_string_object(ret, "%{after_or_before}s", + "after_or_before", + node->newValIsAfter ? "AFTER" : "BEFORE"); + append_string_object(ret, "%{neighbor}L", "neighbor", + node->newValNeighbor); + } + } + else + { + append_string_object(ret, "RENAME VALUE %{value}L TO", "value", + node->oldVal); + append_string_object(ret, "%{newvalue}L", "newvalue", node->newVal); + } + + return ret; +} + +/* + * Deparse an AlterExtensionStmt (ALTER EXTENSION .. UPDATE TO VERSION) + * + * Given an extension OID and a parse tree that modified it, return an ObjTree + * representing the alter type. + * + * Verbose syntax + * ALTER EXTENSION %{identity}I UPDATE %{options: }s + */ +static ObjTree * +deparse_AlterExtensionStmt(Oid objectId, Node *parsetree) +{ + AlterExtensionStmt *node = (AlterExtensionStmt *) parsetree; + Relation pg_extension; + HeapTuple extTup; + Form_pg_extension extForm; + ObjTree *ret; + List *list = NIL; + ListCell *cell; + DefElem *d_new_version = NULL; + + pg_extension = table_open(ExtensionRelationId, AccessShareLock); + extTup = get_catalog_object_by_oid(pg_extension, Anum_pg_extension_oid, objectId); + if (!HeapTupleIsValid(extTup)) + elog(ERROR, "cache lookup failed for extension with OID %u", + objectId); + extForm = (Form_pg_extension) GETSTRUCT(extTup); + + foreach(cell, node->options) + { + DefElem *opt = (DefElem *) lfirst(cell); + + if (strcmp(opt->defname, "new_version") == 0) + { + ObjTree *tmp; + + if (d_new_version) + elog(ERROR, "conflicting or redundant options for extension with OID %u", + objectId); + + d_new_version = opt; + + tmp = new_objtree_VA("TO %{version}L", 2, + "type", ObjTypeString, "version", + "version", ObjTypeString, defGetString(opt)); + list = lappend(list, new_object_object(tmp)); + } + else + elog(ERROR, "unsupported option %s", opt->defname); + } + + ret = new_objtree_VA("ALTER EXTENSION %{identity}I UPDATE %{options: }s", 2, + "identity", ObjTypeString, NameStr(extForm->extname), + "options", ObjTypeArray, list); + + table_close(pg_extension, AccessShareLock); + + return ret; +} + +/* + * Deparse an AlterExtensionContentsStmt (ALTER EXTENSION ext ADD/DROP object) + * + * Verbose syntax + * ALTER EXTENSION %{extidentity}I %{extoption}s %{extobjtype}s + * ADD/DROP %{objidentity}s + */ +static ObjTree * +deparse_AlterExtensionContentsStmt(Oid objectId, Node *parsetree, + ObjectAddress objectAddress) +{ + AlterExtensionContentsStmt *node = (AlterExtensionContentsStmt *) parsetree; + + Assert(node->action == +1 || node->action == -1); + + return new_objtree_VA("ALTER EXTENSION %{extidentity}I %{extoption}s %{extobjtype}s %{objidentity}s", 4, + "extidentity", ObjTypeString, node->extname, + "extoption", ObjTypeString, + node->action == +1 ? "ADD" : "DROP", + "extobjtype", ObjTypeString, + stringify_objtype(node->objtype, false), + "objidentity", ObjTypeString, + getObjectIdentity(&objectAddress, false)); +} + +/* + * Deparse an CreateCastStmt. + * + * Given a sequence OID and a parse tree that modified it, return an ObjTree + * representing the creation command. + * + * Verbose syntax + * CREATE CAST (%{sourcetype}T AS %{targettype}T) %{mechanism}s %{context}s + */ +static ObjTree * +deparse_CreateCastStmt(Oid objectId, Node *parsetree) +{ + CreateCastStmt *node = (CreateCastStmt *) parsetree; + Relation castrel; + HeapTuple castTup; + Form_pg_cast castForm; + ObjTree *ret; + char *context; + + castrel = table_open(CastRelationId, AccessShareLock); + castTup = get_catalog_object_by_oid(castrel, Anum_pg_cast_oid, objectId); + if (!HeapTupleIsValid(castTup)) + elog(ERROR, "cache lookup failed for cast with OID %u", objectId); + castForm = (Form_pg_cast) GETSTRUCT(castTup); + + ret = new_objtree_VA("CREATE CAST (%{sourcetype}T AS %{targettype}T)", 2, + "sourcetype", ObjTypeObject, + new_objtree_for_type(castForm->castsource, -1), + "targettype", ObjTypeObject, + new_objtree_for_type(castForm->casttarget, -1)); + + if (node->inout) + append_string_object(ret, "%{mechanism}s", "mechanism", + "WITH INOUT"); + else if (node->func == NULL) + append_string_object(ret, "%{mechanism}s", "mechanism", + "WITHOUT FUNCTION"); + else + { + ObjTree *tmp_obj; + StringInfoData func; + HeapTuple funcTup; + Form_pg_proc funcForm; + int i; + + funcTup = SearchSysCache1(PROCOID, castForm->castfunc); + funcForm = (Form_pg_proc) GETSTRUCT(funcTup); + + initStringInfo(&func); + appendStringInfo(&func, "%s(", + quote_qualified_identifier(get_namespace_name(funcForm->pronamespace), + NameStr(funcForm->proname))); + for (i = 0; i < funcForm->pronargs; i++) + { + if (i != 0) + appendStringInfoChar(&func, ','); + + appendStringInfoString(&func, + format_type_be_qualified(funcForm->proargtypes.values[i])); + } + appendStringInfoChar(&func, ')'); + + tmp_obj = new_objtree_VA("WITH FUNCTION %{castfunction}s", 1, + "castfunction", ObjTypeString, func.data); + append_object_object(ret, "%{mechanism}s", tmp_obj); + + ReleaseSysCache(funcTup); + } + + switch (node->context) + { + case COERCION_IMPLICIT: + context = "AS IMPLICIT"; + break; + case COERCION_ASSIGNMENT: + context = "AS ASSIGNMENT"; + break; + case COERCION_EXPLICIT: + context = ""; + break; + default: + elog(ERROR, "invalid coercion code %c", node->context); + return NULL; /* keep compiler quiet */ + } + append_string_object(ret, "%{context}s", "context", context); + + table_close(castrel, AccessShareLock); + + return ret; +} + +/* + * Deparse an ALTER DEFAULT PRIVILEGES statement. + * + * Verbose syntax + * ALTER DEFAULT PRIVILEGES %{in_schema}s %{for_roles}s %{grant}s + */ +static ObjTree * +deparse_AlterDefaultPrivilegesStmt(CollectedCommand *cmd) +{ + ObjTree *ret; + AlterDefaultPrivilegesStmt *stmt = (AlterDefaultPrivilegesStmt *) cmd->parsetree; + List *roles = NIL; + List *schemas = NIL; + List *grantees; + List *privs; + ListCell *cell; + ObjTree *tmp; + ObjTree *grant; + char *objtype; + + ret = new_objtree("ALTER DEFAULT PRIVILEGES"); + + /* Scan the parse node to dig out the FOR ROLE and IN SCHEMA clauses */ + foreach(cell, stmt->options) + { + DefElem *opt = (DefElem *) lfirst(cell); + ListCell *cell2; + + Assert(IsA(opt, DefElem)); + Assert(IsA(opt->arg, List)); + if (strcmp(opt->defname, "roles") == 0) + { + foreach(cell2, (List *) opt->arg) + { + RoleSpec *rolespec = lfirst(cell2); + ObjTree *obj = new_objtree_for_rolespec(rolespec); + + roles = lappend(roles, new_object_object(obj)); + } + } + else if (strcmp(opt->defname, "schemas") == 0) + { + foreach(cell2, (List *) opt->arg) + { + String *val = lfirst_node(String, cell2); + + schemas = lappend(schemas, new_string_object(strVal(val))); + } + } + } + + /* Add the IN SCHEMA clause, if any */ + tmp = new_objtree("IN SCHEMA"); + append_array_object(tmp, "%{schemas:, }I", schemas); + if (schemas == NIL) + append_not_present(tmp); + append_object_object(ret, "%{in_schema}s", tmp); + + /* Add the FOR ROLE clause, if any */ + tmp = new_objtree("FOR ROLE"); + append_array_object(tmp, "%{roles:, }R", roles); + if (roles == NIL) + append_not_present(tmp); + append_object_object(ret, "%{for_roles}s", tmp); + + /* + * Add the GRANT subcommand + * Verbose syntax + * GRANT %{privileges:, }s ON %{target}s TO %{grantees:, }R %{grant_option}s + * or + * REVOKE %{grant_option}s %{privileges:, }s ON %{target}s FROM %{grantees:, }R + */ + if (stmt->action->is_grant) + grant = new_objtree("GRANT"); + else + { + grant = new_objtree("REVOKE"); + + /* Add the GRANT OPTION clause for REVOKE subcommand */ + tmp = new_objtree_VA("GRANT OPTION FOR", 1, + "present", ObjTypeBool, + stmt->action->grant_option); + append_object_object(grant, "%{grant_option}s", tmp); + } + + /* + * Add the privileges list. This uses the parser struct, as opposed to + * the InternalGrant format used by GRANT. There are enough other + * differences that this doesn't seem worth improving. + */ + if (stmt->action->privileges == NIL) + privs = list_make1(new_string_object("ALL PRIVILEGES")); + else + { + privs = NIL; + + foreach(cell, stmt->action->privileges) + { + AccessPriv *priv = lfirst(cell); + + Assert(priv->cols == NIL); + privs = lappend(privs, new_string_object(priv->priv_name)); + } + } + + append_array_object(grant, "%{privileges:, }s", privs); + + switch (cmd->d.defprivs.objtype) + { + case OBJECT_TABLE: + objtype = "TABLES"; + break; + case OBJECT_FUNCTION: + objtype = "FUNCTIONS"; + break; + case OBJECT_ROUTINE: + objtype = "ROUTINES"; + break; + case OBJECT_SEQUENCE: + objtype = "SEQUENCES"; + break; + case OBJECT_TYPE: + objtype = "TYPES"; + break; + case OBJECT_SCHEMA: + objtype = "SCHEMAS"; + break; + default: + elog(ERROR, "invalid OBJECT value %d for default privileges", cmd->d.defprivs.objtype); + } + + /* Add the target object type */ + append_string_object(grant, "ON %{target}s", "target", objtype); + + /* Add the grantee list */ + grantees = NIL; + foreach(cell, stmt->action->grantees) + { + RoleSpec *spec = (RoleSpec *) lfirst(cell); + ObjTree *obj = new_objtree_for_rolespec(spec); + + grantees = lappend(grantees, new_object_object(obj)); + } + + if (stmt->action->is_grant) + { + append_array_object(grant, "TO %{grantees:, }R", grantees); + + /* Add the WITH GRANT OPTION clause for GRANT subcommand */ + tmp = new_objtree_VA("WITH GRANT OPTION", 1, + "present", ObjTypeBool, + stmt->action->grant_option); + append_object_object(grant, "%{grant_option}s", tmp); + } + else + append_array_object(grant, "FROM %{grantees:, }R", grantees); + + append_object_object(ret, "%{grant}s", grant); + + return ret; +} + +/* + * Deparse the CREATE DOMAIN + * + * Given a function OID and the parse tree that created it, return an ObjTree + * representing the creation command. + * + * Verbose syntax + * CREATE DOMAIN %{identity}D AS %{type}T %{not_null}s %{constraints}s + * %{collation}s + */ +static ObjTree * +deparse_CreateDomain(Oid objectId, Node *parsetree) +{ + ObjTree *ret; + ObjTree *tmp_obj; + HeapTuple typTup; + Form_pg_type typForm; + List *constraints; + char *defval; + + typTup = SearchSysCache1(TYPEOID, objectId); + if (!HeapTupleIsValid(typTup)) + elog(ERROR, "cache lookup failed for domain with OID %u", objectId); + typForm = (Form_pg_type) GETSTRUCT(typTup); + + ret = new_objtree_VA("CREATE DOMAIN %{identity}D AS %{type}T %{not_null}s", 3, + "identity", ObjTypeObject, + new_objtree_for_qualname_id(TypeRelationId, objectId), + "type", ObjTypeObject, + new_objtree_for_type(typForm->typbasetype, + typForm->typtypmod), + "not_null", ObjTypeString, + typForm->typnotnull ? "NOT NULL" : ""); + + constraints = obtainConstraints(NIL, InvalidOid, objectId, + ConstrObjDomain); + if (constraints == NIL) + { + tmp_obj = new_objtree(""); + append_not_present(tmp_obj); + } + else + tmp_obj = new_objtree_VA("%{elements:, }s", 1, + "elements", ObjTypeArray, constraints); + append_object_object(ret, "%{constraints}s", tmp_obj); + + tmp_obj = new_objtree("COLLATE"); + if (OidIsValid(typForm->typcollation)) + { + ObjTree *collname; + + collname = new_objtree_for_qualname_id(CollationRelationId, + typForm->typcollation); + append_object_object(tmp_obj, "%{name}D", collname); + } + else + append_not_present(tmp_obj); + append_object_object(ret, "%{collation}s", tmp_obj); + + defval = DomainGetDefault(typTup, true); + if (defval) + append_string_object(ret, "DEFAULT %{default}s", "default", defval); + + ReleaseSysCache(typTup); + + return ret; +} + +/* + * Deparse a CreateFunctionStmt (CREATE FUNCTION) + * + * Given a function OID and the parse tree that created it, return an ObjTree + * representing the creation command. + * + * Verbose syntax + * + * CREATE %{or_replace}s FUNCTION %{signature}s RETURNS %{return_type}s + * LANGUAGE %{transform_type}s %{language}I %{window}s %{volatility}s + * %{parallel_safety}s %{leakproof}s %{strict}s %{security_definer}s + * %{cost}s %{rows}s %{support}s %{set_options: }s AS %{objfile}L, + * %{symbol}L + */ +static ObjTree * +deparse_CreateFunction(Oid objectId, Node *parsetree) +{ + CreateFunctionStmt *node = (CreateFunctionStmt *) parsetree; + ObjTree *ret; + ObjTree *tmp_obj; + Datum tmpdatum; + char *source; + char *probin = NULL; + List *params; + List *defaults; + List *sets = NIL; + List *types = NIL; + ListCell *cell; + ListCell *curdef; + ListCell *table_params = NULL; + HeapTuple procTup; + Form_pg_proc procForm; + HeapTuple langTup; + Oid *typarray; + Oid *trftypes; + Form_pg_language langForm; + int i; + int typnum; + int ntypes; + int paramcount = list_length(node->parameters); + bool isnull; + bool isfunction; + + /* Get the pg_proc tuple */ + procTup = SearchSysCache1(PROCOID, objectId); + if (!HeapTupleIsValid(procTup)) + elog(ERROR, "cache lookup failed for function with OID %u", + objectId); + procForm = (Form_pg_proc) GETSTRUCT(procTup); + + /* Get the corresponding pg_language tuple */ + langTup = SearchSysCache1(LANGOID, procForm->prolang); + if (!HeapTupleIsValid(langTup)) + elog(ERROR, "cache lookup failed for language with OID %u", + procForm->prolang); + langForm = (Form_pg_language) GETSTRUCT(langTup); + + /* + * Determine useful values for prosrc and probin. We cope with probin + * being either NULL or "-", but prosrc must have a valid value. + */ + tmpdatum = SysCacheGetAttr(PROCOID, procTup, + Anum_pg_proc_prosrc, &isnull); + if (isnull) + elog(ERROR, "null prosrc in function with OID %u", objectId); + source = TextDatumGetCString(tmpdatum); + + /* Determine a useful value for probin */ + tmpdatum = SysCacheGetAttr(PROCOID, procTup, + Anum_pg_proc_probin, &isnull); + if (!isnull) + { + probin = TextDatumGetCString(tmpdatum); + if (probin[0] == '\0' || strcmp(probin, "-") == 0) + probin = NULL; + } + + ret = new_objtree_VA("CREATE %{or_replace}s", 1, + "or_replace", ObjTypeString, + node->replace ? "OR REPLACE" : ""); + + /* + * To construct the arguments array, extract the type OIDs from the + * function's pg_proc entry. If pronargs equals the parameter list + * length, there are no OUT parameters and thus we can extract the type + * OID from proargtypes; otherwise we need to decode proallargtypes, which + * is a bit more involved. + */ + typarray = palloc(paramcount * sizeof(Oid)); + if (paramcount > procForm->pronargs) + { + Datum alltypes; + Datum *values; + bool *nulls; + int nelems; + + alltypes = SysCacheGetAttr(PROCOID, procTup, + Anum_pg_proc_proallargtypes, &isnull); + if (isnull) + elog(ERROR, "null proallargtypes, more number of parameters than args in function with OID %u", + objectId); + deconstruct_array(DatumGetArrayTypeP(alltypes), + OIDOID, 4, 't', 'i', + &values, &nulls, &nelems); + if (nelems != paramcount) + elog(ERROR, "mismatched proallargatypes"); + for (i = 0; i < paramcount; i++) + typarray[i] = values[i]; + } + else + { + for (i = 0; i < paramcount; i++) + typarray[i] = procForm->proargtypes.values[i]; + } + + /* + * If there are any default expressions, we read the deparsed expression + * as a list so that we can attach them to each argument. + */ + tmpdatum = SysCacheGetAttr(PROCOID, procTup, + Anum_pg_proc_proargdefaults, &isnull); + if (!isnull) + { + defaults = FunctionGetDefaults(DatumGetTextP(tmpdatum)); + curdef = list_head(defaults); + } + else + { + defaults = NIL; + curdef = NULL; + } + + /* + * Now iterate over each parameter in the parse tree to create the + * parameters array. + */ + params = NIL; + typnum = 0; + foreach(cell, node->parameters) + { + FunctionParameter *param = (FunctionParameter *) lfirst(cell); + ObjTree *param_obj; + ObjTree *defaultval; + ObjTree *name; + + /* + * A PARAM_TABLE parameter indicates the end of input arguments; the + * following parameters are part of the return type. We ignore them + * here, but keep track of the current position in the list so that we + * can easily produce the return type below. + */ + if (param->mode == FUNC_PARAM_TABLE) + { + table_params = cell; + break; + } + + /* + * Verbose syntax for paramater: %{mode}s %{name}s %{type}T + * %{default}s + */ + param_obj = new_objtree_VA("%{mode}s", 1, + "mode", ObjTypeString, + param->mode == FUNC_PARAM_OUT ? "OUT" : + param->mode == FUNC_PARAM_INOUT ? "INOUT" : + param->mode == FUNC_PARAM_VARIADIC ? "VARIADIC" : + "IN"); + + /* Optional wholesale suppression of "name" occurs here */ + name = new_objtree(""); + if (param->name) + append_string_object(name, "%{name}I", "name", param->name); + else + { + append_null_object(name, "%{name}I"); + append_not_present(name); + } + + append_object_object(param_obj, "%{name}s", name); + + defaultval = new_objtree("DEFAULT"); + if (PointerIsValid(param->defexpr)) + { + char *expr; + + if (curdef == NULL) + elog(ERROR, "proargdefaults list too short"); + expr = lfirst(curdef); + + append_string_object(defaultval, "%{value}s", "value", expr); + curdef = lnext(defaults, curdef); + } + else + append_not_present(defaultval); + + append_object_object(param_obj, "%{type}T", + new_objtree_for_type(typarray[typnum++], -1)); + + append_object_object(param_obj, "%{default}s", defaultval); + + params = lappend(params, new_object_object(param_obj)); + } + + tmp_obj = new_objtree_VA("%{identity}D", 1, + "identity", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, + objectId)); + + append_format_string(tmp_obj, "("); + append_array_object(tmp_obj, "%{arguments:, }s", params); + append_format_string(tmp_obj, ")"); + + isfunction = (procForm->prokind != PROKIND_PROCEDURE); + + if (isfunction) + append_object_object(ret, "FUNCTION %{signature}s", tmp_obj); + else + append_object_object(ret, "PROCEDURE %{signature}s", tmp_obj); + + /* + * A return type can adopt one of two forms: either a [SETOF] some_type, + * or a TABLE(list-of-types). We can tell the second form because we saw + * a table param above while scanning the argument list. + */ + if (table_params == NULL) + { + tmp_obj = new_objtree_VA("", 1, + "return_form", ObjTypeString, "plain"); + append_string_object(tmp_obj, "%{setof}s", "setof", + procForm->proretset ? "SETOF" : ""); + append_object_object(tmp_obj, "%{rettype}T", + new_objtree_for_type(procForm->prorettype, -1)); + } + else + { + List *rettypes = NIL; + ObjTree *param_obj; + + tmp_obj = new_objtree_VA("TABLE", 1, + "return_form", ObjTypeString, "table"); + for (; table_params != NULL; table_params = lnext(node->parameters, table_params)) + { + FunctionParameter *param = lfirst(table_params); + + param_obj = new_objtree_VA("%{name}I %{type}T", 2, + "name", ObjTypeString, param->name, + "type", ObjTypeObject, + new_objtree_for_type(typarray[typnum++], -1)); + rettypes = lappend(rettypes, new_object_object(param_obj)); + } + + append_array_object(tmp_obj, "(%{rettypes:, }s)", rettypes); + } + + if (isfunction) + append_object_object(ret, "RETURNS %{return_type}s", tmp_obj); + + /* TRANSFORM FOR TYPE */ + tmp_obj = new_objtree("TRANSFORM"); + + ntypes = get_func_trftypes(procTup, &trftypes); + for (i = 0; i < ntypes; i++) + { + tmp_obj = new_objtree_VA("FOR TYPE %{type}T", 1, + "type", ObjTypeObject, + new_objtree_for_type(trftypes[i], -1)); + types = lappend(types, tmp_obj); + } + + if (types) + append_array_object(tmp_obj, "%{for_type:, }s", types); + else + append_not_present(tmp_obj); + + append_object_object(ret, "%{transform_type}s", tmp_obj); + + append_string_object(ret, "LANGUAGE %{language}I", "language", + NameStr(langForm->lanname)); + + if (isfunction) + { + append_string_object(ret, "%{window}s", "window", + procForm->prokind == PROKIND_WINDOW ? "WINDOW" : ""); + append_string_object(ret, "%{volatility}s", "volatility", + procForm->provolatile == PROVOLATILE_VOLATILE ? + "VOLATILE" : + procForm->provolatile == PROVOLATILE_STABLE ? + "STABLE" : "IMMUTABLE"); + + append_string_object(ret, "%{parallel_safety}s", + "parallel_safety", + procForm->proparallel == PROPARALLEL_SAFE ? + "PARALLEL SAFE" : + procForm->proparallel == PROPARALLEL_RESTRICTED ? + "PARALLEL RESTRICTED" : "PARALLEL UNSAFE"); + + append_string_object(ret, "%{leakproof}s", "leakproof", + procForm->proleakproof ? "LEAKPROOF" : ""); + append_string_object(ret, "%{strict}s", "strict", + procForm->proisstrict ? + "RETURNS NULL ON NULL INPUT" : + "CALLED ON NULL INPUT"); + + append_string_object(ret, "%{security_definer}s", + "security_definer", + procForm->prosecdef ? + "SECURITY DEFINER" : "SECURITY INVOKER"); + + append_object_object(ret, "%{cost}s", + new_objtree_VA("COST %{cost}n", 1, + "cost", ObjTypeFloat, + procForm->procost)); + + tmp_obj = new_objtree("ROWS"); + if (procForm->prorows == 0) + append_not_present(tmp_obj); + else + append_float_object(tmp_obj, "%{rows}n", procForm->prorows); + append_object_object(ret, "%{rows}s", tmp_obj); + + tmp_obj = new_objtree("SUPPORT %{name}s"); + if (procForm->prosupport) + { + Oid argtypes[] = { INTERNALOID }; + + /* + * We should qualify the support function's name if it wouldn't be + * resolved by lookup in the current search path. + */ + append_string_object(tmp_obj, "%{name}s", "name", + generate_function_name(procForm->prosupport, 1, + NIL, argtypes, + false, NULL, + EXPR_KIND_NONE)); + } + else + append_not_present(tmp_obj); + + append_object_object(ret, "%{support}s", tmp_obj); + } + + foreach(cell, node->options) + { + DefElem *defel = (DefElem *) lfirst(cell); + + if (strcmp(defel->defname, "set") == 0) + { + VariableSetStmt *sstmt = (VariableSetStmt *) defel->arg; + char *value = ExtractSetVariableArgs(sstmt); + + tmp_obj = deparse_FunctionSet(sstmt->kind, sstmt->name, value); + sets = lappend(sets, new_object_object(tmp_obj)); + } + } + append_array_object(ret, "%{set_options: }s", sets); + + /* Add the function definition */ + (void) SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_prosqlbody, &isnull); + if (procForm->prolang == SQLlanguageId && !isnull) + { + StringInfoData buf; + + initStringInfo(&buf); + print_function_sqlbody(&buf, procTup); + + append_string_object(ret, "%{definition}s", "definition", + buf.data); + } + else if (probin == NULL) + append_string_object(ret, "AS %{definition}L", "definition", source); + else + { + append_string_object(ret, "AS %{objfile}L", "objfile", probin); + append_string_object(ret, ", %{symbol}L", "symbol", source); + } + + ReleaseSysCache(langTup); + ReleaseSysCache(procTup); + + return ret; +} + +/* + * Deparse a CREATE OPERATOR CLASS command. + * + * Verbose syntax + * CREATE OPERATOR CLASS %{identity}D %{default}s FOR TYPE %{type}T USING + * %{amname}I %{opfamily}s AS %{items:, }s + */ +static ObjTree * +deparse_CreateOpClassStmt(CollectedCommand *cmd) +{ + Oid opcoid = cmd->d.createopc.address.objectId; + HeapTuple opcTup; + HeapTuple opfTup; + Form_pg_opfamily opfForm; + Form_pg_opclass opcForm; + ObjTree *ret; + ObjTree *tmp_obj; + List *list; + ListCell *cell; + + /* Don't deparse SQL commands generated while creating extension */ + if (cmd->in_extension) + return NULL; + + opcTup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opcoid)); + if (!HeapTupleIsValid(opcTup)) + elog(ERROR, "cache lookup failed for opclass with OID %u", opcoid); + opcForm = (Form_pg_opclass) GETSTRUCT(opcTup); + + opfTup = SearchSysCache1(OPFAMILYOID, opcForm->opcfamily); + if (!HeapTupleIsValid(opfTup)) + elog(ERROR, "cache lookup failed for operator family with OID %u", opcForm->opcfamily); + opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup); + + ret = new_objtree_VA("CREATE OPERATOR CLASS %{identity}D %{default}s FOR TYPE %{type}T USING %{amname}I", 4, + "identity", ObjTypeObject, + new_objtree_for_qualname(opcForm->opcnamespace, + NameStr(opcForm->opcname)), + "default", ObjTypeString, + opcForm->opcdefault ? "DEFAULT" : "", + "type", ObjTypeObject, + new_objtree_for_type(opcForm->opcintype, -1), + "amname", ObjTypeString, get_am_name(opcForm->opcmethod)); + + /* + * Add the FAMILY clause, but if it has the same name and namespace as the + * opclass, then have it expand to empty because it would cause a failure + * if the opfamily was created internally. + */ + tmp_obj = new_objtree_VA("FAMILY %{opfamily}D", 1, + "opfamily", ObjTypeObject, + new_objtree_for_qualname(opfForm->opfnamespace, + NameStr(opfForm->opfname))); + + if (strcmp(NameStr(opfForm->opfname), NameStr(opcForm->opcname)) == 0 && + opfForm->opfnamespace == opcForm->opcnamespace) + append_not_present(tmp_obj); + + append_object_object(ret, "%{opfamily}s", tmp_obj); + + /* + * Add the initial item list. Note we always add the STORAGE clause, even + * when it is implicit in the original command. + */ + tmp_obj = new_objtree("STORAGE"); + append_object_object(tmp_obj, "%{type}T", + new_objtree_for_type(OidIsValid(opcForm->opckeytype) ? + opcForm->opckeytype : opcForm->opcintype, + -1)); + list = list_make1(new_object_object(tmp_obj)); + + /* Add the declared operators */ + foreach(cell, cmd->d.createopc.operators) + { + OpFamilyMember *oper = lfirst(cell); + + tmp_obj = new_objtree_VA("OPERATOR %{num}n %{operator}O(%{ltype}T, %{rtype}T)", 4, + "num", ObjTypeInteger, oper->number, + "operator", ObjTypeObject, + new_objtree_for_qualname_id(OperatorRelationId, + oper->object), + "ltype", ObjTypeObject, + new_objtree_for_type(oper->lefttype, -1), + "rtype", ObjTypeObject, + new_objtree_for_type(oper->righttype, -1)); + + /* Add the FOR SEARCH / FOR ORDER BY clause */ + if (oper->sortfamily == InvalidOid) + append_string_object(tmp_obj, "%{purpose}s", "purpose", + "FOR SEARCH"); + else + { + ObjTree *tmp_obj2; + + tmp_obj2 = new_objtree("FOR ORDER BY %{opfamily}D"); + append_object_object(tmp_obj2, "opfamily", + new_objtree_for_qualname_id(OperatorFamilyRelationId, + oper->sortfamily)); + append_object_object(tmp_obj, "%{purpose}s", tmp_obj2); + } + + list = lappend(list, new_object_object(tmp_obj)); + } + + /* Add the declared support functions */ + foreach(cell, cmd->d.createopc.procedures) + { + OpFamilyMember *proc = lfirst(cell); + HeapTuple procTup; + Form_pg_proc procForm; + Oid *proargtypes; + List *arglist = NIL; + int i; + + procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(proc->object)); + if (!HeapTupleIsValid(procTup)) + elog(ERROR, "cache lookup failed for procedure with OID %u", + proc->object); + procForm = (Form_pg_proc) GETSTRUCT(procTup); + + tmp_obj = new_objtree_VA("FUNCTION %{num}n (%{ltype}T, %{rtype}T) %{function}D", 4, + "num", ObjTypeInteger, proc->number, + "ltype", ObjTypeObject, + new_objtree_for_type(proc->lefttype, -1), + "rtype", ObjTypeObject, + new_objtree_for_type(proc->righttype, -1), + "function", ObjTypeObject, + new_objtree_for_qualname(procForm->pronamespace, + NameStr(procForm->proname))); + + proargtypes = procForm->proargtypes.values; + for (i = 0; i < procForm->pronargs; i++) + { + ObjTree *arg; + + arg = new_objtree_for_type(proargtypes[i], -1); + arglist = lappend(arglist, new_object_object(arg)); + } + + append_format_string(tmp_obj, "("); + append_array_object(tmp_obj, "%{argtypes:, }T", arglist); + append_format_string(tmp_obj, ")"); + + ReleaseSysCache(procTup); + + list = lappend(list, new_object_object(tmp_obj)); + } + + append_array_object(ret, "AS %{items:, }s", list); + + ReleaseSysCache(opfTup); + ReleaseSysCache(opcTup); + + return ret; +} + +/* + * Deparse a CreateOpFamilyStmt (CREATE OPERATOR FAMILY) + * + * Given a operator family OID and the parse tree that created it, return an + * ObjTree representing the creation command. + * + * Verbose syntax + * CREATE OPERATOR FAMILY %{identity}D USING %{amname}I + */ +static ObjTree * +deparse_CreateOpFamily(Oid objectId, Node *parsetree) +{ + HeapTuple opfTup; + HeapTuple amTup; + Form_pg_opfamily opfForm; + Form_pg_am amForm; + ObjTree *ret; + + opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(opfTup)) + elog(ERROR, "cache lookup failed for operator family with OID %u", objectId); + opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup); + + amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod)); + if (!HeapTupleIsValid(amTup)) + elog(ERROR, "cache lookup failed for access method with OID %u", + opfForm->opfmethod); + amForm = (Form_pg_am) GETSTRUCT(amTup); + + ret = new_objtree_VA("CREATE OPERATOR FAMILY %{identity}D USING %{amname}I", 2, + "identity", ObjTypeObject, + new_objtree_for_qualname(opfForm->opfnamespace, + NameStr(opfForm->opfname)), + "amname", ObjTypeString, NameStr(amForm->amname)); + + ReleaseSysCache(amTup); + ReleaseSysCache(opfTup); + + return ret; +} + +/* + * Add common clauses to CreatePolicy or AlterPolicy deparse objects. + */ +static void +add_policy_clauses(ObjTree *ret, Oid policyOid, List *roles, bool do_qual, + bool do_with_check) +{ + Relation polRel = table_open(PolicyRelationId, AccessShareLock); + HeapTuple polTup = get_catalog_object_by_oid(polRel, Anum_pg_policy_oid, policyOid); + Form_pg_policy polForm; + + if (!HeapTupleIsValid(polTup)) + elog(ERROR, "cache lookup failed for policy with OID %u", policyOid); + + polForm = (Form_pg_policy) GETSTRUCT(polTup); + + /* Add the "ON table" clause */ + append_object_object(ret, "ON %{table}D", + new_objtree_for_qualname_id(RelationRelationId, + polForm->polrelid)); + + /* + * Add the "TO role" clause, if any. In the CREATE case, it always + * contains at least PUBLIC, but in the ALTER case it might be empty. + */ + if (roles) + { + List *list = NIL; + ListCell *cell; + + foreach(cell, roles) + { + RoleSpec *spec = (RoleSpec *) lfirst(cell); + + list = lappend(list, + new_object_object(new_objtree_for_rolespec(spec))); + } + append_array_object(ret, "TO %{role:, }R", list); + } + else + append_not_present(ret); + + /* Add the USING clause, if any */ + if (do_qual) + { + Datum deparsed; + Datum storedexpr; + bool isnull; + + storedexpr = heap_getattr(polTup, Anum_pg_policy_polqual, + RelationGetDescr(polRel), &isnull); + if (isnull) + elog(ERROR, "null polqual expression in policy %u", policyOid); + deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid); + append_string_object(ret, "USING (%{expression}s)", "expression", + TextDatumGetCString(deparsed)); + } + else + append_not_present(ret); + + /* Add the WITH CHECK clause, if any */ + if (do_with_check) + { + Datum deparsed; + Datum storedexpr; + bool isnull; + + storedexpr = heap_getattr(polTup, Anum_pg_policy_polwithcheck, + RelationGetDescr(polRel), &isnull); + if (isnull) + elog(ERROR, "null polwithcheck expression in policy %u", policyOid); + deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid); + append_string_object(ret, "WITH CHECK (%{expression}s)", + "expression", TextDatumGetCString(deparsed)); + } + else + append_not_present(ret); + + relation_close(polRel, AccessShareLock); +} + +/* + * Deparse a CreatePolicyStmt (CREATE POLICY) + * + * Given a policy OID and the parse tree that created it, return an ObjTree + * representing the creation command. + * + * Verbose syntax + * CREATE POLICY %{identity}I + */ +static ObjTree * +deparse_CreatePolicyStmt(Oid objectId, Node *parsetree) +{ + CreatePolicyStmt *node = (CreatePolicyStmt *) parsetree; + ObjTree *ret; + + ret = new_objtree_VA("CREATE POLICY %{identity}I", 1, + "identity", ObjTypeString, node->policy_name); + + /* Add the rest of the stuff */ + add_policy_clauses(ret, objectId, node->roles, node->qual != NULL, + node->with_check != NULL); + + return ret; +} + +/* + * Deparse a AlterPolicyStmt (ALTER POLICY) + * + * Given a policy OID and the parse tree of the ALTER POLICY command, return + * an ObjTree representing the alter command. + * + * Verbose syntax + * ALTER POLICY %{identity}I + */ +static ObjTree * +deparse_AlterPolicyStmt(Oid objectId, Node *parsetree) +{ + AlterPolicyStmt *node = (AlterPolicyStmt *) parsetree; + ObjTree *ret; + + ret = new_objtree_VA("ALTER POLICY %{identity}I", 1, + "identity", ObjTypeString, node->policy_name); + + /* Add the rest of the stuff */ + add_policy_clauses(ret, objectId, node->roles, node->qual != NULL, + node->with_check != NULL); + + return ret; +} + +/* + * Deparse a CreateSchemaStmt. + * + * Given a schema OID and the parse tree that created it, return an ObjTree + * representing the creation command. + * + * Verbose syntax + * CREATE SCHEMA %{if_not_exists}s %{name}I AUTHORIZATION %{authorization}s +*/ +static ObjTree * +deparse_CreateSchemaStmt(Oid objectId, Node *parsetree) +{ + CreateSchemaStmt *node = (CreateSchemaStmt *) parsetree; + ObjTree *ret; + ObjTree *auth; + + ret = new_objtree_VA("CREATE SCHEMA %{if_not_exists}s %{name}I", 2, + "if_not_exists", ObjTypeString, + node->if_not_exists ? "IF NOT EXISTS" : "", + "name", ObjTypeString, + node->schemaname ? node->schemaname : ""); + + auth = new_objtree("AUTHORIZATION"); + if (node->authrole) + append_string_object(auth, "%{authorization_role}I", + "authorization_role", + get_rolespec_name(node->authrole)); + else + { + append_null_object(auth, "%{authorization_role}I"); + append_not_present(auth); + } + append_object_object(ret, "%{authorization}s", auth); + + return ret; +} + +/* + * Return the default value of a domain. + */ +static char * +DomainGetDefault(HeapTuple domTup, bool missing_ok) +{ + Datum def; + Node *defval; + char *defstr; + bool isnull; + + def = SysCacheGetAttr(TYPEOID, domTup, Anum_pg_type_typdefaultbin, + &isnull); + if (isnull) + { + if (!missing_ok) + elog(ERROR, "domain \"%s\" does not have a default value", + NameStr(((Form_pg_type) GETSTRUCT(domTup))->typname)); + else + return NULL; + } + + defval = stringToNode(TextDatumGetCString(def)); + defstr = deparse_expression(defval, NULL /* dpcontext? */ , + false, false); + + return defstr; +} + +/* + * Deparse a AlterDomainStmt. + * + * Verbose syntax + * ALTER DOMAIN %{identity}D DROP DEFAULT + * OR + * ALTER DOMAIN %{identity}D SET DEFAULT %{default}s + * OR + * ALTER DOMAIN %{identity}D DROP NOT NULL + * OR + * ALTER DOMAIN %{identity}D SET NOT NULL + * OR + * ALTER DOMAIN %{identity}D ADD CONSTRAINT %{constraint_name}s %{definition}s + * OR + * ALTER DOMAIN %{identity}D DROP CONSTRAINT IF EXISTS %{constraint_name}s %{cascade}s + * OR + * ALTER DOMAIN %{identity}D VALIDATE CONSTRAINT %{constraint_name}s + */ +static ObjTree * +deparse_AlterDomainStmt(Oid objectId, Node *parsetree, + ObjectAddress constraintAddr) +{ + AlterDomainStmt *node = (AlterDomainStmt *) parsetree; + HeapTuple domTup; + Form_pg_type domForm; + ObjTree *ret; + + domTup = SearchSysCache1(TYPEOID, objectId); + if (!HeapTupleIsValid(domTup)) + elog(ERROR, "cache lookup failed for domain with OID %u", objectId); + domForm = (Form_pg_type) GETSTRUCT(domTup); + + switch (node->subtype) + { + case 'T': + /* SET DEFAULT / DROP DEFAULT */ + if (node->def == NULL) + ret = new_objtree_VA("ALTER DOMAIN %{identity}D DROP DEFAULT", 2, + "type", ObjTypeString, "drop default", + "identity", ObjTypeObject, + new_objtree_for_qualname(domForm->typnamespace, + NameStr(domForm->typname))); + else + ret = new_objtree_VA("ALTER DOMAIN %{identity}D SET DEFAULT %{default}s", 3, + "type", ObjTypeString, "set default", + "identity", ObjTypeObject, + new_objtree_for_qualname(domForm->typnamespace, + NameStr(domForm->typname)), + "default", ObjTypeString, + DomainGetDefault(domTup, false)); + break; + case 'N': + /* DROP NOT NULL */ + ret = new_objtree_VA("ALTER DOMAIN %{identity}D DROP NOT NULL", 2, + "type", ObjTypeString, "drop not null", + "identity", ObjTypeObject, + new_objtree_for_qualname(domForm->typnamespace, + NameStr(domForm->typname))); + break; + case 'O': + /* SET NOT NULL */ + ret = new_objtree_VA("ALTER DOMAIN %{identity}D SET NOT NULL", 2, + "type", ObjTypeString, "set not null", + "identity", ObjTypeObject, + new_objtree_for_qualname(domForm->typnamespace, + NameStr(domForm->typname))); + break; + case 'C': + /* + * ADD CONSTRAINT. Only CHECK constraints are supported by + * domains + */ + ret = new_objtree_VA("ALTER DOMAIN %{identity}D ADD CONSTRAINT %{constraint_name}s %{definition}s", 4, + "type", ObjTypeString, "add constraint", + "identity", ObjTypeObject, + new_objtree_for_qualname(domForm->typnamespace, + NameStr(domForm->typname)), + "constraint_name", ObjTypeString, + get_constraint_name(constraintAddr.objectId), + "definition", ObjTypeString, + pg_get_constraintdef_string(constraintAddr.objectId)); + break; + case 'V': + /* VALIDATE CONSTRAINT */ + /* + * Process subtype=specific options. Validation a constraint + * requires its name. + */ + ret = new_objtree_VA("ALTER DOMAIN %{identity}D VALIDATE CONSTRAINT %{constraint_name}s", 3, + "type", ObjTypeString, "validate constraint", + "identity", ObjTypeObject, + new_objtree_for_qualname(domForm->typnamespace, + NameStr(domForm->typname)), + "constraint_name", ObjTypeString, node->name); + break; + case 'X': + { + ObjTree *tmp_obj; + + /* DROP CONSTRAINT */ + ret = new_objtree_VA("ALTER DOMAIN %{identity}D DROP CONSTRAINT IF EXISTS %{constraint_name}s", 3, + "type", ObjTypeString, "drop constraint", + "identity", ObjTypeObject, + new_objtree_for_qualname(domForm->typnamespace, + NameStr(domForm->typname)), + "constraint_name", ObjTypeString, node->name); + + tmp_obj = new_objtree_VA("CASCADE", 1, + "present", ObjTypeBool, node->behavior == DROP_CASCADE); + + append_object_object(ret, "%{cascade}s", tmp_obj); + } + break; + default: + elog(ERROR, "invalid subtype %c", node->subtype); + } + + /* Done */ + ReleaseSysCache(domTup); + + return ret; +} + +/* + * Deparse a CreateStatsStmt. + * + * Given a statistics OID and the parse tree that created it, return an ObjTree + * representing the creation command. + * + * Verbose syntax + * CREATE STATISTICS %{if_not_exists}s %{identity}D ON %{expr:, }s FROM %{stat_table_identity}D + */ +static ObjTree * +deparse_CreateStatisticsStmt(Oid objectId, Node *parsetree) +{ + CreateStatsStmt *node = (CreateStatsStmt *) parsetree; + Form_pg_statistic_ext statform; + ObjTree *ret; + HeapTuple tup; + Datum datum; + bool isnull; + List *statexprs = NIL; + + tup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for statistic with OID %u", objectId); + + statform = (Form_pg_statistic_ext) GETSTRUCT(tup); + + ret = new_objtree_VA("CREATE STATISTICS %{if_not_exists}s %{identity}D", 2, + "if_not_exists", ObjTypeString, + node->if_not_exists ? "IF NOT EXISTS" : "", + "identity", ObjTypeObject, + new_objtree_for_qualname(statform->stxnamespace, + NameStr(statform->stxname))); + + datum = SysCacheGetAttr(STATEXTOID, tup, Anum_pg_statistic_ext_stxexprs, + &isnull); + if (!isnull) + { + ListCell *lc; + Relation statsrel; + List *context; + List *exprs = NIL; + char *exprsString; + + statsrel = relation_open(statform->stxrelid, AccessShareLock); + context = deparse_context_for(RelationGetRelationName(statsrel), + RelationGetRelid(statsrel)); + + exprsString = TextDatumGetCString(datum); + exprs = (List *) stringToNode(exprsString); + + foreach(lc, exprs) + { + Node *expr = (Node *) lfirst(lc); + char *statexpr; + + statexpr = deparse_expression(expr, context, false, false); + statexprs = lappend(statexprs, new_string_object(statexpr)); + } + + append_array_object(ret, "ON %{expr:, }s", statexprs); + pfree(exprsString); + relation_close(statsrel, AccessShareLock); + } + + datum = SysCacheGetAttr(STATEXTOID, tup, Anum_pg_statistic_ext_stxkeys, + &isnull); + if (!isnull) + { + int keyno; + char *attname; + List *statcols = NIL; + int2vector *indoption; + + indoption = (int2vector *) DatumGetPointer(datum); + + for (keyno = 0; keyno < indoption->dim1; keyno++) + { + attname = get_attname(statform->stxrelid, indoption->values[keyno], + false); + statcols = lappend(statcols, new_string_object(attname)); + } + + if (indoption->dim1) + { + /* Append a ',' if statexprs is present else append 'ON' */ + append_string_object(ret, "%{comma}s", "comma", + statexprs ? "," : "ON"); + append_array_object(ret, "%{cols:, }s", statcols); + } + } + + append_object_object(ret, "FROM %{stat_table_identity}D", + new_objtree_for_qualname(get_rel_namespace(statform->stxrelid), + get_rel_name(statform->stxrelid))); + + ReleaseSysCache(tup); + + return ret; +} + +/* + * Deparse an CreateForeignServerStmt (CREATE SERVER) + * + * Given a server OID and the parse tree that created it, return an ObjTree + * representing the alter command. + * + * Verbose syntax + * CREATE SERVER %{identity}I %{type}s %{version}s FOREIGN DATA WRAPPER %{fdw}I + * %{generic_options}s + */ +static ObjTree * +deparse_CreateForeignServerStmt(Oid objectId, Node *parsetree) +{ + CreateForeignServerStmt *node = (CreateForeignServerStmt *) parsetree; + ObjTree *ret; + ObjTree *tmp; + + ret = new_objtree_VA("CREATE SERVER %{identity}I", 1, + "identity", ObjTypeString, node->servername); + + /* Add a TYPE clause, if any */ + tmp = new_objtree("TYPE"); + if (node->servertype) + append_string_object(tmp, "%{type}L", "type", node->servertype); + else + append_not_present(tmp); + append_object_object(ret, "%{type}s", tmp); + + /* Add a VERSION clause, if any */ + tmp = new_objtree("VERSION"); + if (node->version) + append_string_object(tmp, "%{version}L", "version", node->version); + else + append_not_present(tmp); + append_object_object(ret, "%{version}s", tmp); + + append_string_object(ret, "FOREIGN DATA WRAPPER %{fdw}I", "fdw", + node->fdwname); + + /* Add an OPTIONS clause, if any */ + append_object_object(ret, "%{generic_options}s", + deparse_FdwOptions(node->options, NULL, false)); + + return ret; +} + +/* + * Deparse an AlterForeignServerStmt (ALTER SERVER) + * + * Given a server OID and the parse tree that created it, return an ObjTree + * representing the alter command. + * + * Verbose syntax + * ALTER SERVER %{identity}I %{version}s %{generic_options}s + */ +static ObjTree * +deparse_AlterForeignServerStmt(Oid objectId, Node *parsetree) +{ + AlterForeignServerStmt *node = (AlterForeignServerStmt *) parsetree; + ObjTree *tmp; + + /* Add a VERSION clause, if any */ + tmp = new_objtree("VERSION"); + if (node->has_version && node->version) + append_string_object(tmp, "%{version}L", "version", node->version); + else if (node->has_version) + append_string_object(tmp, "version", "version", "NULL"); + else + append_not_present(tmp); + + return new_objtree_VA("ALTER SERVER %{identity}I %{version}s %{generic_options}s", 3, + "identity", ObjTypeString, node->servername, + "version", ObjTypeObject, tmp, + "generic_options", ObjTypeObject, + deparse_FdwOptions(node->options, NULL, false)); +} + +/* + * Deparse a CreatePLangStmt. + * + * Given a language OID and the parse tree that created it, return an ObjTree + * representing the creation command. + * + * Verbose syntax + * CREATE %{or_replace}s %{trusted}s LANGUAGE %{identity}s HANDLER %{handler}D + * %{inline}s %{validator}s + */ +static ObjTree * +deparse_CreateLangStmt(Oid objectId, Node *parsetree) +{ + CreatePLangStmt *node = (CreatePLangStmt *) parsetree; + ObjTree *ret; + ObjTree *tmp; + HeapTuple langTup; + Form_pg_language langForm; + + langTup = SearchSysCache1(LANGOID, + ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(langTup)) + elog(ERROR, "cache lookup failed for language with OID %u", objectId); + langForm = (Form_pg_language) GETSTRUCT(langTup); + + ret = new_objtree_VA("CREATE %{or_replace}s %{trusted}s LANGUAGE %{identity}s", 3, + "or_replace", ObjTypeString, + node->replace ? "OR REPLACE" : "", + "trusted", ObjTypeString, + langForm->lanpltrusted ? "TRUSTED" : "", + "identity", ObjTypeString, node->plname); + + if (node->plhandler != NIL) + { + /* Add the HANDLER clause */ + append_object_object(ret, "HANDLER %{handler}D", + new_objtree_for_qualname_id(ProcedureRelationId, + langForm->lanplcallfoid)); + + /* Add the INLINE clause, if any */ + tmp = new_objtree("INLINE"); + if (OidIsValid(langForm->laninline)) + { + append_object_object(tmp, "%{handler_name}D", + new_objtree_for_qualname_id(ProcedureRelationId, + langForm->laninline)); + } + else + append_not_present(tmp); + append_object_object(ret, "%{inline}s", tmp); + + /* Add the VALIDATOR clause, if any */ + tmp = new_objtree("VALIDATOR"); + if (OidIsValid(langForm->lanvalidator)) + { + append_object_object(tmp, "%{handler_name}D", + new_objtree_for_qualname_id(ProcedureRelationId, + langForm->lanvalidator)); + } + else + append_not_present(tmp); + append_object_object(ret, "%{validator}s", tmp); + } + + ReleaseSysCache(langTup); + + return ret; +} + +/* + * Deparse a CreateForeignTableStmt (CREATE FOREIGN TABLE). + * + * Given a table OID and the parse tree that created it, return an ObjTree + * representing the creation command. + * + * CREATE FOREIGN TABLE %{if_not_exists}s %{identity}D + * [(%{table_elements:, }s) %{inherits}s + * | PARTITION OF %{parent_identity}D (%{typed_table_elements:, }s) %{partition_bound}s] + * SERVER %{server}I %{generic_options}s + */ +static ObjTree * +deparse_CreateForeignTableStmt(Oid objectId, Node *parsetree) +{ + CreateForeignTableStmt *stmt = (CreateForeignTableStmt *) parsetree; + Relation relation = relation_open(objectId, AccessShareLock); + List *dpcontext; + ObjTree *createStmt; + ObjTree *tmpobj; + List *tableelts = NIL; + + createStmt = new_objtree("CREATE FOREIGN TABLE"); + + append_string_object(createStmt, "%{if_not_exists}s", "if_not_exists", + stmt->base.if_not_exists ? "IF NOT EXISTS" : ""); + + tmpobj = new_objtree_for_qualname(relation->rd_rel->relnamespace, + RelationGetRelationName(relation)); + append_object_object(createStmt, "%{identity}D", tmpobj); + + dpcontext = deparse_context_for(RelationGetRelationName(relation), + objectId); + if (stmt->base.partbound) + { + /* Partitioned table */ + List *parents; + ObjElem *elem; + + parents = deparse_InhRelations(objectId); + elem = (ObjElem *) linitial(parents); + + Assert(list_length(parents) == 1); + + append_format_string(createStmt, "PARTITION OF"); + + append_object_object(createStmt, "%{parent_identity}D", + elem->value.object); + + tableelts = deparse_TableElements(relation, stmt->base.tableElts, dpcontext, + true, /* typed table */ + false); /* not composite */ + tableelts = obtainConstraints(tableelts, objectId, InvalidOid, + ConstrObjForeignTable); + + tmpobj = new_objtree(""); + if (tableelts) + append_array_object(tmpobj, "(%{elements:, }s)", tableelts); + else + append_not_present(tmpobj); + + append_object_object(createStmt, "%{typed_table_elements}s", tmpobj); + + /* + * Add the partition_bound_spec, i.e. the FOR VALUES clause. + * Get pg_class.relpartbound. We cannot use partbound in the parsetree + * directly as it's the original partbound expression which haven't + * been transformed. + */ + append_string_object(createStmt, "%{partition_bound}s", "partition_bound", + RelationGetPartitionBound(objectId)); + + /* No PARTITION BY clause for CREATE FOREIGN TABLE */ + } + else + { + tableelts = deparse_TableElements(relation, stmt->base.tableElts, dpcontext, + false, /* not typed table */ + false); /* not composite */ + tableelts = obtainConstraints(tableelts, objectId, InvalidOid, + ConstrObjForeignTable); + + if (tableelts) + append_array_object(createStmt, "(%{table_elements:, }s)", tableelts); + else + append_format_string(createStmt, "()"); + + /* + * Add inheritance specification. We cannot simply scan the list of + * parents from the parser node, because that may lack the actual + * qualified names of the parent relations. Rather than trying to + * re-resolve them from the information in the parse node, it seems + * more accurate and convenient to grab it from pg_inherits. + */ + tmpobj = new_objtree("INHERITS"); + if (stmt->base.inhRelations != NIL) + append_array_object(tmpobj, "(%{parents:, }D)", deparse_InhRelations(objectId)); + else + { + append_null_object(tmpobj, "(%{parents:, }D)"); + append_not_present(tmpobj); + } + append_object_object(createStmt, "%{inherits}s", tmpobj); + } + + append_string_object(createStmt, "SERVER %{server}I", "server", stmt->servername); + + /* add an OPTIONS clause, if any */ + append_object_object(createStmt, "%{generic_options}s", + deparse_FdwOptions(stmt->options, NULL, false)); + + relation_close(relation, AccessShareLock); + + return createStmt; +} + +/* + * Deparse a DefineStmt. + */ +static ObjTree * +deparse_DefineStmt(Oid objectId, Node *parsetree, ObjectAddress secondaryObj) +{ + DefineStmt *define = (DefineStmt *) parsetree; + + switch (define->kind) + { + case OBJECT_AGGREGATE: + return deparse_DefineStmt_Aggregate(objectId, define); + + case OBJECT_COLLATION: + return deparse_DefineStmt_Collation(objectId, define, secondaryObj); + + case OBJECT_OPERATOR: + return deparse_DefineStmt_Operator(objectId, define); + + case OBJECT_TSCONFIGURATION: + return deparse_DefineStmt_TSConfig(objectId, define, secondaryObj); + + case OBJECT_TSDICTIONARY: + return deparse_DefineStmt_TSDictionary(objectId, define); + + case OBJECT_TSPARSER: + return deparse_DefineStmt_TSParser(objectId, define); + + case OBJECT_TSTEMPLATE: + return deparse_DefineStmt_TSTemplate(objectId, define); + + case OBJECT_TYPE: + return deparse_DefineStmt_Type(objectId, define); + + default: + elog(ERROR, "unsupported object kind: %d", define->kind); + } + + return NULL; +} + +/* + * Form an ObjElem to be used as a single argument in an aggregate argument + * list + */ +static ObjElem * +form_agg_argument(int idx, char *modes, char **names, Oid *types) +{ + ObjTree *arg; + + arg = new_objtree(""); + + append_string_object(arg, "%{mode}s", "mode", + (modes && modes[idx] == 'v') ? "VARIADIC" : ""); + append_string_object(arg, "%{name}s", "name", names ? names[idx] : ""); + append_object_object(arg, "%{type}T", + new_objtree_for_type(types[idx], -1)); + + return new_object_object(arg); +} + +/* + * Deparse a DefineStmt (CREATE AGGREGATE) + * + * Given a aggregate OID, return an ObjTree representing the CREATE command. + * + * Verbose syntax + * CREATE %{or_replace}s AGGREGATE %{identity}D(%{types}s) (%{elems:, }s) + */ +static ObjTree * +deparse_DefineStmt_Aggregate(Oid objectId, DefineStmt *define) +{ + HeapTuple aggTup; + HeapTuple procTup; + ObjTree *stmt; + ObjTree *tmp; + List *list; + Datum initval; + bool isnull; + Form_pg_aggregate agg; + Form_pg_proc proc; + Form_pg_operator op; + HeapTuple tup; + + aggTup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(aggTup)) + elog(ERROR, "cache lookup failed for aggregate with OID %u", objectId); + agg = (Form_pg_aggregate) GETSTRUCT(aggTup); + + procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(agg->aggfnoid)); + if (!HeapTupleIsValid(procTup)) + elog(ERROR, "cache lookup failed for procedure with OID %u", + agg->aggfnoid); + proc = (Form_pg_proc) GETSTRUCT(procTup); + + stmt = new_objtree_VA("CREATE %{or_replace}s AGGREGATE", 1, + "or_replace", ObjTypeString, define->replace ? "OR REPLACE" : ""); + + append_object_object(stmt, "%{identity}D", + new_objtree_for_qualname(proc->pronamespace, + NameStr(proc->proname))); + + /* + * Add the argument list. There are three cases to consider: + * + * 1. no arguments, in which case the signature is (*). + * + * 2. Not an ordered-set aggregate. In this case there's one or more + * arguments. + * + * 3. Ordered-set aggregates. These have zero or more direct arguments, and + * one or more ordered arguments. + * + * We don't need to consider default values or table parameters, and the + * only mode that we need to consider is VARIADIC. + */ + + if (proc->pronargs == 0) + append_string_object(stmt, "(%{types}s)", "types", "*"); + else if (!AGGKIND_IS_ORDERED_SET(agg->aggkind)) + { + int i; + int nargs; + Oid *types; + char *modes; + char **names; + + nargs = get_func_arg_info(procTup, &types, &names, &modes); + + /* only direct arguments in this case */ + list = NIL; + for (i = 0; i < nargs; i++) + { + list = lappend(list, form_agg_argument(i, modes, names, types)); + } + + tmp = new_objtree_VA("%{direct:, }s", 1, + "direct", ObjTypeArray, list); + append_object_object(stmt, "(%{types}s)", tmp); + } + else + { + int i; + int nargs; + Oid *types; + char *modes; + char **names; + + nargs = get_func_arg_info(procTup, &types, &names, &modes); + + /* direct arguments ... */ + list = NIL; + for (i = 0; i < agg->aggnumdirectargs; i++) + { + list = lappend(list, form_agg_argument(i, modes, names, types)); + } + tmp = new_objtree_VA("%{direct:, }s", 1, + "direct", ObjTypeArray, list); + + /* + * ... and aggregated arguments. If the last direct argument is + * VARIADIC, we need to repeat it here rather than searching for more + * arguments. (See aggr_args production in gram.y for an explanation.) + */ + if (modes && modes[agg->aggnumdirectargs - 1] == 'v') + { + list = list_make1(form_agg_argument(agg->aggnumdirectargs - 1, + modes, names, types)); + } + else + { + list = NIL; + for (i = agg->aggnumdirectargs; i < nargs; i++) + { + list = lappend(list, form_agg_argument(i, modes, names, types)); + } + } + append_array_object(tmp, "ORDER BY %{aggregated:, }s", list); + + append_object_object(stmt, "(%{types}s)", tmp); + } + + /* Add the definition clause */ + list = NIL; + + /* SFUNC */ + tmp = new_objtree_VA("SFUNC=%{procedure}D", 1, + "procedure", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, agg->aggtransfn)); + list = lappend(list, new_object_object(tmp)); + + /* STYPE */ + tmp = new_objtree_VA("STYPE=%{type}T", 1, + "type", ObjTypeObject, + new_objtree_for_type(agg->aggtranstype, -1)); + list = lappend(list, new_object_object(tmp)); + + /* SSPACE */ + tmp = new_objtree("SSPACE="); + if (agg->aggtransspace != 0) + append_int_object(tmp,"%{space}n", agg->aggtransspace); + else + append_not_present(tmp); + + list = lappend(list, new_object_object(tmp)); + + /* FINALFUNC */ + tmp = new_objtree("FINALFUNC="); + if (OidIsValid(agg->aggfinalfn)) + append_object_object(tmp, "%{procedure}D", + new_objtree_for_qualname_id(ProcedureRelationId, + agg->aggfinalfn)); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* FINALFUNC_EXTRA */ + tmp = new_objtree("FINALFUNC_EXTRA="); + if (agg->aggfinalextra) + append_string_object(tmp, "%{value}s", "value", "true"); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* INITCOND */ + initval = SysCacheGetAttr(AGGFNOID, aggTup, + Anum_pg_aggregate_agginitval, + &isnull); + tmp = new_objtree("INITCOND="); + if (!isnull) + append_string_object(tmp,"%{initval}L", "initval", TextDatumGetCString(initval)); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* MSFUNC */ + tmp = new_objtree("MSFUNC="); + if (OidIsValid(agg->aggmtransfn)) + append_object_object(tmp, "%{procedure}D", + new_objtree_for_qualname_id(ProcedureRelationId, agg->aggmtransfn)); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* MSTYPE */ + tmp = new_objtree("MSTYPE="); + if (OidIsValid(agg->aggmtranstype)) + append_object_object(tmp, "%{type}T", new_objtree_for_type(agg->aggmtranstype, -1)); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* MSSPACE */ + tmp = new_objtree("MSSPACE="); + if (agg->aggmtransspace != 0) + append_int_object(tmp, "%{space}n", agg->aggmtransspace); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* MINVFUNC */ + tmp = new_objtree("MINVFUNC="); + if (OidIsValid(agg->aggminvtransfn)) + append_object_object(tmp, "%{type}T", new_objtree_for_qualname_id(ProcedureRelationId, + agg->aggminvtransfn)); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* MFINALFUNC */ + tmp = new_objtree("MFINALFUNC="); + if (OidIsValid(agg->aggmfinalfn)) + append_object_object(tmp, "%{procedure}D", + new_objtree_for_qualname_id(ProcedureRelationId, + agg->aggmfinalfn)); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* MFINALFUNC_EXTRA */ + tmp = new_objtree("MFINALFUNC_EXTRA="); + if (agg->aggmfinalextra) + append_string_object(tmp, "%{value}s", "value", "true"); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* MINITCOND */ + initval = SysCacheGetAttr(AGGFNOID, aggTup, + Anum_pg_aggregate_aggminitval, + &isnull); + tmp = new_objtree("MINITCOND="); + if (!isnull) + append_string_object(tmp, "%{initval}L", "initval", TextDatumGetCString(initval)); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* HYPOTHETICAL */ + tmp = new_objtree("HYPOTHETICAL="); + if (agg->aggkind == AGGKIND_HYPOTHETICAL) + append_string_object(tmp, "%{value}s", "value", "true"); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* SORTOP */ + tmp = new_objtree("SORTOP="); + if (OidIsValid(agg->aggsortop)) + { + tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(agg->aggsortop)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for operator with OID %u", agg->aggsortop); + op = (Form_pg_operator) GETSTRUCT(tup); + append_object_object(tmp, "%{operator}D", + new_objtree_for_qualname(op->oprnamespace, + NameStr(op->oprname))); + + ReleaseSysCache(tup); + } + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* Done with the definition clause */ + append_array_object(stmt, "(%{elems:, }s)", list); + + ReleaseSysCache(procTup); + ReleaseSysCache(aggTup); + + return stmt; +} + +/* + * Deparse a DefineStmt (CREATE COLLATION) + * + * Given a collation OID, return an ObjTree representing the CREATE command. + * + * Verbose syntax + * CREATE COLLATION %{if_not_exists}s %{identity}D FROM %{from_identity}D + * (%{elems:, }s) + */ +static ObjTree * +deparse_DefineStmt_Collation(Oid objectId, DefineStmt *define, + ObjectAddress fromCollid) +{ + ObjTree *ret; + HeapTuple colTup; + Form_pg_collation colForm; + Datum datum; + bool isnull; + ObjTree *tmp; + List *list = NIL; + + colTup = SearchSysCache1(COLLOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(colTup)) + elog(ERROR, "cache lookup failed for collation with OID %u", objectId); + colForm = (Form_pg_collation) GETSTRUCT(colTup); + + ret = new_objtree_VA("CREATE COLLATION %{if_not_exists}s %{identity}D", 2, + "if_not_exists", ObjTypeString, + define->if_not_exists ? "IF NOT EXISTS" : "", + "identity", ObjTypeObject, + new_objtree_for_qualname(colForm->collnamespace, + NameStr(colForm->collname))); + + if (OidIsValid(fromCollid.objectId)) + { + Oid collid = fromCollid.objectId; + HeapTuple tp; + Form_pg_collation fromColForm; + + /* + * CREATE COLLATION %{identity}D FROM existing_collation; + */ + tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for collation with OID %u", collid); + + fromColForm = (Form_pg_collation) GETSTRUCT(tp); + + append_object_object(ret, "FROM %{from_identity}D", + new_objtree_for_qualname(fromColForm->collnamespace, + NameStr(fromColForm->collname))); + + ReleaseSysCache(tp); + ReleaseSysCache(colTup); + return ret; + } + + /* LOCALE */ + datum = SysCacheGetAttr(COLLOID, colTup, Anum_pg_collation_colliculocale, &isnull); + if (!isnull) + { + tmp = new_objtree_VA("LOCALE=%{locale}L", 2, + "clause", ObjTypeString, "locale", + "locale", ObjTypeString, + psprintf("%s", TextDatumGetCString(datum))); + list = lappend(list, new_object_object(tmp)); + } + + /* LC_COLLATE */ + datum = SysCacheGetAttr(COLLOID, colTup, Anum_pg_collation_collcollate, &isnull); + if (!isnull) + { + tmp = new_objtree_VA("LC_COLLATE=%{collate}L", 2, + "clause", ObjTypeString, "collate", + "collate", ObjTypeString, + psprintf("%s", TextDatumGetCString(datum))); + list = lappend(list, new_object_object(tmp)); + } + + /* LC_CTYPE */ + datum = SysCacheGetAttr(COLLOID, colTup, Anum_pg_collation_collctype, &isnull); + if (!isnull) + { + tmp = new_objtree_VA("LC_CTYPE=%{ctype}L", 2, + "clause", ObjTypeString, "ctype", + "ctype", ObjTypeString, + psprintf("%s", TextDatumGetCString(datum))); + list = lappend(list, new_object_object(tmp)); + } + + /* PROVIDER */ + if (colForm->collprovider == COLLPROVIDER_ICU) + { + tmp = new_objtree_VA("PROVIDER=%{provider}L", 2, + "clause", ObjTypeString, "provider", + "provider", ObjTypeString, + psprintf("%s", "icu")); + list = lappend(list, new_object_object(tmp)); + } + else if (colForm->collprovider == COLLPROVIDER_LIBC) + { + tmp = new_objtree_VA("PROVIDER=%{provider}L", 2, + "clause", ObjTypeString, "provider", + "provider", ObjTypeString, + psprintf("%s", "libc")); + list = lappend(list, new_object_object(tmp)); + } + + /* DETERMINISTIC */ + if (colForm->collisdeterministic) + { + tmp = new_objtree_VA("DETERMINISTIC=%{deterministic}L", 2, + "clause", ObjTypeString, "deterministic", + "deterministic", ObjTypeString, + psprintf("%s", "true")); + list = lappend(list, new_object_object(tmp)); + } + + /* VERSION */ + datum = SysCacheGetAttr(COLLOID, colTup, Anum_pg_collation_collversion, &isnull); + if (!isnull) + { + tmp = new_objtree_VA("VERSION=%{version}L", 2, + "clause", ObjTypeString, "version", + "version", ObjTypeString, + psprintf("%s", TextDatumGetCString(datum))); + list = lappend(list, new_object_object(tmp)); + } + + append_array_object(ret, "(%{elems:, }s)", list); + ReleaseSysCache(colTup); + + return ret; +} + +/* + * Deparse a DefineStmt (CREATE OPERATOR) + * + * Given a operator OID and the parse tree that created it, return an ObjTree + * representing the creation command. + * + * Verbose syntax + * CREATE OPERATOR %{identity}O (%{elems:, }s) + */ +static ObjTree * +deparse_DefineStmt_Operator(Oid objectId, DefineStmt *define) +{ + HeapTuple oprTup; + ObjTree *ret; + ObjTree *tmp_obj; + List *list = NIL; + Form_pg_operator oprForm; + + oprTup = SearchSysCache1(OPEROID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(oprTup)) + elog(ERROR, "cache lookup failed for operator with OID %u", objectId); + oprForm = (Form_pg_operator) GETSTRUCT(oprTup); + + ret = new_objtree_VA("CREATE OPERATOR %{identity}O", 1, + "identity", ObjTypeObject, + new_objtree_for_qualname(oprForm->oprnamespace, + NameStr(oprForm->oprname))); + + /* PROCEDURE */ + tmp_obj = new_objtree_VA("PROCEDURE=%{procedure}D", 2, + "clause", ObjTypeString, "procedure", + "procedure", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, + oprForm->oprcode)); + list = lappend(list, new_object_object(tmp_obj)); + + /* LEFTARG */ + tmp_obj = new_objtree_VA("LEFTARG=", 1, + "clause", ObjTypeString, "leftarg"); + if (OidIsValid(oprForm->oprleft)) + append_object_object(tmp_obj, "%{type}T", + new_objtree_for_type(oprForm->oprleft, -1)); + else + append_not_present(tmp_obj); + list = lappend(list, new_object_object(tmp_obj)); + + /* RIGHTARG */ + tmp_obj = new_objtree_VA("RIGHTARG=", 1, + "clause", ObjTypeString, "rightarg"); + if (OidIsValid(oprForm->oprright)) + append_object_object(tmp_obj, "%{type}T", + new_objtree_for_type(oprForm->oprright, -1)); + else + append_not_present(tmp_obj); + list = lappend(list, new_object_object(tmp_obj)); + + /* COMMUTATOR */ + tmp_obj = new_objtree_VA("COMMUTATOR=", 1, + "clause", ObjTypeString, "commutator"); + if (OidIsValid(oprForm->oprcom)) + append_object_object(tmp_obj, "%{oper}D", + new_objtree_for_qualname_id(OperatorRelationId, + oprForm->oprcom)); + else + append_not_present(tmp_obj); + list = lappend(list, new_object_object(tmp_obj)); + + /* NEGATOR */ + tmp_obj = new_objtree_VA("NEGATOR=", 1, + "clause", ObjTypeString, "negator"); + if (OidIsValid(oprForm->oprnegate)) + append_object_object(tmp_obj, "%{oper}D", + new_objtree_for_qualname_id(OperatorRelationId, + oprForm->oprnegate)); + else + append_not_present(tmp_obj); + list = lappend(list, new_object_object(tmp_obj)); + + /* RESTRICT */ + tmp_obj = new_objtree_VA("RESTRICT=", 1, + "clause", ObjTypeString, "restrict"); + if (OidIsValid(oprForm->oprrest)) + append_object_object(tmp_obj, "%{procedure}D", + new_objtree_for_qualname_id(ProcedureRelationId, + oprForm->oprrest)); + else + append_not_present(tmp_obj); + list = lappend(list, new_object_object(tmp_obj)); + + /* JOIN */ + tmp_obj = new_objtree_VA("JOIN=", 1, + "clause", ObjTypeString, "join"); + if (OidIsValid(oprForm->oprjoin)) + append_object_object(tmp_obj, "%{procedure}D", + new_objtree_for_qualname_id(ProcedureRelationId, + oprForm->oprjoin)); + else + append_not_present(tmp_obj); + list = lappend(list, new_object_object(tmp_obj)); + + /* HASHES */ + tmp_obj = new_objtree_VA("HASHES", 1, + "clause", ObjTypeString, "hashes"); + if (!oprForm->oprcanhash) + append_not_present(tmp_obj); + list = lappend(list, new_object_object(tmp_obj)); + + /* MERGES */ + tmp_obj = new_objtree_VA("MERGES", 1, + "clause", ObjTypeString, "merges"); + if (!oprForm->oprcanmerge) + append_not_present(tmp_obj); + list = lappend(list, new_object_object(tmp_obj)); + + append_array_object(ret, "(%{elems:, }s)", list); + + ReleaseSysCache(oprTup); + + return ret; +} + +/* + * Deparse a CREATE TYPE statement. + * + * Verbose syntax + * CREATE TYPE %{identity}D %{elems:, }s) + */ +static ObjTree * +deparse_DefineStmt_Type(Oid objectId, DefineStmt *define) +{ + HeapTuple typTup; + ObjTree *ret; + ObjTree *tmp; + List *list = NIL; + char *str; + Datum dflt; + bool isnull; + Form_pg_type typForm; + + typTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(typTup)) + elog(ERROR, "cache lookup failed for type with OID %u", objectId); + typForm = (Form_pg_type) GETSTRUCT(typTup); + + ret = new_objtree_VA("CREATE TYPE %{identity}D", 1, + "identity", ObjTypeObject, + new_objtree_for_qualname(typForm->typnamespace, + NameStr(typForm->typname))); + + /* Shell types. */ + if (!typForm->typisdefined) + { + ReleaseSysCache(typTup); + return ret; + } + + /* Add the definition clause */ + /* INPUT */ + tmp = new_objtree_VA("(INPUT=%{procedure}D", 2, + "clause", ObjTypeString, "input", + "procedure", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, + typForm->typinput)); + list = lappend(list, new_object_object(tmp)); + + /* OUTPUT */ + tmp = new_objtree_VA("OUTPUT=%{procedure}D", 2, + "clause", ObjTypeString, "output", + "procedure", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, + typForm->typoutput)); + list = lappend(list, new_object_object(tmp)); + + /* RECEIVE */ + tmp = new_objtree_VA("RECEIVE=", 1, + "clause", ObjTypeString, "receive"); + if (OidIsValid(typForm->typreceive)) + append_object_object(tmp, "%{procedure}D", + new_objtree_for_qualname_id(ProcedureRelationId, + typForm->typreceive)); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* SEND */ + tmp = new_objtree_VA("SEND=", 1, + "clause", ObjTypeString, "send"); + if (OidIsValid(typForm->typsend)) + append_object_object(tmp, "%{procedure}D", + new_objtree_for_qualname_id(ProcedureRelationId, + typForm->typsend)); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* TYPMOD_IN */ + tmp = new_objtree_VA("TYPMOD_IN=", 1, + "clause", ObjTypeString, "typmod_in"); + if (OidIsValid(typForm->typmodin)) + append_object_object(tmp, "%{procedure}D", + new_objtree_for_qualname_id(ProcedureRelationId, + typForm->typmodin)); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* TYPMOD_OUT */ + tmp = new_objtree_VA("TYPMOD_OUT=", 1, + "clause", ObjTypeString, "typmod_out"); + if (OidIsValid(typForm->typmodout)) + append_object_object(tmp, "%{procedure}D", + new_objtree_for_qualname_id(ProcedureRelationId, + typForm->typmodout)); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* ANALYZE */ + tmp = new_objtree_VA("ANALYZE=", 1, + "clause", ObjTypeString, "analyze"); + if (OidIsValid(typForm->typanalyze)) + append_object_object(tmp, "%{procedure}D", + new_objtree_for_qualname_id(ProcedureRelationId, + typForm->typanalyze)); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* INTERNALLENGTH */ + if (typForm->typlen == -1) + tmp = new_objtree("INTERNALLENGTH=VARIABLE"); + else + tmp = new_objtree_VA("INTERNALLENGTH=%{typlen}n", 1, + "typlen", ObjTypeInteger, typForm->typlen); + + list = lappend(list, new_object_object(tmp)); + + /* PASSEDBYVALUE */ + tmp = new_objtree_VA("PASSEDBYVALUE", 1, + "clause", ObjTypeString, "passedbyvalue"); + if (!typForm->typbyval) + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* XXX it's odd to represent alignment with schema-qualified type names */ + switch (typForm->typalign) + { + case 'd': + str = "pg_catalog.float8"; + break; + case 'i': + str = "pg_catalog.int4"; + break; + case 's': + str = "pg_catalog.int2"; + break; + case 'c': + str = "pg_catalog.bpchar"; + break; + default: + elog(ERROR, "invalid alignment %c", typForm->typalign); + } + + /* ALIGNMENT */ + tmp = new_objtree_VA("ALIGNMENT=%{align}s", 2, + "clause", ObjTypeString, "alignment", + "align", ObjTypeString, str); + list = lappend(list, new_object_object(tmp)); + + /* STORAGE */ + tmp = new_objtree_VA("STORAGE=%{storage}s", 2, + "clause", ObjTypeString, "storage", + "storage", ObjTypeString, + get_type_storage(typForm->typstorage)); + list = lappend(list, new_object_object(tmp)); + + /* CATEGORY */ + tmp = new_objtree_VA("CATEGORY=%{category}s", 2, + "clause", ObjTypeString, "category", + "category", ObjTypeString, + psprintf("%c", typForm->typcategory)); + list = lappend(list, new_object_object(tmp)); + + /* PREFERRED */ + tmp = new_objtree_VA("PREFERRED=%{preferred}s", 1, + "preferred", ObjTypeString, + typForm->typispreferred ? "TRUE" : "FALSE"); + if (!typForm->typispreferred) + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* DEFAULT */ + dflt = SysCacheGetAttr(TYPEOID, typTup, + Anum_pg_type_typdefault, + &isnull); + tmp = new_objtree_VA("DEFAULT=", 1, + "clause", ObjTypeString, "default"); + if (!isnull) + append_string_object(tmp, "%{default}s", "default", + TextDatumGetCString(dflt)); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* ELEMENT */ + tmp = new_objtree_VA("ELEMENT=", 1, + "clause", ObjTypeString, "element"); + if (OidIsValid(typForm->typelem)) + append_object_object(tmp, "%{elem}T", + new_objtree_for_type(typForm->typelem, -1)); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* DELIMITER */ + tmp = new_objtree_VA("DELIMITER=", 1, + "clause", ObjTypeString, "delimiter"); + append_string_object(tmp, "%{delim}L", "delim", + psprintf("%c", typForm->typdelim)); + list = lappend(list, new_object_object(tmp)); + + /* COLLATABLE */ + tmp = new_objtree_VA("COLLATABLE=", 1, + "clause", ObjTypeString, "collatable"); + if (!OidIsValid(typForm->typcollation)) + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + append_array_object(ret, "%{elems:, }s)", list); + + ReleaseSysCache(typTup); + + return ret; +} + +/* + * Deparse a CREATE TEXT SEARCH CONFIGURATION statement. + * + * Verbose syntax + * CREATE TEXT SEARCH CONFIGURATION %{identity}D (%{elems:, }s) + */ +static ObjTree * +deparse_DefineStmt_TSConfig(Oid objectId, DefineStmt *define, + ObjectAddress copied) +{ + HeapTuple tscTup; + HeapTuple tspTup; + ObjTree *ret; + ObjTree *tmp; + Form_pg_ts_config tscForm; + Form_pg_ts_parser tspForm; + List *list = NIL; + + tscTup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(tscTup)) + elog(ERROR, "cache lookup failed for text search configuration with OID %u", + objectId); + tscForm = (Form_pg_ts_config) GETSTRUCT(tscTup); + + tspTup = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(tscForm->cfgparser)); + if (!HeapTupleIsValid(tspTup)) + elog(ERROR, "cache lookup failed for text search parser %u", + tscForm->cfgparser); + tspForm = (Form_pg_ts_parser) GETSTRUCT(tspTup); + + /* + * Add the definition clause. If we have COPY'ed an existing config, add + * a COPY clause; otherwise add a PARSER clause. + */ + /* COPY */ + tmp = new_objtree_VA("COPY=", 1, + "clause", ObjTypeString, "copy"); + if (OidIsValid(copied.objectId)) + append_object_object(tmp, "%{tsconfig}D", + new_objtree_for_qualname_id(TSConfigRelationId, + copied.objectId)); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* PARSER */ + tmp = new_objtree_VA("PARSER=", 1, + "clause", ObjTypeString, "parser"); + if (copied.objectId == InvalidOid) + append_object_object(tmp, "%{parser}D", + new_objtree_for_qualname(tspForm->prsnamespace, + NameStr(tspForm->prsname))); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + ret = new_objtree_VA("CREATE TEXT SEARCH CONFIGURATION %{identity}D (%{elems:, }s)", 2, + "identity", ObjTypeObject, + new_objtree_for_qualname(tscForm->cfgnamespace, + NameStr(tscForm->cfgname)), + "elems", ObjTypeArray, list); + + ReleaseSysCache(tspTup); + ReleaseSysCache(tscTup); + return ret; +} + +/* + * Deparse a CREATE TEXT SEARCH PARSER statement. + * + * Verbose syntax + * CREATE TEXT SEARCH PARSER %{identity}D (%{elems:, }s) + */ +static ObjTree * +deparse_DefineStmt_TSParser(Oid objectId, DefineStmt *define) +{ + HeapTuple tspTup; + ObjTree *ret; + ObjTree *tmp; + List *list = NIL; + Form_pg_ts_parser tspForm; + + tspTup = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(tspTup)) + elog(ERROR, "cache lookup failed for text search parser with OID %u", + objectId); + tspForm = (Form_pg_ts_parser) GETSTRUCT(tspTup); + + /* Add the definition clause */ + /* START */ + tmp = new_objtree_VA("START=%{procedure}D", 2, + "clause", ObjTypeString, "start", + "procedure", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, + tspForm->prsstart)); + list = lappend(list, new_object_object(tmp)); + + /* GETTOKEN */ + tmp = new_objtree_VA("GETTOKEN=%{procedure}D", 2, + "clause", ObjTypeString, "gettoken", + "procedure", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, + tspForm->prstoken)); + list = lappend(list, new_object_object(tmp)); + + /* END */ + tmp = new_objtree_VA("END=%{procedure}D", 2, + "clause", ObjTypeString, "end", + "procedure", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, + tspForm->prsend)); + list = lappend(list, new_object_object(tmp)); + + /* LEXTYPES */ + tmp = new_objtree_VA("LEXTYPES=%{procedure}D", 2, + "clause", ObjTypeString, "lextypes", + "procedure", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, + tspForm->prslextype)); + list = lappend(list, new_object_object(tmp)); + + /* HEADLINE */ + tmp = new_objtree_VA("HEADLINE=", 1, + "clause", ObjTypeString, "headline"); + if (OidIsValid(tspForm->prsheadline)) + append_object_object(tmp, "%{procedure}D", + new_objtree_for_qualname_id(ProcedureRelationId, + tspForm->prsheadline)); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + ret = new_objtree_VA("CREATE TEXT SEARCH PARSER %{identity}D (%{elems:, }s)", 2, + "identity", ObjTypeObject, + new_objtree_for_qualname(tspForm->prsnamespace, + NameStr(tspForm->prsname)), + "elems", ObjTypeArray, list); + + ReleaseSysCache(tspTup); + return ret; +} + +/* + * Deparse a CREATE TEXT SEARCH DICTIONARY statement. + * + * Verbose syntax + * CREATE TEXT SEARCH DICTIONARY %{identity}D (%{elems:, }s) + */ +static ObjTree * +deparse_DefineStmt_TSDictionary(Oid objectId, DefineStmt *define) +{ + HeapTuple tsdTup; + ObjTree *ret; + ObjTree *tmp; + List *list = NIL; + Datum options; + bool isnull; + Form_pg_ts_dict tsdForm; + + tsdTup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(tsdTup)) + elog(ERROR, "cache lookup failed for text search dictionary with OID %u", + objectId); + tsdForm = (Form_pg_ts_dict) GETSTRUCT(tsdTup); + + + + /* Add the definition clause */ + /* TEMPLATE */ + tmp = new_objtree_VA("TEMPLATE=", 1, + "clause", ObjTypeString, "template"); + append_object_object(tmp, "%{template}D", + new_objtree_for_qualname_id(TSTemplateRelationId, + tsdForm->dicttemplate)); + list = lappend(list, new_object_object(tmp)); + + /* + * options. XXX this is a pretty useless representation, but we can't do + * better without more ts_cache.c cooperation ... + */ + options = SysCacheGetAttr(TSDICTOID, tsdTup, + Anum_pg_ts_dict_dictinitoption, + &isnull); + tmp = new_objtree(""); + if (!isnull) + append_string_object(tmp, "%{options}s", "options", + TextDatumGetCString(options)); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + ret = new_objtree_VA("CREATE TEXT SEARCH DICTIONARY %{identity}D (%{elems:, }s)", 2, + "identity", ObjTypeObject, + new_objtree_for_qualname(tsdForm->dictnamespace, + NameStr(tsdForm->dictname)), + "elems", ObjTypeArray, list); + + ReleaseSysCache(tsdTup); + return ret; +} + +/* + * Deparse a CREATE TEXT SEARCH TEMPLATE statement. + * + * Verbose syntax + * CREATE TEXT SEARCH TEMPLATE %{identity}D (%{elems:, }s) + */ +static ObjTree * +deparse_DefineStmt_TSTemplate(Oid objectId, DefineStmt *define) +{ + HeapTuple tstTup; + ObjTree *ret; + ObjTree *tmp; + List *list = NIL; + Form_pg_ts_template tstForm; + + tstTup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(tstTup)) + elog(ERROR, "cache lookup failed for text search template with OID %u", + objectId); + tstForm = (Form_pg_ts_template) GETSTRUCT(tstTup); + + /* Add the definition clause */ + /* INIT */ + tmp = new_objtree_VA("INIT=", 1, + "clause", ObjTypeString, "init"); + if (OidIsValid(tstForm->tmplinit)) + append_object_object(tmp, "%{procedure}D", + new_objtree_for_qualname_id(ProcedureRelationId, + tstForm->tmplinit)); + else + append_not_present(tmp); + list = lappend(list, new_object_object(tmp)); + + /* LEXIZE */ + tmp = new_objtree_VA("LEXIZE=%{procedure}D", 2, + "clause", ObjTypeString, "lexize", + "procedure", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, + tstForm->tmpllexize)); + list = lappend(list, new_object_object(tmp)); + + ret = new_objtree_VA("CREATE TEXT SEARCH TEMPLATE %{identity}D (%{elems:, }s)", 2, + "identity", ObjTypeObject, + new_objtree_for_qualname(tstForm->tmplnamespace, + NameStr(tstForm->tmplname)), + "elems", ObjTypeArray, list); + + ReleaseSysCache(tstTup); + return ret; +} + +/* + * Deparse an ALTER TEXT SEARCH CONFIGURATION statement. + * + * Verbose syntax + * ALTER TEXT SEARCH CONFIGURATION %{identity}D ADD MAPPING + * FOR %{tokentype:, }I WITH %{dictionaries:, }D + * OR + * ALTER TEXT SEARCH CONFIGURATION %{identity}D DROP MAPPING %{if_exists}s + * FOR %{tokentype}I + * OR + * ALTER TEXT SEARCH CONFIGURATION %{identity}D ALTER MAPPING + * FOR %{tokentype:, }I WITH %{dictionaries:, }D + * OR + * ALTER TEXT SEARCH CONFIGURATION %{identity}D ALTER MAPPING + * REPLACE %{old_dictionary}D WITH %{new_dictionary}D + * OR + * ALTER TEXT SEARCH CONFIGURATION %{identity}D ALTER MAPPING + * FOR %{tokentype:, }I REPLACE %{old_dictionary}D WITH %{new_dictionary}D + */ +static ObjTree * +deparse_AlterTSConfigurationStmt(CollectedCommand *cmd) +{ + AlterTSConfigurationStmt *node = (AlterTSConfigurationStmt *) cmd->parsetree; + ObjTree *ret; + ObjTree *tmp; + List *list = NIL; + ListCell *cell; + int i; + + ret = new_objtree("ALTER TEXT SEARCH CONFIGURATION"); + + /* Determine the format string appropriate to each subcommand */ + switch (node->kind) + { + case ALTER_TSCONFIG_ADD_MAPPING: + append_object_object(ret, "%{identity}D ADD MAPPING", + new_objtree_for_qualname_id(cmd->d.atscfg.address.classId, + cmd->d.atscfg.address.objectId)); + break; + + case ALTER_TSCONFIG_DROP_MAPPING: + append_object_object(ret, "%{identity}D DROP MAPPING", + new_objtree_for_qualname_id(cmd->d.atscfg.address.classId, + cmd->d.atscfg.address.objectId)); + tmp = new_objtree("IF EXISTS"); + append_bool_object(tmp, "present", node->missing_ok); + append_object_object(ret, "%{if_exists}s", tmp); + break; + + case ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN: + case ALTER_TSCONFIG_REPLACE_DICT: + case ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN: + append_object_object(ret, "%{identity}D ALTER MAPPING", + new_objtree_for_qualname_id(cmd->d.atscfg.address.classId, + cmd->d.atscfg.address.objectId)); + break; + } + + /* Add the affected token list, for subcommands that have one */ + if (node->kind == ALTER_TSCONFIG_ADD_MAPPING || + node->kind == ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN || + node->kind == ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN || + node->kind == ALTER_TSCONFIG_DROP_MAPPING) + { + foreach(cell, node->tokentype) + list = lappend(list, new_string_object(strVal(lfirst(cell)))); + append_array_object(ret, "FOR %{tokentype:, }I", list); + } + + /* Add further subcommand-specific elements */ + if (node->kind == ALTER_TSCONFIG_ADD_MAPPING || + node->kind == ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN) + { + /* ADD MAPPING and ALTER MAPPING FOR need a list of dictionaries */ + list = NIL; + for (i = 0; i < cmd->d.atscfg.ndicts; i++) + { + ObjTree *dict_obj; + + dict_obj = new_objtree_for_qualname_id(TSDictionaryRelationId, + cmd->d.atscfg.dictIds[i]); + list = lappend(list, + new_object_object(dict_obj)); + } + append_array_object(ret, "WITH %{dictionaries:, }D", list); + } + else if (node->kind == ALTER_TSCONFIG_REPLACE_DICT || + node->kind == ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN) + { + /* The REPLACE forms want old and new dictionaries */ + Assert(cmd->d.atscfg.ndicts == 2); + append_object_object(ret, "REPLACE %{old_dictionary}D", + new_objtree_for_qualname_id(TSDictionaryRelationId, + cmd->d.atscfg.dictIds[0])); + append_object_object(ret, "WITH %{new_dictionary}D", + new_objtree_for_qualname_id(TSDictionaryRelationId, + cmd->d.atscfg.dictIds[1])); + } + + return ret; +} + +/* + * Deparse an ALTER TEXT SEARCH DICTIONARY statement. + * + * Verbose syntax + * ALTER TEXT SEARCH DICTIONARY %{identity}D (%{definition:, }s) + */ +static ObjTree * +deparse_AlterTSDictionaryStmt(Oid objectId, Node *parsetree) +{ + ObjTree *ret; + ObjTree *tmp; + Datum options; + List *definition = NIL; + bool isnull; + HeapTuple tsdTup; + Form_pg_ts_dict tsdForm; + + tsdTup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(tsdTup)) + elog(ERROR, "cache lookup failed for text search dictionary with OID %u", + objectId); + tsdForm = (Form_pg_ts_dict) GETSTRUCT(tsdTup); + + /* + * Add the definition list according to the pg_ts_dict dictinitoption + * column + */ + options = SysCacheGetAttr(TSDICTOID, tsdTup, + Anum_pg_ts_dict_dictinitoption, + &isnull); + tmp = new_objtree(""); + if (!isnull) + append_string_object(tmp, "%{options}s", "options", + TextDatumGetCString(options)); + else + append_not_present(tmp); + + definition = lappend(definition, new_object_object(tmp)); + + ret = new_objtree_VA("ALTER TEXT SEARCH DICTIONARY %{identity}D (%{definition:, }s)", 2, + "identity", ObjTypeObject, + new_objtree_for_qualname(tsdForm->dictnamespace, + NameStr(tsdForm->dictname)), + "definition", ObjTypeArray, definition); + + ReleaseSysCache(tsdTup); + return ret; +} + +/* + * deparse_ViewStmt + * deparse a ViewStmt + * + * Given a view OID and the parse tree that created it, return an ObjTree + * representing the creation command. + * + * Verbose syntax + * CREATE %{or_replace}s %{persistence}s VIEW %{identity}D AS %{query}s + */ +static ObjTree * +deparse_ViewStmt(Oid objectId, Node *parsetree) +{ + ViewStmt *node = (ViewStmt *) parsetree; + ObjTree *ret; + Relation relation; + + relation = relation_open(objectId, AccessShareLock); + + ret = new_objtree_VA("CREATE %{or_replace}s %{persistence}s VIEW %{identity}D AS %{query}s", 4, + "or_replace", ObjTypeString, + node->replace ? "OR REPLACE" : "", + "persistence", ObjTypeString, + get_persistence_str(relation->rd_rel->relpersistence), + "identity", ObjTypeObject, + new_objtree_for_qualname(relation->rd_rel->relnamespace, + RelationGetRelationName(relation)), + "query", ObjTypeString, + pg_get_viewdef_string(objectId)); + + relation_close(relation, AccessShareLock); + return ret; +} + +/* + * Deparse CREATE Materialized View statement, it is a variant of + * CreateTableAsStmt + * + * Note that CREATE TABLE AS SELECT INTO can also be deparsed by + * deparse_CreateTableAsStmt to remove the SELECT INTO clause. + * + * Verbose syntax + * CREATE %{persistence}s [MATERIALIZED VIEW | TABLE] %{if_not_exists}s + * %{identity}D %{columns}s [%{on_commit}s] %{tablespace}s + * AS %{query}s %{with_no_data}s" + */ +static ObjTree * +deparse_CreateTableAsStmt_vanilla(Oid objectId, Node *parsetree) +{ + CreateTableAsStmt *node = (CreateTableAsStmt *) parsetree; + Relation relation = relation_open(objectId, AccessShareLock); + ObjTree *ret; + ObjTree *tmp; + ObjTree *tmp2; + char *fmt; + List *list = NIL; + ListCell *cell; + + /* Reject unsupported case right away. */ + if (((Query *) (node->query))->commandType == CMD_UTILITY) + elog(ERROR, "unimplemented deparse of CREATE TABLE AS EXECUTE"); + + /* + * Note that INSERT INTO is deparsed as CREATE TABLE AS. They are + * functionally equivalent synonyms so there is no harm from this. + */ + if (node->objtype == OBJECT_MATVIEW) + fmt = "CREATE %{persistence}s MATERIALIZED VIEW %{if_not_exists}s %{identity}D"; + else + fmt = "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D"; + + ret = new_objtree_VA(fmt, 3, + "persistence", ObjTypeString, + get_persistence_str(node->into->rel->relpersistence), + "if_not_exists", ObjTypeString, + node->if_not_exists ? "IF NOT EXISTS" : "", + "identity", ObjTypeObject, + new_objtree_for_qualname_id(RelationRelationId, + objectId)); + + /* COLUMNS clause */ + if (node->into->colNames) + { + foreach(cell, node->into->colNames) + list = lappend(list, new_string_object(strVal(lfirst(cell)))); + + tmp = new_objtree_VA("(%{columns:, }I)", 1, + "columns", ObjTypeArray, list); + } + else + tmp = new_objtree_VA("", 1, + "present", ObjTypeBool, false); + + append_object_object(ret, "%{columns}s", tmp); + + /* USING clause */ + tmp = new_objtree("USING"); + if (node->into->accessMethod) + append_string_object(tmp, "%{access_method}I", "access_method", + node->into->accessMethod); + else + { + append_null_object(tmp, "%{access_method}I"); + append_not_present(tmp); + } + append_object_object(ret, "%{access_method}s", tmp); + + /* WITH clause */ + tmp = new_objtree("WITH"); + list = NIL; + + foreach(cell, node->into->options) + { + DefElem *opt = (DefElem *) lfirst(cell); + + tmp2 = deparse_DefElem(opt, false); + list = lappend(list, new_object_object(tmp2)); + } + + if (list) + append_array_object(tmp, "(%{with:, }s)", list); + else + append_not_present(tmp); + + append_object_object(ret, "%{with_clause}s", tmp); + + /* ON COMMIT clause. CREATE MATERIALIZED VIEW doesn't have one */ + if (node->objtype == OBJECT_TABLE) + append_object_object(ret, "%{on_commit}s", + deparse_OnCommitClause(node->into->onCommit)); + + /* TABLESPACE clause */ + tmp = new_objtree("TABLESPACE %{tablespace}I"); + if (node->into->tableSpaceName) + append_string_object(tmp, "%{tablespace}s", "tablespace", + node->into->tableSpaceName); + else + { + append_null_object(tmp, "%{tablespace}I"); + append_not_present(tmp); + } + append_object_object(ret, "%{tablespace}s", tmp); + + /* Add the query */ + Assert(IsA(node->query, Query)); + append_string_object(ret, "AS %{query}s", "query", + pg_get_querydef((Query *) node->query, false)); + + /* Add a WITH NO DATA clause */ + tmp = new_objtree_VA("WITH NO DATA", 1, + "present", ObjTypeBool, + node->into->skipData ? true : false); + append_object_object(ret, "%{with_no_data}s", tmp); + + relation_close(relation, AccessShareLock); + + return ret; +} + +/* + * Deparse a CreateTrigStmt (CREATE TRIGGER) + * + * Given a trigger OID and the parse tree that created it, return an ObjTree + * representing the creation command. + * + * Verbose syntax + * CREATE %{or_replace}s %{constraint}s TRIGGER %{name}I %{time}s + * %{events: OR }s ON %{relation}D %{from_table}s %{constraint_attrs: }s + * %{referencing: }s FOR EACH %{for_each}s %{when}s EXECUTE PROCEDURE + * %{function}s + */ +static ObjTree * +deparse_CreateTrigStmt(Oid objectId, Node *parsetree) +{ + CreateTrigStmt *node = (CreateTrigStmt *) parsetree; + Relation pg_trigger; + HeapTuple trigTup; + Form_pg_trigger trigForm; + ObjTree *ret; + ObjTree *tmp_obj; + int tgnargs; + List *list = NIL; + List *events; + char *trigtiming; + char *tgoldtable; + char *tgnewtable; + Datum value; + bool isnull; + + pg_trigger = table_open(TriggerRelationId, AccessShareLock); + + trigTup = get_catalog_object_by_oid(pg_trigger, Anum_pg_trigger_oid, objectId); + trigForm = (Form_pg_trigger) GETSTRUCT(trigTup); + + trigtiming = node->timing == TRIGGER_TYPE_BEFORE ? "BEFORE" : + node->timing == TRIGGER_TYPE_AFTER ? "AFTER" : + node->timing == TRIGGER_TYPE_INSTEAD ? "INSTEAD OF" : + NULL; + if (!trigtiming) + elog(ERROR, "unrecognized trigger timing type %d", node->timing); + + ret = new_objtree_VA("CREATE %{or_replace}s %{constraint}s TRIGGER %{name}I %{time}s", 4, + "or_replace", ObjTypeString, node->replace ? "OR REPLACE" : "", + "constraint", ObjTypeString, node->isconstraint ? "CONSTRAINT" : "", + "name", ObjTypeString, node->trigname, + "time", ObjTypeString, trigtiming); + + /* + * Decode the events that the trigger fires for. The output is a list; in + * most cases it will just be a string with the event name, but when + * there's an UPDATE with a list of columns, we return a JSON object. + */ + events = NIL; + if (node->events & TRIGGER_TYPE_INSERT) + events = lappend(events, new_string_object("INSERT")); + if (node->events & TRIGGER_TYPE_DELETE) + events = lappend(events, new_string_object("DELETE")); + if (node->events & TRIGGER_TYPE_TRUNCATE) + events = lappend(events, new_string_object("TRUNCATE")); + if (node->events & TRIGGER_TYPE_UPDATE) + { + if (node->columns == NIL) + { + events = lappend(events, new_string_object("UPDATE")); + } + else + { + ObjTree *update; + ListCell *cell; + List *cols = NIL; + + /* + * Currently only UPDATE OF can be objects in the output JSON, but + * we add a "kind" element so that user code can distinguish + * possible future new event types. + */ + update = new_objtree_VA("UPDATE OF", 1, + "kind", ObjTypeString, "update_of"); + + foreach(cell, node->columns) + { + char *colname = strVal(lfirst(cell)); + + cols = lappend(cols, new_string_object(colname)); + } + + append_array_object(update, "%{columns:, }I", cols); + + events = lappend(events, new_object_object(update)); + } + } + append_array_object(ret, "%{events: OR }s", events); + + tmp_obj = new_objtree_for_qualname_id(RelationRelationId, + trigForm->tgrelid); + append_object_object(ret, "ON %{relation}D", tmp_obj); + + tmp_obj = new_objtree("FROM"); + if (trigForm->tgconstrrelid) + { + ObjTree *rel; + + rel = new_objtree_for_qualname_id(RelationRelationId, + trigForm->tgconstrrelid); + append_object_object(tmp_obj, "%{relation}D", rel); + } + else + append_not_present(tmp_obj); + append_object_object(ret, "%{from_table}s", tmp_obj); + + if (node->isconstraint) + { + if (!node->deferrable) + list = lappend(list, new_string_object("NOT")); + list = lappend(list, new_string_object("DEFERRABLE INITIALLY")); + if (node->initdeferred) + list = lappend(list, new_string_object("DEFERRED")); + else + list = lappend(list, new_string_object("IMMEDIATE")); + } + append_array_object(ret, "%{constraint_attrs: }s", list); + + list = NIL; + value = fastgetattr(trigTup, Anum_pg_trigger_tgoldtable, + RelationGetDescr(pg_trigger), &isnull); + if (!isnull) + tgoldtable = NameStr(*DatumGetName(value)); + else + tgoldtable = NULL; + value = fastgetattr(trigTup, Anum_pg_trigger_tgnewtable, + RelationGetDescr(pg_trigger), &isnull); + if (!isnull) + tgnewtable = NameStr(*DatumGetName(value)); + else + tgnewtable = NULL; + if (tgoldtable != NULL || tgnewtable != NULL) + { + list = lappend(list, new_string_object("REFERENCING")); + + if (tgoldtable != NULL) + { + list = lappend(list, new_string_object("OLD TABLE AS")); + list = lappend(list, new_string_object(tgoldtable)); + } + if (tgnewtable != NULL) + { + list = lappend(list, new_string_object("NEW TABLE AS")); + list = lappend(list, new_string_object(tgnewtable)); + } + } + append_array_object(ret, "%{referencing: }s", list); + + append_string_object(ret, "FOR EACH %{for_each}s", "for_each", + node->row ? "ROW" : "STATEMENT"); + + tmp_obj = new_objtree("WHEN"); + if (node->whenClause) + { + Node *whenClause; + + value = fastgetattr(trigTup, Anum_pg_trigger_tgqual, + RelationGetDescr(pg_trigger), &isnull); + if (isnull) + elog(ERROR, "null tgqual for trigger \"%s\"", + NameStr(trigForm->tgname)); + + whenClause = stringToNode(TextDatumGetCString(value)); + append_string_object(tmp_obj, "(%{clause}s)", "clause", + pg_get_trigger_whenclause(trigForm, + whenClause, + false)); + } + else + append_not_present(tmp_obj); + append_object_object(ret, "%{when}s", tmp_obj); + + tmp_obj = new_objtree_VA("%{funcname}D", 1, + "funcname", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, + trigForm->tgfoid)); + list = NIL; + tgnargs = trigForm->tgnargs; + if (tgnargs > 0) + { + char *p; + int i; + + value = fastgetattr(trigTup, Anum_pg_trigger_tgargs, + RelationGetDescr(pg_trigger), &isnull); + if (isnull) + elog(ERROR, "null tgargs for trigger \"%s\"", + NameStr(trigForm->tgname)); + p = (char *) VARDATA_ANY(DatumGetByteaPP(value)); + for (i = 0; i < tgnargs; i++) + { + list = lappend(list, new_string_object(p)); + /* advance p to next string embedded in tgargs */ + while (*p) + p++; + p++; + } + } + + append_format_string(tmp_obj, "("); + append_array_object(tmp_obj, "%{args:, }L", list); /* might be NIL */ + append_format_string(tmp_obj, ")"); + + append_object_object(ret, "EXECUTE FUNCTION %{function}s", tmp_obj); + + table_close(pg_trigger, AccessShareLock); + + return ret; +} + +/* + * Deparse a CreateUserMappingStmt (CREATE USER MAPPING) + * + * Given a User Mapping OID and the parse tree that created it, + * return an ObjTree representing the CREATE USER MAPPING command. + * + * Verbose syntax + * CREATE USER MAPPING FOR %{role}R SERVER %{server}I + */ +static ObjTree * +deparse_CreateUserMappingStmt(Oid objectId, Node *parsetree) +{ + CreateUserMappingStmt *node = (CreateUserMappingStmt *) parsetree; + ObjTree *ret; + Relation rel; + HeapTuple tp; + Form_pg_user_mapping form; + ForeignServer *server; + + rel = table_open(UserMappingRelationId, RowExclusiveLock); + + /* + * Lookup object in the catalog, so we don't have to deal with + * current_user and such. + */ + tp = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for user mapping with OID %u", + objectId); + + form = (Form_pg_user_mapping) GETSTRUCT(tp); + + server = GetForeignServer(form->umserver); + + ret = new_objtree_VA("CREATE USER MAPPING FOR %{role}R SERVER %{server}I %{generic_options}s", 3, + "role", ObjTypeObject, + new_objtree_for_role_id(form->umuser), + "server", ObjTypeString, server->servername, + "generic_options", ObjTypeObject, + deparse_FdwOptions(node->options, NULL, false)); + + ReleaseSysCache(tp); + table_close(rel, RowExclusiveLock); + return ret; +} + +/* + * deparse_AlterUserMapping + * + * Given a User Mapping OID and the parse tree that created it, return an + * ObjTree representing the alter command. + * + * Verbose syntax + * ALTER USER MAPPING FOR %{role}R SERVER %{server}I + */ +static ObjTree * +deparse_AlterUserMappingStmt(Oid objectId, Node *parsetree) +{ + AlterUserMappingStmt *node = (AlterUserMappingStmt *) parsetree; + ObjTree *ret; + Relation rel; + HeapTuple tp; + Form_pg_user_mapping form; + ForeignServer *server; + + rel = table_open(UserMappingRelationId, RowExclusiveLock); + + /* + * Lookup object in the catalog, so we don't have to deal with + * current_user and such. + */ + tp = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for user mapping with OID %u", + objectId); + + form = (Form_pg_user_mapping) GETSTRUCT(tp); + + server = GetForeignServer(form->umserver); + + ret = new_objtree_VA("ALTER USER MAPPING FOR %{role}R SERVER %{server}I %{generic_options}s", 3, + "role", ObjTypeObject, + new_objtree_for_role_id(form->umuser), + "server", ObjTypeString, server->servername, + "generic_options", ObjTypeObject, + deparse_FdwOptions(node->options, NULL, false)); + + ReleaseSysCache(tp); + table_close(rel, RowExclusiveLock); + return ret; +} + +/* + * Deparse an AlterStatsStmt (ALTER STATISTICS) + * + * Given a alter statistics OID and the parse tree that created it, return an + * ObjTree representing the alter command. + * + * Verbose syntax + * ALTER STATISTICS %{identity}D SET STATISTICS %{target}n + */ +static ObjTree * +deparse_AlterStatsStmt(Oid objectId, Node *parsetree) +{ + AlterStatsStmt *node = (AlterStatsStmt *) parsetree; + ObjTree *ret; + HeapTuple tp; + Form_pg_statistic_ext statform; + + if (!node->stxstattarget) + return NULL; + + /* Lookup object in the catalog */ + tp = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for statistic with OID %u", objectId); + + statform = (Form_pg_statistic_ext) GETSTRUCT(tp); + ret = new_objtree_VA("ALTER STATISTICS %{identity}D SET STATISTICS %{target}n", 2, + "identity", ObjTypeObject, + new_objtree_for_qualname(statform->stxnamespace, + NameStr(statform->stxname)), + "target", ObjTypeInteger, node->stxstattarget); + + ReleaseSysCache(tp); + return ret; +} + +/* + * Deparse a RefreshMatViewStmt (REFRESH MATERIALIZED VIEW) + * + * Given a materialized view OID and the parse tree that created it, return an + * ObjTree representing the refresh command. + * + * Verbose syntax + * REFRESH MATERIALIZED VIEW %{concurrently}s %{identity}D %{with_no_data}s + */ +static ObjTree * +deparse_RefreshMatViewStmt(Oid objectId, Node *parsetree) +{ + RefreshMatViewStmt *node = (RefreshMatViewStmt *) parsetree; + ObjTree *ret; + ObjTree *tmp; + + ret = new_objtree_VA("REFRESH MATERIALIZED VIEW %{concurrently}s %{identity}D", 2, + "concurrently", ObjTypeString, + node->concurrent ? "CONCURRENTLY" : "", + "identity", ObjTypeObject, + new_objtree_for_qualname_id(RelationRelationId, + objectId)); + + /* Add a WITH NO DATA clause */ + tmp = new_objtree_VA("WITH NO DATA", 1, + "present", ObjTypeBool, + node->skipData ? true : false); + append_object_object(ret, "%{with_no_data}s", tmp); + + return ret; +} + +/* + * Deparse a RenameStmt. + * + * Verbose syntax + * ALTER %{objtype}s %{if_exists}s %{identity}D RENAME TO %{newname}I + * OR + * ALTER POLICY %{if_exists}s %{policyname}I ON %{identity}D RENAME TO %{newname}I + * OR + * ALTER DOMAIN %{identity}D RENAME CONSTRAINT %{oldname}I TO %{newname}I + * OR + * ALTER TABLE %{only}s %{identity}D RENAME CONSTRAINT %{oldname}I TO %{newname}I + * OR + * ALTER TYPE %{identity}D RENAME ATTRIBUTE %{colname}I TO %{newname}I %{cascade}s + * OR + * ALTER %{objtype}s %{if_exists}s %{only}s %{identity}D RENAME COLUMN %{colname}I TO %{newname}I %{cascade}s + * OR + * ALTER %{objtype}s %{identity}s RENAME TO %{newname}I + * OR + * ALTER %{objtype}s %{identity}D USING %{index_method}s RENAME TO %{newname}I + * OR + * ALTER SCHEMA %{identity}I RENAME TO %{newname}I + * OR + * ALTER RULE %{rulename}I ON %{identity}D RENAME TO %{newname}I + * OR + * ALTER TRIGGER %{triggername}I ON %{identity}D RENAME TO %{newname}I + * OR + * ALTER %{objtype}s %{identity}D RENAME TO %{newname}I + */ +static ObjTree * +deparse_RenameStmt(ObjectAddress address, Node *parsetree) +{ + RenameStmt *node = (RenameStmt *) parsetree; + ObjTree *ret; + Relation relation; + Oid schemaId; + + /* + * In an ALTER .. RENAME command, we don't have the original name of the + * object in system catalogs: since we inspect them after the command has + * executed, the old name is already gone. Therefore, we extract it from + * the parse node. Note we still extract the schema name from the catalog + * (it might not be present in the parse node); it cannot possibly have + * changed anyway. + */ + switch (node->renameType) + { + case OBJECT_TABLE: + case OBJECT_INDEX: + case OBJECT_SEQUENCE: + case OBJECT_VIEW: + case OBJECT_MATVIEW: + case OBJECT_FOREIGN_TABLE: + relation = relation_open(address.objectId, AccessShareLock); + schemaId = RelationGetNamespace(relation); + ret = new_objtree_VA("ALTER %{objtype}s %{if_exists}s %{identity}D RENAME TO %{newname}I", 4, + "objtype", ObjTypeString, + stringify_objtype(node->renameType, false), + "if_exists", ObjTypeString, + node->missing_ok ? "IF EXISTS" : "", + "identity", ObjTypeObject, + new_objtree_for_qualname(schemaId, + node->relation->relname), + "newname", ObjTypeString, + node->newname); + relation_close(relation, AccessShareLock); + break; + + case OBJECT_POLICY: + { + HeapTuple polTup; + Form_pg_policy polForm; + Relation pg_policy; + ScanKeyData key; + SysScanDesc scan; + + pg_policy = relation_open(PolicyRelationId, AccessShareLock); + ScanKeyInit(&key, Anum_pg_policy_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(address.objectId)); + scan = systable_beginscan(pg_policy, PolicyOidIndexId, true, + NULL, 1, &key); + polTup = systable_getnext(scan); + if (!HeapTupleIsValid(polTup)) + elog(ERROR, "cache lookup failed for policy with OID %u", + address.objectId); + polForm = (Form_pg_policy) GETSTRUCT(polTup); + + ret = new_objtree_VA("ALTER POLICY %{if_exists}s %{policyname}I ON %{identity}D RENAME TO %{newname}I", 4, + "if_exists", ObjTypeString, + node->missing_ok ? "IF EXISTS" : "", + "policyname", ObjTypeString, + node->subname, + "identity", ObjTypeObject, + new_objtree_for_qualname_id(RelationRelationId, + polForm->polrelid), + "newname", ObjTypeString, + node->newname); + systable_endscan(scan); + relation_close(pg_policy, AccessShareLock); + } + break; + + case OBJECT_DOMCONSTRAINT: + { + HeapTuple domtup; + HeapTuple consttup; + Form_pg_constraint constform; + Form_pg_type domform; + + /* Get domain id from the constraint */ + consttup = SearchSysCache1(CONSTROID, + ObjectIdGetDatum(address.objectId)); + if (!HeapTupleIsValid(consttup)) + elog(ERROR, "cache lookup failed for constraint with OID %u", + address.objectId); + constform = (Form_pg_constraint) GETSTRUCT(consttup); + + domtup = SearchSysCache1(TYPEOID, + ObjectIdGetDatum(constform->contypid)); + if (!HeapTupleIsValid(domtup)) + elog(ERROR, "cache lookup failed for domain with OID %u", + constform->contypid); + ReleaseSysCache(consttup); + + domform = (Form_pg_type) GETSTRUCT(domtup); + ret = new_objtree_VA("ALTER DOMAIN %{identity}D RENAME CONSTRAINT %{oldname}I TO %{newname}I", 3, + "identity", ObjTypeObject, + new_objtree_for_qualname(domform->typnamespace, + NameStr(domform->typname)), + "oldname", ObjTypeString, node->subname, + "newname", ObjTypeString, node->newname); + ReleaseSysCache(domtup); + } + break; + + case OBJECT_TABCONSTRAINT: + { + HeapTuple constrtup; + Form_pg_constraint constform; + + constrtup = SearchSysCache1(CONSTROID, + ObjectIdGetDatum(address.objectId)); + if (!HeapTupleIsValid(constrtup)) + elog(ERROR, "cache lookup failed for constraint with OID %u", + address.objectId); + constform = (Form_pg_constraint) GETSTRUCT(constrtup); + + ret = new_objtree_VA("ALTER TABLE %{only}s %{identity}D RENAME CONSTRAINT %{oldname}I TO %{newname}I", 4, + "only", ObjTypeString, + node->relation->inh ? "" : "ONLY", + "identity", ObjTypeObject, + new_objtree_for_qualname_id(RelationRelationId, + constform->conrelid), + "oldname", ObjTypeString, node->subname, + "newname", ObjTypeString, node->newname); + ReleaseSysCache(constrtup); + } + break; + + case OBJECT_ATTRIBUTE: + case OBJECT_COLUMN: + relation = relation_open(address.objectId, AccessShareLock); + schemaId = RelationGetNamespace(relation); + + if (node->renameType == OBJECT_ATTRIBUTE) + ret = new_objtree_VA("ALTER TYPE %{identity}D RENAME ATTRIBUTE %{colname}I", 2, + "identity", ObjTypeObject, + new_objtree_for_qualname(schemaId, + node->relation->relname), + "colname", ObjTypeString, node->subname); + else + { + ret = new_objtree_VA("ALTER %{objtype}s", 1, + "objtype", ObjTypeString, + stringify_objtype(node->relationType, false)); + + /* Composite types do not support IF EXISTS */ + if (node->renameType == OBJECT_COLUMN) + append_string_object(ret, "%{if_exists}s", + "if_exists", + node->missing_ok ? "IF EXISTS" : ""); + if (!node->relation->inh) + append_string_object(ret, "%{only}s", + "only", + "ONLY"); + append_object_object(ret, "%{identity}D", + new_objtree_for_qualname(schemaId, + node->relation->relname)); + append_string_object(ret, "RENAME COLUMN %{colname}I", + "colname", node->subname); + } + + append_string_object(ret, "TO %{newname}I", "newname", + node->newname); + + if (node->renameType == OBJECT_ATTRIBUTE) + append_object_object(ret, "%{cascade}s", + new_objtree_VA("CASCADE", 1, + "present", ObjTypeBool, + node->behavior == DROP_CASCADE)); + + relation_close(relation, AccessShareLock); + break; + + case OBJECT_AGGREGATE: + case OBJECT_FUNCTION: + case OBJECT_ROUTINE: + { + char *ident; + HeapTuple proctup; + Form_pg_proc procform; + List *identity; + + Assert(IsA(node->object, ObjectWithArgs)); + identity = ((ObjectWithArgs *) node->object)->objname; + + proctup = SearchSysCache1(PROCOID, + ObjectIdGetDatum(address.objectId)); + if (!HeapTupleIsValid(proctup)) + elog(ERROR, "cache lookup failed for procedure with OID %u", + address.objectId); + procform = (Form_pg_proc) GETSTRUCT(proctup); + + /* XXX does this work for ordered-set aggregates? */ + ident = psprintf("%s%s", + quote_qualified_identifier(get_namespace_name(procform->pronamespace), + strVal(llast(identity))), + format_procedure_args(address.objectId, true)); + + ret = new_objtree_VA("ALTER %{objtype}s %{identity}s RENAME TO %{newname}I", 3, + "objtype", ObjTypeString, + stringify_objtype(node->renameType, false), + "identity", ObjTypeString, ident, + "newname", ObjTypeString, node->newname); + + ReleaseSysCache(proctup); + } + break; + + case OBJECT_OPCLASS: + { + HeapTuple opcTup; + Form_pg_opclass opcForm; + List *oldnames; + char *schemaname; + char *opcname; + + opcTup = SearchSysCache1(CLAOID, ObjectIdGetDatum(address.objectId)); + if (!HeapTupleIsValid(opcTup)) + elog(ERROR, "cache lookup failed for opclass with OID %u", + address.objectId); + + opcForm = (Form_pg_opclass) GETSTRUCT(opcTup); + + oldnames = list_copy_tail((List *) node->object, 1); + + /* Deconstruct the name list */ + DeconstructQualifiedName(oldnames, &schemaname, &opcname); + + ret = new_objtree_VA("ALTER %{objtype}s %{identity}D USING %{index_method}s RENAME TO %{newname}I", 4, + "objtype", ObjTypeString, + stringify_objtype(node->renameType, false), + "identity", ObjTypeObject, + new_objtree_for_qualname(opcForm->opcnamespace, + opcname), + "index_method", ObjTypeString, + get_am_name(opcForm->opcmethod), + "newname", ObjTypeString, node->newname); + + ReleaseSysCache(opcTup); + } + break; + + case OBJECT_OPFAMILY: + { + HeapTuple opfTup; + HeapTuple amTup; + Form_pg_opfamily opfForm; + Form_pg_am amForm; + List *oldnames; + char *schemaname; + char *opfname; + + opfTup = SearchSysCache1(OPFAMILYOID, address.objectId); + if (!HeapTupleIsValid(opfTup)) + elog(ERROR, "cache lookup failed for operator family with OID %u", + address.objectId); + opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup); + + amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod)); + if (!HeapTupleIsValid(amTup)) + elog(ERROR, "cache lookup failed for access method with OID %u", + opfForm->opfmethod); + + amForm = (Form_pg_am) GETSTRUCT(amTup); + + oldnames = list_copy_tail((List *) node->object, 1); + + /* Deconstruct the name list */ + DeconstructQualifiedName(oldnames, &schemaname, &opfname); + + ret = new_objtree_VA("ALTER %{objtype}s %{identity}D USING %{index_method}s RENAME TO %{newname}I", 4, + "objtype", ObjTypeString, + stringify_objtype(node->renameType, false), + "identity", ObjTypeObject, + new_objtree_for_qualname(opfForm->opfnamespace, + opfname), + "index_method", ObjTypeString, + NameStr(amForm->amname), + "newname", ObjTypeString, node->newname); + + ReleaseSysCache(amTup); + ReleaseSysCache(opfTup); + } + break; + + case OBJECT_SCHEMA: + ret = new_objtree_VA("ALTER SCHEMA %{identity}I RENAME TO %{newname}I", 2, + "identity", ObjTypeString, node->subname, + "newname", ObjTypeString, node->newname); + break; + + case OBJECT_FDW: + case OBJECT_LANGUAGE: + case OBJECT_FOREIGN_SERVER: + case OBJECT_PUBLICATION: + ret = new_objtree_VA("ALTER %{objtype}s %{identity}s RENAME TO %{newname}I", 3, + "objtype", ObjTypeString, + stringify_objtype(node->renameType, false), + "identity", ObjTypeString, + strVal(castNode(String, node->object)), + "newname", ObjTypeString, node->newname); + break; + + case OBJECT_RULE: + { + HeapTuple rewrTup; + Form_pg_rewrite rewrForm; + Relation pg_rewrite; + + pg_rewrite = relation_open(RewriteRelationId, AccessShareLock); + rewrTup = get_catalog_object_by_oid(pg_rewrite, Anum_pg_rewrite_oid, address.objectId); + rewrForm = (Form_pg_rewrite) GETSTRUCT(rewrTup); + + ret = new_objtree_VA("ALTER RULE %{rulename}I ON %{identity}D RENAME TO %{newname}I", 3, + "rulename", ObjTypeString, node->subname, + "identity", ObjTypeObject, + new_objtree_for_qualname_id(RelationRelationId, + rewrForm->ev_class), + "newname", ObjTypeString, node->newname); + relation_close(pg_rewrite, AccessShareLock); + } + break; + + case OBJECT_TRIGGER: + { + HeapTuple trigTup; + Form_pg_trigger trigForm; + Relation pg_trigger; + + pg_trigger = relation_open(TriggerRelationId, AccessShareLock); + trigTup = get_catalog_object_by_oid(pg_trigger, get_object_attnum_oid(address.classId), address.objectId); + trigForm = (Form_pg_trigger) GETSTRUCT(trigTup); + + ret = new_objtree_VA("ALTER TRIGGER %{triggername}I ON %{identity}D RENAME TO %{newname}I", 3, + "triggername", ObjTypeString, node->subname, + "identity", ObjTypeObject, + new_objtree_for_qualname_id(RelationRelationId, + trigForm->tgrelid), + "newname", ObjTypeString, node->newname); + + relation_close(pg_trigger, AccessShareLock); + } + break; + + case OBJECT_COLLATION: + case OBJECT_STATISTIC_EXT: + case OBJECT_TYPE: + case OBJECT_CONVERSION: + case OBJECT_DOMAIN: + case OBJECT_TSDICTIONARY: + case OBJECT_TSPARSER: + case OBJECT_TSTEMPLATE: + case OBJECT_TSCONFIGURATION: + { + HeapTuple objTup; + Relation catalog; + Datum objnsp; + bool isnull; + AttrNumber Anum_namespace; + List *identity = castNode(List, node->object); + + /* Obtain object tuple */ + catalog = relation_open(address.classId, AccessShareLock); + objTup = get_catalog_object_by_oid(catalog, get_object_attnum_oid(address.classId), address.objectId); + + /* Obtain namespace */ + Anum_namespace = get_object_attnum_namespace(address.classId); + objnsp = heap_getattr(objTup, Anum_namespace, + RelationGetDescr(catalog), &isnull); + if (isnull) + elog(ERROR, "invalid NULL namespace"); + + ret = new_objtree_VA("ALTER %{objtype}s %{identity}D RENAME TO %{newname}I", 3, + "objtype", ObjTypeString, + stringify_objtype(node->renameType, false), + "identity", ObjTypeObject, + new_objtree_for_qualname(DatumGetObjectId(objnsp), + strVal(llast(identity))), + "newname", ObjTypeString, node->newname); + relation_close(catalog, AccessShareLock); + } + break; + + default: + elog(ERROR, "unsupported object type %d", node->renameType); + } + + return ret; +} + +/* + * Deparse a AlterObjectDependsStmt (ALTER ... DEPENDS ON ...). + * + * Verbose syntax + * ALTER INDEX %{identity}D %{no}s DEPENDS ON EXTENSION %{newname}I + */ +static ObjTree * +deparse_AlterDependStmt(Oid objectId, Node *parsetree) +{ + AlterObjectDependsStmt *node = (AlterObjectDependsStmt *) parsetree; + ObjTree *ret = NULL; + + if (node->objectType == OBJECT_INDEX) + { + ObjTree *qualified; + Relation relation = relation_open(objectId, AccessShareLock); + + qualified = new_objtree_for_qualname(relation->rd_rel->relnamespace, + node->relation->relname); + relation_close(relation, AccessShareLock); + + ret = new_objtree_VA("ALTER INDEX %{identity}D %{no}s DEPENDS ON EXTENSION %{newname}I", 3, + "identity", ObjTypeObject, qualified, + "no", ObjTypeString, + node->remove ? "NO" : "", + "newname", ObjTypeString, strVal(node->extname)); + } + else + elog(LOG, "unrecognized node type in deparse command: %d", + (int) nodeTag(parsetree)); + + return ret; +} + +/* + * Deparse a RuleStmt (CREATE RULE). + * + * Given a rule OID and the parse tree that created it, return an ObjTree + * representing the rule command. + * + * Verbose syntax + * CREATE RULE %{or_replace}s %{identity}I AS ON %{event}s TO %{table}D + * %{where_clause}s DO %{instead}s %{actions:, }s + */ +static ObjTree * +deparse_RuleStmt(Oid objectId, Node *parsetree) +{ + RuleStmt *node = (RuleStmt *) parsetree; + ObjTree *ret; + ObjTree *tmp; + Relation pg_rewrite; + Form_pg_rewrite rewrForm; + HeapTuple rewrTup; + SysScanDesc scan; + ScanKeyData key; + Datum ev_qual; + Datum ev_actions; + bool isnull; + char *qual; + List *actions; + List *list = NIL; + ListCell *cell; + char *event_str; + + pg_rewrite = table_open(RewriteRelationId, AccessShareLock); + ScanKeyInit(&key, + Anum_pg_rewrite_oid, + BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(objectId)); + + scan = systable_beginscan(pg_rewrite, RewriteOidIndexId, true, + NULL, 1, &key); + rewrTup = systable_getnext(scan); + if (!HeapTupleIsValid(rewrTup)) + elog(ERROR, "cache lookup failed for rewrite rule with OID %u", + objectId); + + rewrForm = (Form_pg_rewrite) GETSTRUCT(rewrTup); + + event_str = node->event == CMD_SELECT ? "SELECT" : + node->event == CMD_UPDATE ? "UPDATE" : + node->event == CMD_DELETE ? "DELETE" : + node->event == CMD_INSERT ? "INSERT" : NULL; + Assert(event_str != NULL); + + ev_qual = heap_getattr(rewrTup, Anum_pg_rewrite_ev_qual, + RelationGetDescr(pg_rewrite), &isnull); + ev_actions = heap_getattr(rewrTup, Anum_pg_rewrite_ev_action, + RelationGetDescr(pg_rewrite), &isnull); + + pg_get_ruledef_detailed(ev_qual, ev_actions, &qual, &actions); + + tmp = new_objtree(""); + if (qual) + append_string_object(tmp, "WHERE %{clause}s", "clause", qual); + else + { + append_null_object(tmp, "WHERE %{clause}s"); + append_not_present(tmp); + } + + if (actions == NIL) + list = lappend(list, new_string_object("NOTHING")); + else + { + foreach(cell, actions) + list = lappend(list, new_string_object(lfirst(cell))); + } + + ret = new_objtree_VA("CREATE RULE %{or_replace}s %{identity}I AS ON %{event}s TO %{table}D %{where_clause}s DO %{instead}s", 6, + "or_replace", ObjTypeString, + node->replace ? "OR REPLACE" : "", + "identity", ObjTypeString, node->rulename, + "event", ObjTypeString, event_str, + "table", ObjTypeObject, + new_objtree_for_qualname_id(RelationRelationId, + rewrForm->ev_class), + "where_clause", ObjTypeObject, tmp, + "instead", ObjTypeString, + node->instead ? "INSTEAD" : "ALSO"); + + if (list_length(list) > 1) + append_array_object(ret, "(%{actions:; }s)", list); + else + append_array_object(ret, "%{actions:; }s", list); + + systable_endscan(scan); + table_close(pg_rewrite, AccessShareLock); + + return ret; +} + +/* + * Deparse a CreateTransformStmt (CREATE TRANSFORM). + * + * Given a transform OID and the parse tree that created it, return an ObjTree + * representing the CREATE TRANSFORM command. + * + * Verbose syntax + * CREATE %{or_replace}s TRANSFORM FOR %{typename}D LANGUAGE %{language}I + * ( FROM SQL WITH FUNCTION %{signature}s, TO SQL WITH FUNCTION + * %{signature_tof}s ) + */ +static ObjTree * +deparse_CreateTransformStmt(Oid objectId, Node *parsetree) +{ + CreateTransformStmt *node = (CreateTransformStmt *) parsetree; + ObjTree *ret; + ObjTree *signature; + HeapTuple trfTup; + HeapTuple langTup; + HeapTuple procTup; + Form_pg_transform trfForm; + Form_pg_language langForm; + Form_pg_proc procForm; + int i; + + /* Get the pg_transform tuple */ + trfTup = SearchSysCache1(TRFOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(trfTup)) + elog(ERROR, "cache lookup failed for transform with OID %u", + objectId); + trfForm = (Form_pg_transform) GETSTRUCT(trfTup); + + /* Get the corresponding pg_language tuple */ + langTup = SearchSysCache1(LANGOID, trfForm->trflang); + if (!HeapTupleIsValid(langTup)) + elog(ERROR, "cache lookup failed for language with OID %u", + trfForm->trflang); + langForm = (Form_pg_language) GETSTRUCT(langTup); + + ret = new_objtree_VA("CREATE %{or_replace}s TRANSFORM FOR %{typename}D LANGUAGE %{language}I", 3, + "or_replace", ObjTypeString, + node->replace ? "OR REPLACE" : "", + "typename", ObjTypeObject, + new_objtree_for_qualname_id(TypeRelationId, + trfForm->trftype), + "language", ObjTypeString, + NameStr(langForm->lanname)); + + /* Deparse the transform_element_list */ + if (OidIsValid(trfForm->trffromsql)) + { + List *params = NIL; + + /* Get the pg_proc tuple for the FROM FUNCTION */ + procTup = SearchSysCache1(PROCOID, trfForm->trffromsql); + if (!HeapTupleIsValid(procTup)) + elog(ERROR, "cache lookup failed for function with OID %u", + trfForm->trffromsql); + procForm = (Form_pg_proc) GETSTRUCT(procTup); + + /* + * CREATE TRANSFORM does not change function signature so we can use + * catalog to get input type Oids. + */ + for (i = 0; i < procForm->pronargs; i++) + { + ObjTree *param_obj; + + param_obj = new_objtree_VA("%{type}T", 1, + "type", ObjTypeObject, + new_objtree_for_type(procForm->proargtypes.values[i], -1)); + params = lappend(params, new_object_object(param_obj)); + } + + signature = new_objtree_VA("%{identity}D (%{arguments:, }s)", 2, + "identity", ObjTypeObject, + new_objtree_for_qualname(procForm->pronamespace, + NameStr(procForm->proname)), + "arguments", ObjTypeArray, params); + + append_object_object(ret, "(FROM SQL WITH FUNCTION %{signature}s", + signature); + ReleaseSysCache(procTup); + } + if (OidIsValid(trfForm->trftosql)) + { + List *params = NIL; + + /* Append a ',' if trffromsql is present, else append '(' */ + append_format_string(ret, OidIsValid(trfForm->trffromsql) ? "," : "("); + + /* Get the pg_proc tuple for the TO FUNCTION */ + procTup = SearchSysCache1(PROCOID, trfForm->trftosql); + if (!HeapTupleIsValid(procTup)) + elog(ERROR, "cache lookup failed for function with OID %u", + trfForm->trftosql); + procForm = (Form_pg_proc) GETSTRUCT(procTup); + + /* + * CREATE TRANSFORM does not change function signature so we can use + * catalog to get input type Oids. + */ + for (i = 0; i < procForm->pronargs; i++) + { + ObjTree *param_obj = new_objtree(""); + + param_obj = new_objtree_VA("%{type}T", 1, + "type", ObjTypeObject, + new_objtree_for_type(procForm->proargtypes.values[i], -1)); + params = lappend(params, new_object_object(param_obj)); + } + + signature = new_objtree_VA("%{identity}D (%{arguments:, }s)", 2, + "identity", ObjTypeObject, + new_objtree_for_qualname(procForm->pronamespace, + NameStr(procForm->proname)), + "arguments", ObjTypeArray, params); + + append_object_object(ret, "TO SQL WITH FUNCTION %{signature_tof}s", + signature); + ReleaseSysCache(procTup); + } + + append_format_string(ret, ")"); + + ReleaseSysCache(langTup); + ReleaseSysCache(trfTup); + return ret; +} + +/* + * Deparse a CommentStmt when it pertains to a constraint. + * + * Verbose syntax + * COMMENT ON CONSTRAINT %{identity}s ON [DOMAIN] %{parentobj}s IS %{comment}s + */ +static ObjTree * +deparse_CommentOnConstraintSmt(Oid objectId, Node *parsetree) +{ + CommentStmt *node = (CommentStmt *) parsetree; + ObjTree *ret; + HeapTuple constrTup; + Form_pg_constraint constrForm; + ObjectAddress addr; + + Assert(node->objtype == OBJECT_TABCONSTRAINT || node->objtype == OBJECT_DOMCONSTRAINT); + + constrTup = SearchSysCache1(CONSTROID, objectId); + if (!HeapTupleIsValid(constrTup)) + elog(ERROR, "cache lookup failed for constraint with OID %u", objectId); + constrForm = (Form_pg_constraint) GETSTRUCT(constrTup); + + if (OidIsValid(constrForm->conrelid)) + ObjectAddressSet(addr, RelationRelationId, constrForm->conrelid); + else + ObjectAddressSet(addr, TypeRelationId, constrForm->contypid); + + ret = new_objtree_VA("COMMENT ON CONSTRAINT %{identity}s ON %{domain}s %{parentobj}s", 3, + "identity", ObjTypeString, pstrdup(NameStr(constrForm->conname)), + "domain", ObjTypeString, + (node->objtype == OBJECT_DOMCONSTRAINT) ? "DOMAIN" : "", + "parentobj", ObjTypeString, + getObjectIdentity(&addr, false)); + + /* Add the comment clause */ + append_literal_or_null(ret, "IS %{comment}s", node->comment); + + ReleaseSysCache(constrTup); + return ret; +} + +/* + * Deparse an CommentStmt (COMMENT ON ...). + * + * Given the object address and the parse tree that created it, return an + * ObjTree representing the comment command. + * + * Verbose syntax + * COMMENT ON %{objtype}s %{identity}s IS %{comment}s + */ +static ObjTree * +deparse_CommentStmt(ObjectAddress address, Node *parsetree) +{ + CommentStmt *node = (CommentStmt *) parsetree; + ObjTree *ret; + char *identity; + + /* Comment on subscription is not supported */ + if (node->objtype == OBJECT_SUBSCRIPTION) + return NULL; + + /* + * Constraints are sufficiently different that it is easier to handle them + * separately. + */ + if (node->objtype == OBJECT_DOMCONSTRAINT || + node->objtype == OBJECT_TABCONSTRAINT) + { + Assert(address.classId == ConstraintRelationId); + return deparse_CommentOnConstraintSmt(address.objectId, parsetree); + } + + ret = new_objtree_VA("COMMENT ON %{objtype}s", 1, + "objtype", ObjTypeString, + (char *) stringify_objtype(node->objtype, false)); + + /* + * Add the object identity clause. For zero argument aggregates we need + * to add the (*) bit; in all other cases we can just use + * getObjectIdentity. + * + * XXX shouldn't we instead fix the object identities for zero-argument + * aggregates? + */ + if (node->objtype == OBJECT_AGGREGATE) + { + HeapTuple procTup; + Form_pg_proc procForm; + + procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(address.objectId)); + if (!HeapTupleIsValid(procTup)) + elog(ERROR, "cache lookup failed for procedure with OID %u", + address.objectId); + procForm = (Form_pg_proc) GETSTRUCT(procTup); + if (procForm->pronargs == 0) + identity = psprintf("%s(*)", + quote_qualified_identifier(get_namespace_name(procForm->pronamespace), + NameStr(procForm->proname))); + else + identity = getObjectIdentity(&address, false); + ReleaseSysCache(procTup); + } + else + identity = getObjectIdentity(&address, false); + + append_string_object(ret, "%{identity}s", "identity", identity); + + /* Add the comment clause; can be either NULL or a quoted literal. */ + append_literal_or_null(ret, "IS %{comment}s", node->comment); + + return ret; +} + +/* + * Deparse a SECURITY LABEL command. + * + * Verbose syntax + * SECURITY LABEL FOR %{provider}s ON %{object_type_name}s %{identity}s IS %{label}s + */ +static ObjTree * +deparse_SecLabelStmt(CollectedCommand *cmd) +{ + ObjTree *ret; + SecLabelStmt *node = (SecLabelStmt *) cmd->parsetree; + + /* Don't deparse sql commands generated while creating extension */ + if (cmd->in_extension) + return NULL; + + Assert(cmd->d.seclabel.provider); + + ret = new_objtree_VA("SECURITY LABEL FOR %{provider}s ON %{objtype}s %{identity}s", 3, + "provider", ObjTypeString, cmd->d.seclabel.provider, + "objtype", ObjTypeString, stringify_objtype(node->objtype, false), + "identity", ObjTypeString, getObjectIdentity(&(cmd->d.seclabel.address), false)); + + /* Add the label clause; can be either NULL or a quoted literal. */ + append_literal_or_null(ret, "IS %{label}s", node->label); + + return ret; +} + +/* + * Deparse a CreateAmStmt (CREATE ACCESS METHOD). + * + * Given an access method OID and the parse tree that created it, return an + * ObjTree representing the CREATE ACCESS METHOD command. + * + * Verbose syntax + * CREATE ACCESS METHOD %{identity}I TYPE %{am_type}s HANDLER %{handler}D + */ +static ObjTree * +deparse_CreateAmStmt(Oid objectId, Node *parsetree) +{ + ObjTree *ret; + HeapTuple amTup; + Form_pg_am amForm; + char *amtype; + + amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(amTup)) + elog(ERROR, "cache lookup failed for access method with OID %u", + objectId); + amForm = (Form_pg_am) GETSTRUCT(amTup); + + switch (amForm->amtype) + { + case 'i': + amtype = "INDEX"; + break; + case 't': + amtype = "TABLE"; + break; + default: + elog(ERROR, "invalid type %c for access method", amForm->amtype); + } + + ret = new_objtree_VA("CREATE ACCESS METHOD %{identity}I TYPE %{am_type}s HANDLER %{handler}D", 3, + "identity", ObjTypeString, + NameStr(amForm->amname), + "am_type", ObjTypeString, amtype, + "handler", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, + amForm->amhandler)); + + ReleaseSysCache(amTup); + + return ret; +} + +/* + * Deparse the publication column list. + * + * Given a tuple of pg_publication_rel, return an objTree that represent the + * column names. + */ +static ObjTree * +deparse_PublicationRelationColumnList(HeapTuple pubreltup) +{ + bool isnull; + List *collist = NIL; + ObjTree *columnlist; + Datum attrsdatum; + + attrsdatum = SysCacheGetAttr(PUBLICATIONRELMAP, pubreltup, + Anum_pg_publication_rel_prattrs, + &isnull); + + columnlist = new_objtree(""); + if (!isnull) + { + Form_pg_publication_rel pubrel; + ArrayType *arr; + int nelems; + int16 *elems; + + arr = DatumGetArrayTypeP(attrsdatum); + nelems = ARR_DIMS(arr)[0]; + elems = (int16 *) ARR_DATA_PTR(arr); + + pubrel = (Form_pg_publication_rel) GETSTRUCT(pubreltup); + + for (int i = 0; i < nelems; i++) + { + char *colname = get_attname(pubrel->prrelid, elems[i], false); + collist = lappend(collist, new_string_object(colname)); + } + + append_array_object(columnlist, "(%{cols:, }s)", collist); + } + else + append_not_present(columnlist); + + return columnlist; +} + +/* + * Deparse the publication where clause. + * + * Given a tuple of pg_publication_rel, return a objTree that represent the + * publication where clause. + */ +static ObjTree * +deparse_PublicationRelationWhereClause(HeapTuple pubreltup) +{ + Datum qualdatum; + ObjTree *whereclause; + bool isnull; + + qualdatum = SysCacheGetAttr(PUBLICATIONRELMAP, pubreltup, + Anum_pg_publication_rel_prqual, &isnull); + + whereclause = new_objtree(""); + if (!isnull) + { + Form_pg_publication_rel pubrel; + Node *qualnode; + List *context; + char *qualstr; + Oid relid; + + pubrel = (Form_pg_publication_rel) GETSTRUCT(pubreltup); + relid = pubrel->prrelid; + + context = deparse_context_for(get_rel_name(relid), relid); + qualnode = stringToNode(TextDatumGetCString(qualdatum)); + qualstr = deparse_expression(qualnode, context, false, false); + + append_string_object(whereclause, "WHERE %{where}s", "where", qualstr); + } + else + append_not_present(whereclause); + + return whereclause; +} + +/* + * Subroutine for CREATE PUBLICATION deparsing. + * + * Deal with all the publication table information including the where clause + * and column list. + */ +static List * +deparse_PublicationRelationDefs(Oid puboid) +{ + Relation pubrelsrel; + ScanKeyData scankey; + SysScanDesc scan; + HeapTuple tup; + List *reldefs = NIL; + + pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock); + + ScanKeyInit(&scankey, + Anum_pg_publication_rel_prpubid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(puboid)); + + scan = systable_beginscan(pubrelsrel, PublicationRelPrpubidIndexId, + true, NULL, 1, &scankey); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + Form_pg_publication_rel pubrel; + Oid relid; + ObjTree *columnlist; + ObjTree *whereclause; + ObjTree *reldef; + + pubrel = (Form_pg_publication_rel) GETSTRUCT(tup); + relid = pubrel->prrelid; + + reldef = new_objtree_VA("%{tablename}D", 1, + "tablename", ObjTypeObject, + new_objtree_for_qualname_id(RelationRelationId, relid)); + + columnlist = deparse_PublicationRelationColumnList(tup); + append_object_object(reldef, "%{column_list}s", columnlist); + + whereclause = deparse_PublicationRelationWhereClause(tup); + append_object_object(reldef, "%{where_clause}s", whereclause); + + reldefs = lappend(reldefs, new_object_object(reldef)); + } + + systable_endscan(scan); + table_close(pubrelsrel, AccessShareLock); + + return reldefs; +} + +/* + * Deparse a CreatePublicationStmt (CREATE PUBLICATION). + * + * Given an publication OID and the parse tree that created it, return an + * ObjTree representing the CREATE PUBLICATION command. * * Verbose syntax - * ALTER %{objtype}s %{if_exists}s %{identity}D RENAME TO %{newname}I + * CREATE PUBLICATION %{identity}I %{for_tables}s %{for_schemas}s %{with_clause}s * OR - * ALTER TABLE %{only}s %{identity}D RENAME CONSTRAINT %{oldname}I TO %{newname}I + * CREATE PUBLICATION %{identity}I FOR ALL TABLES %{with_clause}s + */ +static ObjTree * +deparse_CreatePublicationStmt(Oid objectId, Node *parsetree) +{ + CreatePublicationStmt *node = (CreatePublicationStmt *) parsetree; + ObjTree *createPub; + ObjTree *tmp; + HeapTuple pubTup; + Form_pg_publication pubForm; + List *list = NIL; + ListCell *cell; + + pubTup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(pubTup)) + elog(ERROR, "cache lookup failed for access method with OID %u", + objectId); + pubForm = (Form_pg_publication) GETSTRUCT(pubTup); + + createPub = new_objtree_VA("CREATE PUBLICATION %{identity}s", 1, + "identity", ObjTypeString, + NameStr(pubForm->pubname)); + + if (node->for_all_tables) + append_format_string(createPub, "FOR ALL TABLES"); + else + { + List *oldschemaids; + StringInfoData for_schemas; + + /* FOR TABLES t1,t2,... */ + tmp = new_objtree("FOR TABLE"); + + list = deparse_PublicationRelationDefs(objectId); + if (list != NIL) + append_array_object(tmp, "%{tables:, }s", list); + else + append_not_present(tmp); + + append_object_object(createPub, "%{for_tables}s", tmp); + + /* FOR TABLES IN SCHEMA s1,s2,... */ + initStringInfo(&for_schemas); + if (list != NIL) + appendStringInfoString(&for_schemas, ", "); + else + appendStringInfoString(&for_schemas, "FOR "); + appendStringInfoString(&for_schemas, "TABLES IN SCHEMA"); + + tmp = new_objtree(for_schemas.data); + list = NIL; + oldschemaids = GetPublicationSchemas(pubForm->oid); + foreach(cell, oldschemaids) + { + Oid schemaid = lfirst_oid(cell); + list = lappend(list, new_string_object(get_namespace_name(schemaid))); + } + + if (list != NIL) + append_array_object(tmp, "%{schemas:, }I", list); + else + append_not_present(tmp); + + append_object_object(createPub, "%{for_schemas}s", tmp); + } + + /* WITH clause */ + tmp = new_objtree("WITH"); + + list = NIL; + foreach(cell, node->options) + { + ObjTree *tmp_obj; + DefElem *opt = (DefElem *) lfirst(cell); + + tmp_obj = deparse_DefElem(opt, false); + list = lappend(list, new_object_object(tmp_obj)); + } + + if (list != NIL) + append_array_object(tmp, "(%{with:, }s)", list); + else + append_not_present(tmp); + + append_object_object(createPub, "%{with_clause}s", tmp); + + ReleaseSysCache(pubTup); + + return createPub; +} + +/* + * Deparse a AlterPublicationStmt (ALTER PUBLICATION). + * + * Given an publication relation OID or publication schema OID and the parse + * tree that added it, return an ObjTree representing the ALTER PUBLICATION + * command. + * + * Note that only ALTER PUBLICATION ADD/SET should be deparsed here, ALTER + * PUBLICATION DROP is deparsed in different places. + * + * XXX ALTER PUBLICATION SET publication_object is converted to ALTER + * PUBLICATION ADD/DROP. + * + * Verbose syntax + * ALTER PUBLICATION %{identity}I %{add_object}s * OR - * ALTER %{objtype}s %{if_exists}s %{only}s %{identity}D RENAME COLUMN %{colname}I TO %{newname}I %{cascade}s + * ALTER PUBLICATION %{identity}I %{set_options}s */ static ObjTree * -deparse_RenameStmt(ObjectAddress address, Node *parsetree) +deparse_AlterPublicationAddStmt(ObjectAddress object, Node *parsetree) { - RenameStmt *node = (RenameStmt *) parsetree; - ObjTree *ret; - Relation relation; - Oid schemaId; + char *pubname; + ObjTree *alterpub; + ObjTree *addobject = NULL; + ObjTree *setoption = NULL; + AlterPublicationStmt *node = (AlterPublicationStmt *) parsetree; - /* - * In an ALTER .. RENAME command, we don't have the original name of the - * object in system catalogs: since we inspect them after the command has - * executed, the old name is already gone. Therefore, we extract it from - * the parse node. Note we still extract the schema name from the catalog - * (it might not be present in the parse node); it cannot possibly have - * changed anyway. - */ - switch (node->renameType) + switch (object.classId) { - case OBJECT_INDEX: - case OBJECT_TABLE: - relation = relation_open(address.objectId, AccessShareLock); - schemaId = RelationGetNamespace(relation); - ret = new_objtree_VA("ALTER %{objtype}s %{if_exists}s %{identity}D RENAME TO %{newname}I", 4, - "objtype", ObjTypeString, - stringify_objtype(node->renameType, false), - "if_exists", ObjTypeString, - node->missing_ok ? "IF EXISTS" : "", - "identity", ObjTypeObject, - new_objtree_for_qualname(schemaId, - node->relation->relname), - "newname", ObjTypeString, - node->newname); - relation_close(relation, AccessShareLock); + /* ADD TABLE */ + case PublicationRelRelationId: + { + ObjTree *whereclause; + ObjTree *columnlist; + Form_pg_publication_rel pubrelform; + Oid relid; + HeapTuple tup; + + tup = SearchSysCache1(PUBLICATIONREL, + ObjectIdGetDatum(object.objectId)); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for publication table %u", + object.objectId); + + pubrelform = (Form_pg_publication_rel) GETSTRUCT(tup); + relid = pubrelform->prrelid; + + addobject = new_objtree_VA("ADD TABLE %{tablename}D", 1, + "tablename", ObjTypeObject, + new_objtree_for_qualname_id(RelationRelationId, relid)); + + columnlist = deparse_PublicationRelationColumnList(tup); + append_object_object(addobject, "%{column_list}s", columnlist); + + whereclause = deparse_PublicationRelationWhereClause(tup); + append_object_object(addobject, "%{where_clause}s", whereclause); + + ReleaseSysCache(tup); + } break; - case OBJECT_TABCONSTRAINT: + /* ADD TABLES IN SCHEMA */ + case PublicationNamespaceRelationId: { - HeapTuple constrtup; - Form_pg_constraint constform; + Form_pg_publication_namespace pubnspform; + HeapTuple tup; - constrtup = SearchSysCache1(CONSTROID, - ObjectIdGetDatum(address.objectId)); - if (!HeapTupleIsValid(constrtup)) - elog(ERROR, "cache lookup failed for constraint with OID %u", - address.objectId); - constform = (Form_pg_constraint) GETSTRUCT(constrtup); + tup = SearchSysCache1(PUBLICATIONNAMESPACE, + ObjectIdGetDatum(object.objectId)); - ret = new_objtree_VA("ALTER TABLE %{only}s %{identity}D RENAME CONSTRAINT %{oldname}I TO %{newname}I", 4, - "only", ObjTypeString, - node->relation->inh ? "" : "ONLY", - "identity", ObjTypeObject, - new_objtree_for_qualname_id(RelationRelationId, - constform->conrelid), - "oldname", ObjTypeString, node->subname, - "newname", ObjTypeString, node->newname); - ReleaseSysCache(constrtup); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for publication schema %u", + object.objectId); + + pubnspform = (Form_pg_publication_namespace) GETSTRUCT(tup); + + addobject = new_objtree_VA("ADD TABLES IN SCHEMA %{schema}I", 1, + "schema", ObjTypeString, + get_namespace_name(pubnspform->pnnspid)); + + ReleaseSysCache(tup); } break; - case OBJECT_COLUMN: - relation = relation_open(address.objectId, AccessShareLock); - schemaId = RelationGetNamespace(relation); - - ret = new_objtree_VA("ALTER %{objtype}s", 1, - "objtype", ObjTypeString, - stringify_objtype(node->relationType, false)); + /* SET option */ + case PublicationRelationId: + { + List *optionlist = NIL; + ListCell *cell; - /* Composite types do not support IF EXISTS */ - if (node->renameType == OBJECT_COLUMN) - append_string_object(ret, "%{if_exists}s", - "if_exists", - node->missing_ok ? "IF EXISTS" : ""); - if (!node->relation->inh) - append_string_object(ret, "%{only}s", - "only", - "ONLY"); - append_object_object(ret, "%{identity}D", - new_objtree_for_qualname(schemaId, - node->relation->relname)); - append_string_object(ret, "RENAME COLUMN %{colname}I", - "colname", node->subname); + Assert(node->options != NIL); - append_string_object(ret, "TO %{newname}I", "newname", - node->newname); + foreach(cell, node->options) + { + ObjTree *tmp_obj; + DefElem *opt = (DefElem *) lfirst(cell); - if (node->renameType == OBJECT_ATTRIBUTE) - append_object_object(ret, "%{cascade}s", - new_objtree_VA("CASCADE", 1, - "present", ObjTypeBool, - node->behavior == DROP_CASCADE)); + tmp_obj = deparse_DefElem(opt, false); + optionlist = lappend(optionlist, new_object_object(tmp_obj)); + } - relation_close(relation, AccessShareLock); + setoption = new_objtree_VA("SET (%{options:, }s)", 1, + "options", ObjTypeArray, + optionlist); + } break; default: - elog(ERROR, "unsupported object type %d", node->renameType); + Assert(false); + break; } - return ret; + pubname = pstrdup(node->pubname); + + alterpub = new_objtree_VA("ALTER PUBLICATION %{identity}s", 1, + "identity", ObjTypeString, pubname); + + Assert(addobject || setoption); + + if (addobject) + append_object_object(alterpub, "%{add_object}s", addobject); + else + append_object_object(alterpub, "%{set_options}s", setoption); + + return alterpub; } /* - * Deparse a AlterObjectDependsStmt (ALTER ... DEPENDS ON ...). + * Handle deparsing of ALTER PUBLICATION DROP commands. * * Verbose syntax - * ALTER INDEX %{identity}D %{no}s DEPENDS ON EXTENSION %{newname}I + * ALTER PUBLICATION %{identity}I %{drop_object}s */ -static ObjTree * -deparse_AlterDependStmt(Oid objectId, Node *parsetree) +char * +deparse_AlterPublicationDropStmt(SQLDropObject *obj) { - AlterObjectDependsStmt *node = (AlterObjectDependsStmt *) parsetree; - ObjTree *ret = NULL; + ObjTree *alterpub; + ObjTree *drop_object = NULL; + char objname[NAMEDATALEN]; + char pubname[NAMEDATALEN]; + Jsonb *jsonb; + char *command; + StringInfoData str; - if (node->objectType == OBJECT_INDEX) + if (sscanf(obj->objidentity, "%s in publication %s", objname, pubname) != 2) + elog(ERROR, "could not parse ALTER PUBLICATION command \"%s\"", + obj->objidentity); + + switch (getObjectClass(&obj->address)) { - ObjTree *qualified; - Relation relation = relation_open(objectId, AccessShareLock); + /* DROP TABLE */ + case OCLASS_PUBLICATION_REL: + drop_object = new_objtree_VA("DROP TABLE %{tablename}s", 1, + "tablename", ObjTypeString, objname); + break; - qualified = new_objtree_for_qualname(relation->rd_rel->relnamespace, - node->relation->relname); - relation_close(relation, AccessShareLock); + /* DROP TABLES IN SCHEMA */ + case OCLASS_PUBLICATION_NAMESPACE: + drop_object = new_objtree_VA("DROP TABLES IN SCHEMA %{schemaname}s", 1, + "schemaname", ObjTypeString, objname); + break; - ret = new_objtree_VA("ALTER INDEX %{identity}D %{no}s DEPENDS ON EXTENSION %{newname}I", 3, - "identity", ObjTypeObject, qualified, - "no", ObjTypeString, - node->remove ? "NO" : "", - "newname", ObjTypeString, strVal(node->extname)); + default: + Assert(false); + break; } - else - elog(LOG, "unrecognized node type in deparse command: %d", - (int) nodeTag(parsetree)); - return ret; + alterpub = new_objtree_VA("ALTER PUBLICATION %{identity}s", 1, + "identity", ObjTypeString, pubname); + + Assert(drop_object); + + append_object_object(alterpub, "%{drop_object}s", drop_object); + + initStringInfo(&str); + jsonb = objtree_to_jsonb(alterpub, NULL /* Owner/role can be skipped for drop command */); + command = JsonbToCString(&str, &jsonb->root, JSONB_ESTIMATED_LEN); + + return command; } /* @@ -3666,6 +9896,40 @@ deparse_simple_command(CollectedCommand *cmd, bool *include_owner) /* This switch needs to handle everything that ProcessUtilitySlow does */ switch (nodeTag(parsetree)) { + case T_AlterCollationStmt: + *include_owner = false; + return deparse_AlterCollation(objectId, parsetree); + + case T_AlterDomainStmt: + *include_owner = false; + return deparse_AlterDomainStmt(objectId, parsetree, + cmd->d.simple.secondaryObject); + + case T_AlterEnumStmt: + *include_owner = false; + return deparse_AlterEnumStmt(objectId, parsetree); + + case T_AlterExtensionContentsStmt: + *include_owner = false; + return deparse_AlterExtensionContentsStmt(objectId, parsetree, + cmd->d.simple.secondaryObject); + + case T_AlterExtensionStmt: + *include_owner = false; + return deparse_AlterExtensionStmt(objectId, parsetree); + + case T_AlterFdwStmt: + *include_owner = false; + return deparse_AlterFdwStmt(objectId, parsetree); + + case T_AlterForeignServerStmt: + *include_owner = false; + return deparse_AlterForeignServerStmt(objectId, parsetree); + + case T_AlterFunctionStmt: + *include_owner = false; + return deparse_AlterFunction(objectId, parsetree); + case T_AlterObjectDependsStmt: *include_owner = false; return deparse_AlterDependStmt(objectId, parsetree); @@ -3676,20 +9940,138 @@ deparse_simple_command(CollectedCommand *cmd, bool *include_owner) parsetree, cmd->d.simple.secondaryObject); + case T_AlterOperatorStmt: + *include_owner = false; + return deparse_AlterOperatorStmt(objectId, parsetree); + case T_AlterOwnerStmt: *include_owner = false; return deparse_AlterOwnerStmt(cmd->d.simple.address, parsetree); + case T_AlterPolicyStmt: + *include_owner = false; + return deparse_AlterPolicyStmt(objectId, parsetree); + + case T_AlterSeqStmt: + *include_owner = false; + return deparse_AlterSeqStmt(objectId, parsetree); + + case T_AlterStatsStmt: + *include_owner = false; + return deparse_AlterStatsStmt(objectId, parsetree); + + case T_AlterTSDictionaryStmt: + *include_owner = false; + return deparse_AlterTSDictionaryStmt(objectId, parsetree); + + case T_AlterTypeStmt: + *include_owner = false; + return deparse_AlterTypeSetStmt(objectId, parsetree); + + case T_AlterUserMappingStmt: + *include_owner = false; + return deparse_AlterUserMappingStmt(objectId, parsetree); + + case T_CommentStmt: + *include_owner = false; + return deparse_CommentStmt(cmd->d.simple.address, parsetree); + + case T_CompositeTypeStmt: + return deparse_CompositeTypeStmt(objectId, parsetree); + + case T_CreateAmStmt: + return deparse_CreateAmStmt(objectId, parsetree); + + case T_CreateCastStmt: + return deparse_CreateCastStmt(objectId, parsetree); + + case T_CreateConversionStmt: + return deparse_CreateConversion(objectId, parsetree); + + case T_CreateDomainStmt: + return deparse_CreateDomain(objectId, parsetree); + + case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */ + return deparse_CreateEnumStmt(objectId, parsetree); + + case T_CreateExtensionStmt: + return deparse_CreateExtensionStmt(objectId, parsetree); + + case T_CreateFdwStmt: + return deparse_CreateFdwStmt(objectId, parsetree); + + case T_CreateForeignServerStmt: + return deparse_CreateForeignServerStmt(objectId, parsetree); + + case T_CreateFunctionStmt: + return deparse_CreateFunction(objectId, parsetree); + + case T_CreateOpFamilyStmt: + return deparse_CreateOpFamily(objectId, parsetree); + + case T_CreatePLangStmt: + return deparse_CreateLangStmt(objectId, parsetree); + + case T_CreatePolicyStmt: + return deparse_CreatePolicyStmt(objectId, parsetree); + + case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */ + return deparse_CreateRangeStmt(objectId, parsetree); + + case T_CreateSchemaStmt: + return deparse_CreateSchemaStmt(objectId, parsetree); + + case T_CreateSeqStmt: + return deparse_CreateSeqStmt(objectId, parsetree); + + case T_CreateStatsStmt: + return deparse_CreateStatisticsStmt(objectId, parsetree); + case T_CreateStmt: return deparse_CreateStmt(objectId, parsetree); + case T_CreateForeignTableStmt: + return deparse_CreateForeignTableStmt(objectId, parsetree); + + case T_CreateTableAsStmt: /* CREATE MATERIALIZED VIEW */ + return deparse_CreateTableAsStmt_vanilla(objectId, parsetree); + + case T_CreateTransformStmt: + return deparse_CreateTransformStmt(objectId, parsetree); + + case T_CreateTrigStmt: + return deparse_CreateTrigStmt(objectId, parsetree); + + case T_CreateUserMappingStmt: + return deparse_CreateUserMappingStmt(objectId, parsetree); + + case T_DefineStmt: + return deparse_DefineStmt(objectId, parsetree, + cmd->d.simple.secondaryObject); + case T_IndexStmt: return deparse_IndexStmt(objectId, parsetree); + case T_RefreshMatViewStmt: + *include_owner = false; + return deparse_RefreshMatViewStmt(objectId, parsetree); + case T_RenameStmt: *include_owner = false; return deparse_RenameStmt(cmd->d.simple.address, parsetree); + case T_RuleStmt: + return deparse_RuleStmt(objectId, parsetree); + + case T_ViewStmt: /* CREATE VIEW */ + return deparse_ViewStmt(objectId, parsetree); + + case T_CreatePublicationStmt: + return deparse_CreatePublicationStmt(objectId, parsetree); + + case T_AlterPublicationStmt: + return deparse_AlterPublicationAddStmt(cmd->d.simple.address, parsetree); + default: elog(LOG, "unrecognized node type in deparse command: %d", (int) nodeTag(parsetree)); @@ -3755,9 +10137,32 @@ deparse_utility_command(CollectedCommand *cmd, bool include_owner, bool verbose_ tree = deparse_AlterRelation(cmd); include_owner = false; break; + case SCT_Grant: + tree = deparse_GrantStmt(cmd); + include_owner = false; + break; case SCT_CreateTableAs: tree = deparse_CreateTableAsStmt(cmd); break; + case SCT_AlterOpFamily: + include_owner = false; + tree = deparse_AlterOpFamily(cmd); + break; + case SCT_CreateOpClass: + tree = deparse_CreateOpClassStmt(cmd); + break; + case SCT_AlterDefaultPrivileges: + include_owner = false; + tree = deparse_AlterDefaultPrivilegesStmt(cmd); + break; + case SCT_AlterTSConfig: + include_owner = false; + tree = deparse_AlterTSConfigurationStmt(cmd); + break; + case SCT_SecurityLabel: + include_owner = false; + tree = deparse_SecLabelStmt(cmd); + break; default: elog(ERROR, "unexpected deparse node type %d", cmd->type); } diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 38c11f000c..1b75003b5f 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -2074,6 +2074,37 @@ EventTriggerCollectAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt) MemoryContextSwitchTo(oldcxt); } +/* + * EventTriggerCollectSecLabel + * Save data about an SECURITY LABEL command being executed + */ +void +EventTriggerCollectSecLabel(ObjectAddress address, char *provider, + SecLabelStmt *stmt) +{ + MemoryContext oldcxt; + CollectedCommand *command; + + /* ignore if event trigger context not set, or collection disabled */ + if (!currentEventTriggerState || + currentEventTriggerState->commandCollectionInhibited) + return; + + oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); + + command = palloc0(sizeof(CollectedCommand)); + command->type = SCT_SecurityLabel; + command->in_extension = creating_extension; + command->role = GetUserNameFromId(GetUserId(), false); + command->d.seclabel.address = address; + command->d.seclabel.provider = provider; + command->parsetree = (Node *) copyObject(stmt); + + currentEventTriggerState->commandList = + lappend(currentEventTriggerState->commandList, command); + MemoryContextSwitchTo(oldcxt); +} + /* * In a ddl_command_end event trigger, this function reports the DDL commands * being run. @@ -2125,6 +2156,7 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS) case SCT_AlterOpFamily: case SCT_CreateOpClass: case SCT_AlterTSConfig: + case SCT_SecurityLabel: case SCT_CreateTableAs: { char *identity; @@ -2143,6 +2175,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS) addr = cmd->d.createopc.address; else if (cmd->type == SCT_AlterTSConfig) addr = cmd->d.atscfg.address; + else if (cmd->type == SCT_SecurityLabel) + addr = cmd->d.seclabel.address; else if (cmd->type == SCT_CreateTableAs) addr = cmd->d.ctas.address; diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c index e3aad1c883..a8a9175d15 100644 --- a/src/backend/commands/publicationcmds.c +++ b/src/backend/commands/publicationcmds.c @@ -104,6 +104,7 @@ parse_publication_options(ParseState *pstate, pubactions->pubtruncate = true; pubactions->pubddl_table = false; pubactions->pubddl_index = false; + pubactions->pubddl_all = false; *publish_via_partition_root = false; /* Parse options */ @@ -180,6 +181,7 @@ parse_publication_options(ParseState *pstate, */ pubactions->pubddl_table = false; pubactions->pubddl_index = false; + pubactions->pubddl_all = false; *ddl_type_given = true; ddl_level = defGetString(defel); @@ -194,7 +196,9 @@ parse_publication_options(ParseState *pstate, { char *publish_opt = (char *) lfirst(lc3); - if (strcmp(publish_opt, "table") == 0) + if (strcmp(publish_opt, "all") == 0) + pubactions->pubddl_all = true; + else if (strcmp(publish_opt, "table") == 0) pubactions->pubddl_table = true; else if (strcmp(publish_opt, "index") == 0) pubactions->pubddl_index = true; @@ -847,28 +851,39 @@ CreateDDLReplicaEventTriggers(PublicationActions pubactions, Oid puboid) List *init_commands = NIL; List *end_commands = NIL; - if (pubactions.pubddl_table) + if (pubactions.pubddl_all) { - start_commands = lappend_int(start_commands, CMDTAG_DROP_TABLE); + start_commands = end_commands = init_commands = + GetCommandTagsForDDLRepl(); + rewrite_commands = lappend_int(rewrite_commands, CMDTAG_ALTER_TABLE); + } + else + { + if (pubactions.pubddl_table) + { + start_commands = lappend_int(start_commands, CMDTAG_DROP_TABLE); + rewrite_commands = lappend_int(rewrite_commands, CMDTAG_ALTER_TABLE); - init_commands = lappend_int(init_commands, CMDTAG_CREATE_TABLE_AS); - init_commands = lappend_int(init_commands, CMDTAG_SELECT_INTO); + init_commands = lappend_int(init_commands, CMDTAG_CREATE_TABLE_AS); + init_commands = lappend_int(init_commands, CMDTAG_SELECT_INTO); - end_commands = lappend_int(end_commands, CMDTAG_CREATE_TABLE); - end_commands = lappend_int(end_commands, CMDTAG_ALTER_TABLE); - end_commands = lappend_int(end_commands, CMDTAG_DROP_TABLE); - } + end_commands = lappend_int(end_commands, CMDTAG_CREATE_TABLE); + end_commands = lappend_int(end_commands, CMDTAG_ALTER_TABLE); + end_commands = lappend_int(end_commands, CMDTAG_DROP_TABLE); + } - if (pubactions.pubddl_index) - { - start_commands = lappend_int(start_commands, CMDTAG_DROP_INDEX); + if (pubactions.pubddl_index) + { + start_commands = lappend_int(start_commands, CMDTAG_DROP_INDEX); - end_commands = lappend_int(end_commands, CMDTAG_CREATE_INDEX); - end_commands = lappend_int(end_commands, CMDTAG_ALTER_INDEX); - end_commands = lappend_int(end_commands, CMDTAG_DROP_INDEX); + end_commands = lappend_int(end_commands, CMDTAG_CREATE_INDEX); + end_commands = lappend_int(end_commands, CMDTAG_ALTER_INDEX); + end_commands = lappend_int(end_commands, CMDTAG_DROP_INDEX); + } } + /* Create the ddl_command_end event trigger */ if (end_commands != NIL) CreateDDLReplicaEventTrigger(PUB_TRIG_EVENT1, end_commands, puboid); @@ -1007,6 +1022,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt) BoolGetDatum(pubactions.pubddl_table); values[Anum_pg_publication_pubddl_index - 1] = BoolGetDatum(pubactions.pubddl_index); + values[Anum_pg_publication_pubddl_all - 1] = + BoolGetDatum(pubactions.pubddl_all); values[Anum_pg_publication_pubviaroot - 1] = BoolGetDatum(publish_via_partition_root); @@ -1203,7 +1220,9 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt, } } - if (ddl_type_given && (pubactions.pubddl_table || pubactions.pubddl_index)) + if (ddl_type_given && + (pubactions.pubddl_table || pubactions.pubddl_index || + pubactions.pubddl_all)) { if (root_relids == NIL) root_relids = GetPublicationRelations(pubform->oid, @@ -1239,7 +1258,8 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt, { /* Recreate the event triggers if the ddl option is changed. */ if (pubform->pubddl_table != pubactions.pubddl_table || - pubform->pubddl_index != pubactions.pubddl_index) + pubform->pubddl_index != pubactions.pubddl_index || + pubform->pubddl_all != pubactions.pubddl_all) { DropDDLReplicaEventTriggers(pubform->oid); CreateDDLReplicaEventTriggers(pubactions, pubform->oid); @@ -1250,6 +1270,9 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt, values[Anum_pg_publication_pubddl_index - 1] = BoolGetDatum(pubactions.pubddl_index); replaces[Anum_pg_publication_pubddl_index - 1] = true; + + values[Anum_pg_publication_pubddl_all - 1] = BoolGetDatum(pubactions.pubddl_all); + replaces[Anum_pg_publication_pubddl_all - 1] = true; } if (publish_via_partition_root_given) @@ -1357,7 +1380,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, if (stmt->action == AP_AddObjects) { - if (pubform->pubddl_table || pubform->pubddl_index) + if (pubform->pubddl_table || pubform->pubddl_index || + pubform->pubddl_all) ereport(ERROR, errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot add table to publication \"%s\" if DDL replication is enabled", @@ -1381,7 +1405,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, List *delrels = NIL; ListCell *oldlc; - if (pubform->pubddl_table || pubform->pubddl_index) + if (pubform->pubddl_table || pubform->pubddl_index || + pubform->pubddl_all) ereport(ERROR, errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot add table to publication \"%s\" if DDL replication is enabled", diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c index 7ff16e3276..7ee54cde8e 100644 --- a/src/backend/commands/seclabel.c +++ b/src/backend/commands/seclabel.c @@ -18,6 +18,7 @@ #include "catalog/indexing.h" #include "catalog/pg_seclabel.h" #include "catalog/pg_shseclabel.h" +#include "commands/event_trigger.h" #include "commands/seclabel.h" #include "miscadmin.h" #include "utils/builtins.h" @@ -213,6 +214,11 @@ ExecSecLabelStmt(SecLabelStmt *stmt) if (relation != NULL) relation_close(relation, NoLock); + /* Pass the info to event triggers about the SECURITY LABEL. */ + if (EventTriggerSupportsObjectType(stmt->objtype)) + EventTriggerCollectSecLabel(address, pstrdup(provider->provider_name), + stmt); + return address; } diff --git a/src/backend/replication/logical/ddltrigger.c b/src/backend/replication/logical/ddltrigger.c index 11e33b9b65..86c520ba11 100644 --- a/src/backend/replication/logical/ddltrigger.c +++ b/src/backend/replication/logical/ddltrigger.c @@ -226,21 +226,103 @@ publication_deparse_ddl_command_end(PG_FUNCTION_ARGS) EventTriggerData *trigdata; char *command; DeparsedCommandType cmdtype; + const char *tmptype; + ObjectClass objclass; + ObjectAddress objaddr; trigdata = (EventTriggerData *) fcinfo->context; stmt = (DropStmt *) trigdata->parsetree; obj = slist_container(SQLDropObject, next, iter.cur); + objaddr = obj->address; + objclass = getObjectClass(&objaddr); if (strcmp(obj->objecttype, "table") == 0) cmdtype = DCT_TableDropEnd; - else if (strcmp(obj->objecttype, "index") == 0) + else if (objclass == OCLASS_SCHEMA || + objclass == OCLASS_OPERATOR || + objclass == OCLASS_OPCLASS || + objclass == OCLASS_OPFAMILY || + objclass == OCLASS_CAST || + objclass == OCLASS_TYPE || + objclass == OCLASS_TRIGGER || + objclass == OCLASS_CONVERSION || + objclass == OCLASS_POLICY || + objclass == OCLASS_REWRITE || + objclass == OCLASS_EXTENSION || + objclass == OCLASS_FDW || + objclass == OCLASS_TSCONFIG || + objclass == OCLASS_TSDICT || + objclass == OCLASS_TSTEMPLATE || + objclass == OCLASS_TSPARSER || + objclass == OCLASS_TRANSFORM || + objclass == OCLASS_FOREIGN_SERVER || + objclass == OCLASS_COLLATION || + objclass == OCLASS_USER_MAPPING || + objclass == OCLASS_LANGUAGE || + objclass == OCLASS_STATISTIC_EXT || + objclass == OCLASS_AM || + objclass == OCLASS_PUBLICATION || + objclass == OCLASS_PUBLICATION_REL || + objclass == OCLASS_PUBLICATION_NAMESPACE || + strcmp(obj->objecttype, "foreign table") == 0 || + strcmp(obj->objecttype, "index") == 0 || + strcmp(obj->objecttype, "sequence") == 0 || + strcmp(obj->objecttype, "view") == 0 || + strcmp(obj->objecttype, "function") == 0 || + strcmp(obj->objecttype, "materialized view") == 0 || + strcmp(obj->objecttype, "procedure") == 0 || + strcmp(obj->objecttype, "routine") == 0 || + strcmp(obj->objecttype, "aggregate") == 0) cmdtype = DCT_ObjectDropEnd; else continue; - command = deparse_drop_command(obj->objidentity, obj->objecttype, - stmt->behavior); + /* Change foreign-data wrapper to foreign data wrapper */ + if (strncmp(obj->objecttype, "foreign-data wrapper", 20) == 0) + { + tmptype = pstrdup("foreign data wrapper"); + command = deparse_drop_command(obj->objidentity, tmptype, + stmt->behavior); + } + + /* Change statistics object to statistics */ + else if (strncmp(obj->objecttype, "statistics object", + strlen("statistics object")) == 0) + { + tmptype = pstrdup("statistics"); + command = deparse_drop_command(obj->objidentity, tmptype, + stmt->behavior); + } + + /* + * Object identity needs to be modified to make the drop work + * + * FROM: on server TO : for server + * + * + */ + else if (strncmp(obj->objecttype, "user mapping", 12) == 0) + { + char *on_server; + + tmptype = palloc(strlen(obj->objidentity) + 2); + on_server = strstr(obj->objidentity, "on server"); + + sprintf((char *) tmptype, "for "); + strncat((char *) tmptype, obj->objidentity, on_server - obj->objidentity); + strcat((char *) tmptype, on_server + 3); + command = deparse_drop_command(tmptype, obj->objecttype, + stmt->behavior); + } + else if (strncmp(obj->objecttype, "publication namespace", + strlen("publication namespace")) == 0 || + strncmp(obj->objecttype, "publication relation", + strlen("publication relation")) == 0) + command = deparse_AlterPublicationDropStmt(obj); + else + command = deparse_drop_command(obj->objidentity, obj->objecttype, + stmt->behavior); if (command) LogLogicalDDLMessage("deparse", obj->address.objectId, cmdtype, diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c index 9a54cbd152..80cd6fad56 100644 --- a/src/backend/replication/pgoutput/pgoutput.c +++ b/src/backend/replication/pgoutput/pgoutput.c @@ -224,6 +224,7 @@ typedef struct DDLSyncCache { bool valid; bool pubindex; + bool puball; } DDLSyncCache; static DDLSyncCache *ddlcache = NULL; @@ -1754,6 +1755,8 @@ is_object_published(LogicalDecodingContext *ctx, Oid objid) RelationSyncEntry *relentry; PGOutputData *data = (PGOutputData *) ctx->output_plugin_private; + build_ddl_sync_cache(data); + /* First check the DDL command filter. */ switch (get_rel_relkind(objid)) { @@ -1766,15 +1769,14 @@ is_object_published(LogicalDecodingContext *ctx, Oid objid) * Skip sending this ddl if we don't publish ddl message or the ddl * need to be published via its root relation. */ - if (!relentry->pubactions.pubddl_table || + if ((!relentry->pubactions.pubddl_table && + !ddlcache->puball) || relentry->publish_as_relid != objid) return false; break; case RELKIND_INDEX: - build_ddl_sync_cache(data); - - if (!ddlcache->pubindex) + if (!ddlcache->pubindex && !ddlcache->puball) return false; /* Get the table OID that the index is for. */ @@ -1787,7 +1789,8 @@ is_object_published(LogicalDecodingContext *ctx, Oid objid) relentry = get_rel_sync_entry(data, relation); RelationClose(relation); - if (!relentry->pubactions.pubddl_table || + if ((!relentry->pubactions.pubddl_table && + !ddlcache->puball) || relentry->publish_as_relid != objid) return false; @@ -2240,7 +2243,8 @@ get_rel_sync_entry(PGOutputData *data, Relation relation) entry->streamed_txns = NIL; entry->pubactions.pubinsert = entry->pubactions.pubupdate = entry->pubactions.pubdelete = entry->pubactions.pubtruncate = - entry->pubactions.pubddl_table = entry->pubactions.pubddl_index = false; + entry->pubactions.pubddl_table = entry->pubactions.pubddl_index = + entry->pubactions.pubddl_all = false; entry->new_slot = NULL; entry->old_slot = NULL; memset(entry->exprstate, 0, sizeof(entry->exprstate)); @@ -2288,6 +2292,7 @@ get_rel_sync_entry(PGOutputData *data, Relation relation) entry->pubactions.pubtruncate = false; entry->pubactions.pubddl_table = false; entry->pubactions.pubddl_index = false; + entry->pubactions.pubddl_all = false; /* * Tuple slots cleanups. (Will be rebuilt later if needed). @@ -2403,6 +2408,7 @@ get_rel_sync_entry(PGOutputData *data, Relation relation) entry->pubactions.pubtruncate |= pub->pubactions.pubtruncate; entry->pubactions.pubddl_table |= pub->pubactions.pubddl_table; entry->pubactions.pubddl_index |= pub->pubactions.pubddl_index; + entry->pubactions.pubddl_all |= pub->pubactions.pubddl_all; /* * We want to publish the changes as the top-most ancestor @@ -2488,6 +2494,7 @@ build_ddl_sync_cache(PGOutputData *data) return; ddlcache->pubindex = false; + ddlcache->puball = false; reload_publications(data); @@ -2496,6 +2503,7 @@ build_ddl_sync_cache(PGOutputData *data) Publication *pub = lfirst(lc); ddlcache->pubindex |= pub->pubactions.pubddl_index; + ddlcache->puball |= pub->pubactions.pubddl_all; } ddlcache->valid = true; diff --git a/src/backend/tcop/cmdtag.c b/src/backend/tcop/cmdtag.c index ce09c5f233..7e03782524 100644 --- a/src/backend/tcop/cmdtag.c +++ b/src/backend/tcop/cmdtag.c @@ -14,6 +14,7 @@ #include "postgres.h" #include "miscadmin.h" +#include "nodes/pg_list.h" #include "tcop/cmdtag.h" #include "utils/builtins.h" @@ -58,16 +59,15 @@ GetCommandTagNameAndLen(CommandTag commandTag, Size *len) return tag_behavior[commandTag].name; } -CommandTag * -GetCommandTagsForDDLRepl(int *ncommands) +List * +GetCommandTagsForDDLRepl(void) { - CommandTag *ddlrepl_commands = palloc0(COMMAND_TAG_NEXTTAG * sizeof(CommandTag)); - *ncommands = 0; + List *ddlrepl_commands = NIL; for(int i = 0; i < COMMAND_TAG_NEXTTAG; i++) { if (tag_behavior[i].ddl_replication_ok) - ddlrepl_commands[(*ncommands)++] = (CommandTag) i; + ddlrepl_commands = lappend_int(ddlrepl_commands, i); } return ddlrepl_commands; diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 578a229ffc..34a9c853a6 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1449,7 +1449,8 @@ ProcessUtilitySlow(ParseState *pstate, address = DefineCollation(pstate, stmt->defnames, stmt->definition, - stmt->if_not_exists); + stmt->if_not_exists, + &secondaryObject); break; default: elog(ERROR, "unrecognized define stmt type: %d", @@ -1839,6 +1840,7 @@ ProcessUtilitySlow(ParseState *pstate, case T_SecLabelStmt: address = ExecSecLabelStmt((SecLabelStmt *) parsetree); + commandCollected = true; break; case T_CreateAmStmt: diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c index 296930eb3b..e46c39d620 100644 --- a/src/backend/utils/adt/regproc.c +++ b/src/backend/utils/adt/regproc.c @@ -307,6 +307,57 @@ format_procedure_qualified(Oid procedure_oid) return format_procedure_extended(procedure_oid, FORMAT_PROC_FORCE_QUALIFY); } +/* + * Append the parenthesized arguments of the given pg_proc row into the output + * buffer. force_qualify indicates whether to schema-qualify type names + * regardless of visibility. + */ +static void +format_procedure_args_internal(Form_pg_proc procform, StringInfo buf, + bool force_qualify) +{ + int i; + char *(*func[2]) (Oid) = {format_type_be, format_type_be_qualified}; + + appendStringInfoChar(buf, '('); + for (i = 0; i < procform->pronargs; i++) + { + Oid thisargtype = procform->proargtypes.values[i]; + char *argtype; + + if (i > 0) + appendStringInfoChar(buf, ','); + + argtype = func[force_qualify] (thisargtype); + appendStringInfoString(buf, argtype); + pfree(argtype); + } + appendStringInfoChar(buf, ')'); +} + +/* + * format_procedure_args - converts proc OID to "(args)" + */ +char * +format_procedure_args(Oid procedure_oid, bool force_qualify) +{ + StringInfoData buf; + HeapTuple proctup; + Form_pg_proc procform; + + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid)); + if (!HeapTupleIsValid(proctup)) + elog(ERROR, "cache lookup failed for procedure %u", procedure_oid); + procform = (Form_pg_proc) GETSTRUCT(proctup); + + initStringInfo(&buf); + format_procedure_args_internal(procform, &buf, force_qualify); + + ReleaseSysCache(proctup); + + return buf.data; +} + /* * format_procedure_extended - converts procedure OID to "pro_name(args)" * diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index c8f5471b18..c489cac8dc 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -35,6 +35,7 @@ #include "catalog/pg_operator.h" #include "catalog/pg_partitioned_table.h" #include "catalog/pg_proc.h" +#include "catalog/pg_rewrite.h" #include "catalog/pg_statistic_ext.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" @@ -341,6 +342,8 @@ 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 void pg_get_rule_whereclause(char *qualstr, Query *query, + StringInfo buf, int prettyFlags); static char *pg_get_indexdef_worker(Oid indexrelid, int colno, const Oid *excludeOps, bool attrsOnly, bool keysOnly, @@ -358,7 +361,6 @@ static int print_function_arguments(StringInfo buf, HeapTuple proctup, bool print_table_args, bool print_defaults); static void print_function_rettype(StringInfo buf, HeapTuple proctup); static void print_function_trftypes(StringInfo buf, HeapTuple proctup); -static void print_function_sqlbody(StringInfo buf, HeapTuple proctup); static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces, Bitmapset *rels_used); static void set_deparse_for_query(deparse_namespace *dpns, Query *query, @@ -555,6 +557,80 @@ pg_get_ruledef_ext(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(string_to_text(res)); } +/* + * Given a pair of Datum corresponding to a rule's pg_rewrite.ev_qual and + * ev_action columns, return their text representation; ev_qual as a single + * string in whereClause and ev_action as a List of strings (which might be + * NIL, signalling NOTHING) in actions. + */ +void +pg_get_ruledef_detailed(Datum ev_qual, Datum ev_action, + char **whereClause, List **actions) +{ + int prettyFlags = 0; + char *qualstr = TextDatumGetCString(ev_qual); + char *actionstr = TextDatumGetCString(ev_action); + List *actionNodeList = (List *) stringToNode(actionstr); + StringInfoData buf; + + *whereClause = NULL; + *actions = NIL; + initStringInfo(&buf); + + if (strcmp(qualstr, "<>") != 0) + { + Query *query = (Query *) linitial(actionNodeList); + + pg_get_rule_whereclause(qualstr, query, &buf, 0); + *whereClause = pstrdup(buf.data); + } + + if (list_length(actionNodeList) > 0) + { + ListCell *cell; + + foreach(cell, actionNodeList) + { + Query *query = (Query *) lfirst(cell); + + resetStringInfo(&buf); + get_query_def(query, &buf, NIL, NULL, true, + prettyFlags, WRAP_COLUMN_DEFAULT, 0); + *actions = lappend(*actions, pstrdup(buf.data)); + } + } +} + +/* + * To get the rewrite rule of a view when the CREATE VIEW command execution is + * still in progress: we search the system cache RULERELNAME to get the rewrite + * rule of the view as opposed to querying pg_rewrite as in pg_get_viewdef_worker(), + * which will return empty result. + */ +char * +pg_get_viewdef_string(Oid viewoid) +{ + StringInfoData buf; + Relation pg_rewrite; + HeapTuple ruletup; + TupleDesc rulettc; + + initStringInfo(&buf); + pg_rewrite = table_open(RewriteRelationId, AccessShareLock); + + ruletup = SearchSysCache2(RULERELNAME, + ObjectIdGetDatum(viewoid), + PointerGetDatum(ViewSelectRuleName)); + if (!HeapTupleIsValid(ruletup)) + elog(ERROR, "cache lookup failed for rewrite rule for view with OID %u", viewoid); + + rulettc = pg_rewrite->rd_att; + make_viewdef(&buf, ruletup, rulettc, 0, WRAP_COLUMN_DEFAULT); + ReleaseSysCache(ruletup); + table_close(pg_rewrite, AccessShareLock); + + return buf.data; +} static char * pg_get_ruledef_worker(Oid ruleoid, int prettyFlags) @@ -1025,65 +1101,12 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty) if (!isnull) { Node *qual; - char relkind; - deparse_context context; - deparse_namespace dpns; - RangeTblEntry *oldrte; - RangeTblEntry *newrte; - - appendStringInfoString(&buf, "WHEN ("); + char *qualstr; qual = stringToNode(TextDatumGetCString(value)); + qualstr = pg_get_trigger_whenclause(trigrec, qual, pretty); - relkind = get_rel_relkind(trigrec->tgrelid); - - /* Build minimal OLD and NEW RTEs for the rel */ - oldrte = makeNode(RangeTblEntry); - oldrte->rtekind = RTE_RELATION; - oldrte->relid = trigrec->tgrelid; - oldrte->relkind = relkind; - oldrte->rellockmode = AccessShareLock; - oldrte->alias = makeAlias("old", NIL); - oldrte->eref = oldrte->alias; - oldrte->lateral = false; - oldrte->inh = false; - oldrte->inFromCl = true; - - newrte = makeNode(RangeTblEntry); - newrte->rtekind = RTE_RELATION; - newrte->relid = trigrec->tgrelid; - newrte->relkind = relkind; - newrte->rellockmode = AccessShareLock; - newrte->alias = makeAlias("new", NIL); - newrte->eref = newrte->alias; - newrte->lateral = false; - newrte->inh = false; - newrte->inFromCl = true; - - /* Build two-element rtable */ - memset(&dpns, 0, sizeof(dpns)); - dpns.rtable = list_make2(oldrte, newrte); - dpns.subplans = NIL; - dpns.ctes = NIL; - dpns.appendrels = NULL; - set_rtable_names(&dpns, NIL, NULL); - set_simple_column_names(&dpns); - - /* Set up context with one-deep namespace stack */ - context.buf = &buf; - context.namespaces = list_make1(&dpns); - context.windowClause = NIL; - context.windowTList = NIL; - context.varprefix = true; - context.prettyFlags = GET_PRETTY_FLAGS(pretty); - context.wrapColumn = WRAP_COLUMN_DEFAULT; - context.indentLevel = PRETTYINDENT_STD; - context.special_exprkind = EXPR_KIND_NONE; - context.appendparents = NULL; - - get_rule_expr(qual, &context, false); - - appendStringInfoString(&buf, ") "); + appendStringInfo(&buf, "WHEN (%s) ", qualstr); } appendStringInfo(&buf, "EXECUTE FUNCTION %s(", @@ -1124,6 +1147,74 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty) return buf.data; } +/* + * Pass back the TriggerWhen clause of a trigger given the pg_trigger record and + * the expression tree (in nodeToString() representation) from pg_trigger.tgqual + * for the trigger's WHEN condition. + */ +char * +pg_get_trigger_whenclause(Form_pg_trigger trigrec, Node *whenClause, bool pretty) +{ + StringInfoData buf; + char relkind; + deparse_context context; + deparse_namespace dpns; + RangeTblEntry *oldrte; + RangeTblEntry *newrte; + + initStringInfo(&buf); + + relkind = get_rel_relkind(trigrec->tgrelid); + + /* Build minimal OLD and NEW RTEs for the rel */ + oldrte = makeNode(RangeTblEntry); + oldrte->rtekind = RTE_RELATION; + oldrte->relid = trigrec->tgrelid; + oldrte->relkind = relkind; + oldrte->rellockmode = AccessShareLock; + oldrte->alias = makeAlias("old", NIL); + oldrte->eref = oldrte->alias; + oldrte->lateral = false; + oldrte->inh = false; + oldrte->inFromCl = true; + + newrte = makeNode(RangeTblEntry); + newrte->rtekind = RTE_RELATION; + newrte->relid = trigrec->tgrelid; + newrte->relkind = relkind; + newrte->rellockmode = AccessShareLock; + newrte->alias = makeAlias("new", NIL); + newrte->eref = newrte->alias; + newrte->lateral = false; + newrte->inh = false; + newrte->inFromCl = true; + + /* Build two-element rtable */ + memset(&dpns, 0, sizeof(dpns)); + dpns.rtable = list_make2(oldrte, newrte); + dpns.subplans = NIL; + dpns.ctes = NIL; + dpns.appendrels = NULL; + set_rtable_names(&dpns, NIL, NULL); + set_simple_column_names(&dpns); + + /* Set up context with one-deep namespace stack */ + context.buf = &buf; + context.namespaces = list_make1(&dpns); + context.windowClause = NIL; + context.windowTList = NIL; + context.varprefix = true; + context.prettyFlags = GET_PRETTY_FLAGS(pretty); + context.wrapColumn = WRAP_COLUMN_DEFAULT; + context.indentLevel = PRETTYINDENT_STD; + context.special_exprkind = EXPR_KIND_NONE; + context.appendparents = NULL; + + get_rule_expr(whenClause, &context, false); + + return buf.data; +} + /* ---------- * pg_get_indexdef - Get the definition of an index * @@ -3486,7 +3577,12 @@ pg_get_function_arg_default(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(string_to_text(str)); } -static void +/* + * Produce the formatted SQL body (not the whole function definition) + * of a function given the pg_proc tuple. Save the formatted SQL in the + * given StringInfo. + */ +void print_function_sqlbody(StringInfo buf, HeapTuple proctup) { int numargs; @@ -5231,48 +5327,18 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, /* If the rule has an event qualification, add it */ if (strcmp(ev_qual, "<>") != 0) { - Node *qual; - Query *query; - deparse_context context; - deparse_namespace dpns; - - if (prettyFlags & PRETTYFLAG_INDENT) - appendStringInfoString(buf, "\n "); - appendStringInfoString(buf, " WHERE "); - - qual = stringToNode(ev_qual); - /* * We need to make a context for recognizing any Vars in the qual * (which can only be references to OLD and NEW). Use the rtable of * the first query in the action list for this purpose. */ - query = (Query *) linitial(actions); - - /* - * If the action is INSERT...SELECT, OLD/NEW have been pushed down - * into the SELECT, and that's what we need to look at. (Ugly kluge - * ... try to fix this when we redesign querytrees.) - */ - query = getInsertSelectQuery(query, NULL); - - /* Must acquire locks right away; see notes in get_query_def() */ - AcquireRewriteLocks(query, false, false); - - context.buf = buf; - context.namespaces = list_make1(&dpns); - context.windowClause = NIL; - context.windowTList = NIL; - context.varprefix = (list_length(query->rtable) != 1); - context.prettyFlags = prettyFlags; - context.wrapColumn = WRAP_COLUMN_DEFAULT; - context.indentLevel = PRETTYINDENT_STD; - context.special_exprkind = EXPR_KIND_NONE; - context.appendparents = NULL; + Query *query = (Query *) linitial(actions); - set_deparse_for_query(&dpns, query, NIL); + if (prettyFlags & PRETTYFLAG_INDENT) + appendStringInfoString(buf, "\n "); + appendStringInfoString(buf, " WHERE "); - get_rule_expr(qual, &context, false); + pg_get_rule_whereclause(ev_qual, query, buf, prettyFlags); } appendStringInfoString(buf, " DO "); @@ -5313,6 +5379,47 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, table_close(ev_relation, AccessShareLock); } +/* + * Given a string corresponding to a rule's pg_rewrite.ev_qual and a query + * parsetree, append ev_qual's text representation into the output buf. + * + * Tries to pretty up the output according to prettyFlags. + */ +static void +pg_get_rule_whereclause(char *qualstr, Query *query, StringInfo buf, + int prettyFlags) +{ + Node *qual; + deparse_context context; + deparse_namespace dpns; + + qual = stringToNode(qualstr); + + /* + * 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; + context.appendparents = NULL; + + set_deparse_for_query(&dpns, query, NIL); + + get_rule_expr(qual, &context, false); +} /* ---------- * make_viewdef - reconstruct the SELECT part of a diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 12259c9dd8..ce5f9b4112 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -5722,6 +5722,7 @@ RelationBuildPublicationDesc(Relation relation, PublicationDesc *pubdesc) pubdesc->pubactions.pubtruncate |= pubform->pubtruncate; pubdesc->pubactions.pubddl_table |= pubform->pubddl_table; pubdesc->pubactions.pubddl_index |= pubform->pubddl_index; + pubdesc->pubactions.pubddl_all |= pubform->pubddl_all; /* * Check if all columns referenced in the filter expression are part diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index fdcf27dd1e..1623abf145 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -4067,6 +4067,7 @@ getPublications(Archive *fout, int *numPublications) int i_pubtruncate; int i_pubddl_table; int i_pubddl_index; + int i_pubddl_all; int i_pubviaroot; int i, ntups; @@ -4086,25 +4087,25 @@ getPublications(Archive *fout, int *numPublications) appendPQExpBufferStr(query, "SELECT p.tableoid, p.oid, p.pubname, " "p.pubowner, " - "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubddl_table, p.pubddl_index, p.pubviaroot " + "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubddl_table, p.pubddl_index, p.pubddl_all, p.pubviaroot " "FROM pg_publication p"); else if (fout->remoteVersion >= 130000) appendPQExpBufferStr(query, "SELECT p.tableoid, p.oid, p.pubname, " "p.pubowner, " - "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false as p.pubddl_table, false as p.pubddl_index, p.pubviaroot " + "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false as p.pubddl_table, false as p.pubddl_index, false as p.pubddl_all, p.pubviaroot " "FROM pg_publication p"); else if (fout->remoteVersion >= 110000) appendPQExpBufferStr(query, "SELECT p.tableoid, p.oid, p.pubname, " "p.pubowner, " - "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false as p.pubddl_table, false as p.pubddl_index, false AS pubviaroot " + "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false as p.pubddl_table, false as p.pubddl_index, false as p.pubddl_all, false AS pubviaroot " "FROM pg_publication p"); else appendPQExpBufferStr(query, "SELECT p.tableoid, p.oid, p.pubname, " "p.pubowner, " - "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, false AS pubtruncate, false as p.pubddl_table, false as p.pubddl_index, false AS pubviaroot " + "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, false AS pubtruncate, false as p.pubddl_table, false as p.pubddl_index, false as p.pubddl_all, false AS pubviaroot " "FROM pg_publication p"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -4122,6 +4123,7 @@ getPublications(Archive *fout, int *numPublications) i_pubtruncate = PQfnumber(res, "pubtruncate"); i_pubddl_table = PQfnumber(res, "pubddl_table"); i_pubddl_index = PQfnumber(res, "pubddl_index"); + i_pubddl_all = PQfnumber(res, "pubddl_all"); i_pubviaroot = PQfnumber(res, "pubviaroot"); pubinfo = pg_malloc(ntups * sizeof(PublicationInfo)); @@ -4149,6 +4151,8 @@ getPublications(Archive *fout, int *numPublications) (strcmp(PQgetvalue(res, i, i_pubddl_table), "t") == 0); pubinfo[i].pubddl_index = (strcmp(PQgetvalue(res, i, i_pubddl_index), "t") == 0); + pubinfo[i].pubddl_all = + (strcmp(PQgetvalue(res, i, i_pubddl_all), "t") == 0); pubinfo[i].pubviaroot = (strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0); @@ -4250,6 +4254,15 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo) first = false; } + if (pubinfo->pubddl_all) + { + if (!first) + appendPQExpBufferStr(query, ", "); + + appendPQExpBufferStr(query, "all"); + first = false; + } + appendPQExpBufferStr(query, "'"); } @@ -8040,7 +8053,7 @@ getPublicationEventTriggers(Archive *fout, SimpleStringList *skipTriggers) appendPQExpBufferStr(query, "SELECT oid FROM pg_publication " - "WHERE pubddl_table"); + "WHERE pubddl_all OR pubddl_table"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); ntups = PQntuples(res); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index efd63477a8..706f206c8b 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -622,6 +622,7 @@ typedef struct _PublicationInfo bool pubtruncate; bool pubddl_table; bool pubddl_index; + bool pubddl_all; bool pubviaroot; } PublicationInfo; diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index df79c79e8d..568fe296b3 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -6208,9 +6208,11 @@ listPublications(const char *pattern) { appendPQExpBuffer(&buf, ",\n pubddl_table AS \"%s\",\n" - " pubddl_index AS \"%s\"\n", + " pubddl_index AS \"%s\",\n" + " pubddl_all AS \"%s\"\n", gettext_noop("Table DDLs"), - gettext_noop("Index DDLs")); + gettext_noop("Index DDLs"), + gettext_noop("All DDLs")); } appendPQExpBuffer(&buf, ",\n pubinsert AS \"%s\",\n" @@ -6343,7 +6345,7 @@ describePublications(const char *pattern) " puballtables"); if (has_pubddl) appendPQExpBufferStr(&buf, - ", pubddl_table, pubddl_index"); + ", pubddl_table, pubddl_index, pubddl_all"); appendPQExpBufferStr(&buf, ", pubinsert, pubupdate, pubdelete"); @@ -6405,7 +6407,7 @@ describePublications(const char *pattern) if (has_pubviaroot) ncols++; if (has_pubddl) - ncols += 2; + ncols += 3; initPQExpBuffer(&title); printfPQExpBuffer(&title, _("Publication %s"), pubname); @@ -6417,6 +6419,7 @@ describePublications(const char *pattern) { printTableAddHeader(&cont, gettext_noop("Table DDLs"), true, align); printTableAddHeader(&cont, gettext_noop("Index DDLs"), true, align); + printTableAddHeader(&cont, gettext_noop("All DDLs"), true, align); } printTableAddHeader(&cont, gettext_noop("Inserts"), true, align); printTableAddHeader(&cont, gettext_noop("Updates"), true, align); @@ -6439,6 +6442,7 @@ describePublications(const char *pattern) { printTableAddCell(&cont, PQgetvalue(res, i, 9), false, false); printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false); + printTableAddCell(&cont, PQgetvalue(res, i, 11), false, false); } if (!puballtables) diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h index 467731c61a..35b3ea25e5 100644 --- a/src/include/catalog/pg_publication.h +++ b/src/include/catalog/pg_publication.h @@ -69,6 +69,9 @@ CATALOG(pg_publication,6104,PublicationRelationId) /* true if index ddls are published */ bool pubddl_index; + + /* true if all supported ddls are published */ + bool pubddl_all; } FormData_pg_publication; /* ---------------- @@ -89,6 +92,7 @@ typedef struct PublicationActions bool pubtruncate; bool pubddl_table; bool pubddl_index; + bool pubddl_all; } PublicationActions; typedef struct PublicationDesc diff --git a/src/include/commands/collationcmds.h b/src/include/commands/collationcmds.h index b76c7b3dc3..53c4a1a8c2 100644 --- a/src/include/commands/collationcmds.h +++ b/src/include/commands/collationcmds.h @@ -18,7 +18,8 @@ #include "catalog/objectaddress.h" #include "parser/parse_node.h" -extern ObjectAddress DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_exists); +extern ObjectAddress DefineCollation(ParseState *pstate, List *names, List *parameters, + bool if_not_exists, ObjectAddress *from_collid); extern void IsThereCollationInNamespace(const char *collname, Oid nspOid); extern ObjectAddress AlterCollation(AlterCollationStmt *stmt); diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h index cba4e72455..339553d456 100644 --- a/src/include/commands/event_trigger.h +++ b/src/include/commands/event_trigger.h @@ -132,5 +132,7 @@ extern void EventTriggerCollectCreateOpClass(CreateOpClassStmt *stmt, extern void EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt, Oid cfgId, Oid *dictIds, int ndicts); extern void EventTriggerCollectAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt); +extern void EventTriggerCollectSecLabel(ObjectAddress address, char *provider, + SecLabelStmt *stmt); #endif /* EVENT_TRIGGER_H */ diff --git a/src/include/tcop/cmdtag.h b/src/include/tcop/cmdtag.h index 076c27e642..0259a4b4f6 100644 --- a/src/include/tcop/cmdtag.h +++ b/src/include/tcop/cmdtag.h @@ -53,7 +53,7 @@ CopyQueryCompletion(QueryCompletion *dst, const QueryCompletion *src) extern void InitializeQueryCompletion(QueryCompletion *qc); extern const char *GetCommandTagName(CommandTag commandTag); extern const char *GetCommandTagNameAndLen(CommandTag commandTag, Size *len); -extern CommandTag *GetCommandTagsForDDLRepl(int *ncommands); +extern List *GetCommandTagsForDDLRepl(void); extern bool command_tag_display_rowcount(CommandTag commandTag); extern bool command_tag_event_trigger_ok(CommandTag commandTag); extern bool command_tag_table_rewrite_ok(CommandTag commandTag); diff --git a/src/include/tcop/ddl_deparse.h b/src/include/tcop/ddl_deparse.h index 2b7754bfac..581a470c09 100644 --- a/src/include/tcop/ddl_deparse.h +++ b/src/include/tcop/ddl_deparse.h @@ -12,11 +12,13 @@ #ifndef DDL_DEPARSE_H #define DDL_DEPARSE_H +#include "commands/event_trigger.h" #include "tcop/deparse_utility.h" extern char *deparse_utility_command(CollectedCommand *cmd, bool include_owner, bool verbose_mode); extern char *deparse_ddl_json_to_string(char *jsonb, char** owner); extern char *deparse_drop_command(const char *objidentity, const char *objecttype, DropBehavior behavior); +extern char * deparse_AlterPublicationDropStmt(SQLDropObject *obj); #endif /* DDL_DEPARSE_H */ diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h index 87a761bb3e..cce3c07fc0 100644 --- a/src/include/tcop/deparse_utility.h +++ b/src/include/tcop/deparse_utility.h @@ -30,6 +30,7 @@ typedef enum CollectedCommandType SCT_AlterDefaultPrivileges, SCT_CreateOpClass, SCT_AlterTSConfig, + SCT_SecurityLabel, SCT_CreateTableAs } CollectedCommandType; @@ -105,6 +106,13 @@ typedef struct CollectedCommand ObjectType objtype; } defprivs; + /* SECURITY LABEL */ + struct + { + ObjectAddress address; + char *provider; + } seclabel; + /* CREATE TABLE AS */ struct { diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h index f8e1238fa2..f05578d322 100644 --- a/src/include/utils/acl.h +++ b/src/include/utils/acl.h @@ -209,6 +209,8 @@ extern AclMode aclmask(const Acl *acl, Oid roleid, Oid ownerId, AclMode mask, AclMaskHow how); extern int aclmembers(const Acl *acl, Oid **roleids); +extern const char *privilege_to_string(AclMode privilege); + extern bool has_privs_of_role(Oid member, Oid role); extern bool member_can_set_role(Oid member, Oid role); extern void check_can_set_role(Oid member, Oid role); diff --git a/src/include/utils/aclchk_internal.h b/src/include/utils/aclchk_internal.h index 55af624fb3..946545f53f 100644 --- a/src/include/utils/aclchk_internal.h +++ b/src/include/utils/aclchk_internal.h @@ -39,6 +39,7 @@ typedef struct List *grantees; bool grant_option; DropBehavior behavior; + Oid grantor_uid; } InternalGrant; diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index c90c32bff1..48b8bfd79a 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -123,6 +123,7 @@ extern Datum numeric_float8_no_overflow(PG_FUNCTION_ARGS); #define FORMAT_TYPE_FORCE_QUALIFY 0x04 /* force qualification of type */ #define FORMAT_TYPE_INVALID_AS_NULL 0x08 /* NULL if undefined */ extern char *format_type_extended(Oid type_oid, int32 typemod, bits16 flags); +extern char *format_procedure_args(Oid procedure_oid, bool force_qualify); extern char *format_type_be(Oid type_oid); extern char *format_type_be_qualified(Oid type_oid); diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h index 9adc589173..b6e20e7ee4 100644 --- a/src/include/utils/ruleutils.h +++ b/src/include/utils/ruleutils.h @@ -13,9 +13,12 @@ #ifndef RULEUTILS_H #define RULEUTILS_H +#include "access/htup.h" +#include "catalog/pg_trigger.h" #include "nodes/nodes.h" #include "nodes/parsenodes.h" #include "nodes/pg_list.h" +#include "parser/parse_node.h" struct Plan; /* avoid including plannodes.h here */ struct PlannedStmt; @@ -23,7 +26,10 @@ struct PlannedStmt; extern char *pg_get_indexdef_string(Oid indexrelid); extern char *pg_get_indexdef_columns(Oid indexrelid, bool pretty); +extern char *pg_get_trigger_whenclause(Form_pg_trigger trigrec, + Node *whenClause, bool pretty); extern char *pg_get_querydef(Query *query, bool pretty); +extern char *pg_get_viewdef_string(Oid viewoid); extern char *pg_get_partkeydef_columns(Oid relid, bool pretty); extern char *pg_get_partkeydef_string(Oid relid); @@ -31,6 +37,9 @@ extern char *pg_get_partconstrdef_string(Oid partitionId, char *aliasname); extern char *pg_get_constraintdef_command(Oid constraintId); extern char *pg_get_constraintdef_string(Oid constraintId); +extern void pg_get_ruledef_detailed(Datum ev_qual, Datum ev_action, + char **whereClause, List **actions); + extern char *deparse_expression(Node *expr, List *dpcontext, bool forceprefix, bool showimplicit); extern List *deparse_context_for(const char *aliasname, Oid relid); @@ -52,5 +61,6 @@ extern void get_opclass_name(Oid opclass, Oid actual_datatype, extern char *flatten_reloptions(Oid relid); extern char *pg_get_statisticsobjdef_string(Oid statextid); +extern void print_function_sqlbody(StringInfo buf, HeapTuple proctup); #endif /* RULEUTILS_H */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index 3e6d2791c8..4474c3b730 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -6223,9 +6223,9 @@ List of schemas (0 rows) \dRp "no.such.publication" - List of publications - Name | Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root -------+-------+------------+------------+------------+---------+---------+---------+-----------+---------- + List of publications + Name | Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +------+-------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- (0 rows) \dRs "no.such.subscription" diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out index ebcff41c0c..c54ce53e7b 100644 --- a/src/test/regress/expected/publication.out +++ b/src/test/regress/expected/publication.out @@ -30,20 +30,20 @@ ERROR: conflicting or redundant options LINE 1: ...ub_xxx WITH (publish_via_partition_root = 'true', publish_vi... ^ \dRp - List of publications - Name | Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------+--------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - testpib_ins_trunct | regress_publication_user | f | f | f | t | f | f | f | f - testpub_default | regress_publication_user | f | f | f | f | t | f | f | f + List of publications + Name | Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + testpib_ins_trunct | regress_publication_user | f | f | f | f | t | f | f | f | f + testpub_default | regress_publication_user | f | f | f | f | f | t | f | f | f (2 rows) ALTER PUBLICATION testpub_default SET (publish = 'insert, update, delete'); \dRp - List of publications - Name | Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------+--------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - testpib_ins_trunct | regress_publication_user | f | f | f | t | f | f | f | f - testpub_default | regress_publication_user | f | f | f | t | t | t | f | f + List of publications + Name | Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + testpib_ins_trunct | regress_publication_user | f | f | f | f | t | f | f | f | f + testpub_default | regress_publication_user | f | f | f | f | t | t | t | f | f (2 rows) --- adding tables @@ -87,10 +87,10 @@ RESET client_min_messages; -- should be able to add schema to 'FOR TABLE' publication ALTER PUBLICATION testpub_fortable ADD TABLES IN SCHEMA pub_test; \dRp+ testpub_fortable - Publication testpub_fortable - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub_fortable + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables: "public.testpub_tbl1" Tables from schemas: @@ -99,20 +99,20 @@ Tables from schemas: -- should be able to drop schema from 'FOR TABLE' publication ALTER PUBLICATION testpub_fortable DROP TABLES IN SCHEMA pub_test; \dRp+ testpub_fortable - Publication testpub_fortable - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub_fortable + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables: "public.testpub_tbl1" -- should be able to set schema to 'FOR TABLE' publication ALTER PUBLICATION testpub_fortable SET TABLES IN SCHEMA pub_test; \dRp+ testpub_fortable - Publication testpub_fortable - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub_fortable + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables from schemas: "pub_test" @@ -123,10 +123,10 @@ CREATE PUBLICATION testpub_forschema FOR TABLES IN SCHEMA pub_test; CREATE PUBLICATION testpub_for_tbl_schema FOR TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk; RESET client_min_messages; \dRp+ testpub_for_tbl_schema - Publication testpub_for_tbl_schema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub_for_tbl_schema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables: "pub_test.testpub_nopk" Tables from schemas: @@ -135,10 +135,10 @@ Tables from schemas: -- should be able to add a table of the same schema to the schema publication ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk; \dRp+ testpub_forschema - Publication testpub_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables: "pub_test.testpub_nopk" Tables from schemas: @@ -147,10 +147,10 @@ Tables from schemas: -- should be able to drop the table ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk; \dRp+ testpub_forschema - Publication testpub_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables from schemas: "pub_test" @@ -161,10 +161,10 @@ ERROR: relation "testpub_nopk" is not part of the publication -- should be able to set table to schema publication ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk; \dRp+ testpub_forschema - Publication testpub_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables: "pub_test.testpub_nopk" @@ -186,10 +186,10 @@ Publications: "testpub_foralltables" \dRp+ testpub_foralltables - Publication testpub_foralltables - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | t | f | f | t | t | f | f | f + Publication testpub_foralltables + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | t | f | f | f | t | t | f | f | f (1 row) DROP TABLE testpub_tbl2; @@ -201,19 +201,19 @@ CREATE PUBLICATION testpub3 FOR TABLE testpub_tbl3; CREATE PUBLICATION testpub4 FOR TABLE ONLY testpub_tbl3; RESET client_min_messages; \dRp+ testpub3 - Publication testpub3 - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub3 + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables: "public.testpub_tbl3" "public.testpub_tbl3a" \dRp+ testpub4 - Publication testpub4 - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub4 + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables: "public.testpub_tbl3" @@ -234,10 +234,10 @@ UPDATE testpub_parted1 SET a = 1; -- only parent is listed as being in publication, not the partition ALTER PUBLICATION testpub_forparted ADD TABLE testpub_parted; \dRp+ testpub_forparted - Publication testpub_forparted - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub_forparted + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables: "public.testpub_parted" @@ -252,10 +252,10 @@ ALTER TABLE testpub_parted DETACH PARTITION testpub_parted1; UPDATE testpub_parted1 SET a = 1; ALTER PUBLICATION testpub_forparted SET (publish_via_partition_root = true); \dRp+ testpub_forparted - Publication testpub_forparted - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | t + Publication testpub_forparted + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | t Tables: "public.testpub_parted" @@ -284,10 +284,10 @@ SET client_min_messages = 'ERROR'; CREATE PUBLICATION testpub5 FOR TABLE testpub_rf_tbl1, testpub_rf_tbl2 WHERE (c <> 'test' AND d < 5) WITH (publish = 'insert'); RESET client_min_messages; \dRp+ testpub5 - Publication testpub5 - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | f | f | f | f + Publication testpub5 + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | f | f | f | f Tables: "public.testpub_rf_tbl1" "public.testpub_rf_tbl2" WHERE ((c <> 'test'::text) AND (d < 5)) @@ -300,10 +300,10 @@ Tables: ALTER PUBLICATION testpub5 ADD TABLE testpub_rf_tbl3 WHERE (e > 1000 AND e < 2000); \dRp+ testpub5 - Publication testpub5 - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | f | f | f | f + Publication testpub5 + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | f | f | f | f Tables: "public.testpub_rf_tbl1" "public.testpub_rf_tbl2" WHERE ((c <> 'test'::text) AND (d < 5)) @@ -319,10 +319,10 @@ Publications: ALTER PUBLICATION testpub5 DROP TABLE testpub_rf_tbl2; \dRp+ testpub5 - Publication testpub5 - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | f | f | f | f + Publication testpub5 + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | f | f | f | f Tables: "public.testpub_rf_tbl1" "public.testpub_rf_tbl3" WHERE ((e > 1000) AND (e < 2000)) @@ -330,10 +330,10 @@ Tables: -- remove testpub_rf_tbl1 and add testpub_rf_tbl3 again (another WHERE expression) ALTER PUBLICATION testpub5 SET TABLE testpub_rf_tbl3 WHERE (e > 300 AND e < 500); \dRp+ testpub5 - Publication testpub5 - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | f | f | f | f + Publication testpub5 + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | f | f | f | f Tables: "public.testpub_rf_tbl3" WHERE ((e > 300) AND (e < 500)) @@ -366,10 +366,10 @@ SET client_min_messages = 'ERROR'; CREATE PUBLICATION testpub_syntax1 FOR TABLE testpub_rf_tbl1, ONLY testpub_rf_tbl3 WHERE (e < 999) WITH (publish = 'insert'); RESET client_min_messages; \dRp+ testpub_syntax1 - Publication testpub_syntax1 - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | f | f | f | f + Publication testpub_syntax1 + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | f | f | f | f Tables: "public.testpub_rf_tbl1" "public.testpub_rf_tbl3" WHERE (e < 999) @@ -379,10 +379,10 @@ SET client_min_messages = 'ERROR'; CREATE PUBLICATION testpub_syntax2 FOR TABLE testpub_rf_tbl1, testpub_rf_schema1.testpub_rf_tbl5 WHERE (h < 999) WITH (publish = 'insert'); RESET client_min_messages; \dRp+ testpub_syntax2 - Publication testpub_syntax2 - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | f | f | f | f + Publication testpub_syntax2 + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | f | f | f | f Tables: "public.testpub_rf_tbl1" "testpub_rf_schema1.testpub_rf_tbl5" WHERE (h < 999) @@ -497,10 +497,10 @@ CREATE PUBLICATION testpub6 FOR TABLES IN SCHEMA testpub_rf_schema2; ALTER PUBLICATION testpub6 SET TABLES IN SCHEMA testpub_rf_schema2, TABLE testpub_rf_schema2.testpub_rf_tbl6 WHERE (i < 99); RESET client_min_messages; \dRp+ testpub6 - Publication testpub6 - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub6 + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables: "testpub_rf_schema2.testpub_rf_tbl6" WHERE (i < 99) Tables from schemas: @@ -714,10 +714,10 @@ CREATE PUBLICATION testpub_table_ins WITH (publish = 'insert, truncate'); RESET client_min_messages; ALTER PUBLICATION testpub_table_ins ADD TABLE testpub_tbl5 (a); -- ok \dRp+ testpub_table_ins - Publication testpub_table_ins - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | f | f | t | f + Publication testpub_table_ins + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | f | f | t | f Tables: "public.testpub_tbl5" (a) @@ -891,10 +891,10 @@ CREATE TABLE testpub_tbl_both_filters (a int, b int, c int, PRIMARY KEY (a,c)); ALTER TABLE testpub_tbl_both_filters REPLICA IDENTITY USING INDEX testpub_tbl_both_filters_pkey; ALTER PUBLICATION testpub_both_filters ADD TABLE testpub_tbl_both_filters (a,c) WHERE (c != 1); \dRp+ testpub_both_filters - Publication testpub_both_filters - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub_both_filters + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables: "public.testpub_tbl_both_filters" (a, c) WHERE (c <> 1) @@ -1099,10 +1099,10 @@ ERROR: relation "testpub_tbl1" is already member of publication "testpub_fortbl CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_tbl1; ERROR: publication "testpub_fortbl" already exists \dRp+ testpub_fortbl - Publication testpub_fortbl - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub_fortbl + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables: "pub_test.testpub_nopk" "public.testpub_tbl1" @@ -1140,10 +1140,10 @@ Publications: "testpub_fortbl" \dRp+ testpub_default - Publication testpub_default - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | f | f + Publication testpub_default + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | f | f Tables: "pub_test.testpub_nopk" "public.testpub_tbl1" @@ -1221,10 +1221,10 @@ REVOKE CREATE ON DATABASE regression FROM regress_publication_user2; DROP TABLE testpub_parted; DROP TABLE testpub_tbl1; \dRp+ testpub_default - Publication testpub_default - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | f | f + Publication testpub_default + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | f | f (1 row) -- fail - must be owner of publication @@ -1234,20 +1234,20 @@ ERROR: must be owner of publication testpub_default RESET ROLE; ALTER PUBLICATION testpub_default RENAME TO testpub_foo; \dRp testpub_foo - List of publications - Name | Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root --------------+--------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - testpub_foo | regress_publication_user | f | f | f | t | t | t | f | f + List of publications + Name | Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +-------------+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + testpub_foo | regress_publication_user | f | f | f | f | t | t | t | f | f (1 row) -- rename back to keep the rest simple ALTER PUBLICATION testpub_foo RENAME TO testpub_default; ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2; \dRp testpub_default - List of publications - Name | Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ------------------+---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - testpub_default | regress_publication_user2 | f | f | f | t | t | t | f | f + List of publications + Name | Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +-----------------+---------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + testpub_default | regress_publication_user2 | f | f | f | f | t | t | t | f | f (1 row) -- adding schemas and tables @@ -1263,19 +1263,19 @@ CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int); SET client_min_messages = 'ERROR'; CREATE PUBLICATION testpub1_forschema FOR TABLES IN SCHEMA pub_test1; \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables from schemas: "pub_test1" CREATE PUBLICATION testpub2_forschema FOR TABLES IN SCHEMA pub_test1, pub_test2, pub_test3; \dRp+ testpub2_forschema - Publication testpub2_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub2_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables from schemas: "pub_test1" "pub_test2" @@ -1289,44 +1289,44 @@ CREATE PUBLICATION testpub6_forschema FOR TABLES IN SCHEMA "CURRENT_SCHEMA", CUR CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"; RESET client_min_messages; \dRp+ testpub3_forschema - Publication testpub3_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub3_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables from schemas: "public" \dRp+ testpub4_forschema - Publication testpub4_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub4_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables from schemas: "CURRENT_SCHEMA" \dRp+ testpub5_forschema - Publication testpub5_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub5_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables from schemas: "CURRENT_SCHEMA" "public" \dRp+ testpub6_forschema - Publication testpub6_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub6_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables from schemas: "CURRENT_SCHEMA" "public" \dRp+ testpub_fortable - Publication testpub_fortable - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub_fortable + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables: "CURRENT_SCHEMA.CURRENT_SCHEMA" @@ -1360,10 +1360,10 @@ ERROR: schema "testpub_view" does not exist -- dropping the schema should reflect the change in publication DROP SCHEMA pub_test3; \dRp+ testpub2_forschema - Publication testpub2_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub2_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables from schemas: "pub_test1" "pub_test2" @@ -1371,20 +1371,20 @@ Tables from schemas: -- renaming the schema should reflect the change in publication ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed; \dRp+ testpub2_forschema - Publication testpub2_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub2_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables from schemas: "pub_test1_renamed" "pub_test2" ALTER SCHEMA pub_test1_renamed RENAME to pub_test1; \dRp+ testpub2_forschema - Publication testpub2_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub2_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables from schemas: "pub_test1" "pub_test2" @@ -1392,10 +1392,10 @@ Tables from schemas: -- alter publication add schema ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA pub_test2; \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables from schemas: "pub_test1" "pub_test2" @@ -1404,10 +1404,10 @@ Tables from schemas: ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA non_existent_schema; ERROR: schema "non_existent_schema" does not exist \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables from schemas: "pub_test1" "pub_test2" @@ -1416,10 +1416,10 @@ Tables from schemas: ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA pub_test1; ERROR: schema "pub_test1" is already member of publication "testpub1_forschema" \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables from schemas: "pub_test1" "pub_test2" @@ -1427,10 +1427,10 @@ Tables from schemas: -- alter publication drop schema ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test2; \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables from schemas: "pub_test1" @@ -1438,10 +1438,10 @@ Tables from schemas: ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test2; ERROR: tables from schema "pub_test2" are not part of the publication \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables from schemas: "pub_test1" @@ -1449,29 +1449,29 @@ Tables from schemas: ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA non_existent_schema; ERROR: schema "non_existent_schema" does not exist \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables from schemas: "pub_test1" -- drop all schemas ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test1; \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f (1 row) -- alter publication set multiple schema ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA pub_test1, pub_test2; \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables from schemas: "pub_test1" "pub_test2" @@ -1480,10 +1480,10 @@ Tables from schemas: ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA non_existent_schema; ERROR: schema "non_existent_schema" does not exist \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables from schemas: "pub_test1" "pub_test2" @@ -1492,10 +1492,10 @@ Tables from schemas: -- removing the duplicate schemas ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA pub_test1, pub_test1; \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables from schemas: "pub_test1" @@ -1574,18 +1574,18 @@ SET client_min_messages = 'ERROR'; CREATE PUBLICATION testpub3_forschema; RESET client_min_messages; \dRp+ testpub3_forschema - Publication testpub3_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub3_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f (1 row) ALTER PUBLICATION testpub3_forschema SET TABLES IN SCHEMA pub_test1; \dRp+ testpub3_forschema - Publication testpub3_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub3_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables from schemas: "pub_test1" @@ -1595,20 +1595,20 @@ CREATE PUBLICATION testpub_forschema_fortable FOR TABLES IN SCHEMA pub_test1, TA CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, TABLES IN SCHEMA pub_test1; RESET client_min_messages; \dRp+ testpub_forschema_fortable - Publication testpub_forschema_fortable - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub_forschema_fortable + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables: "pub_test2.tbl1" Tables from schemas: "pub_test1" \dRp+ testpub_fortable_forschema - Publication testpub_fortable_forschema - Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | f | f | t | t | t | t | f + Publication testpub_fortable_forschema + Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+---------- + regress_publication_user | f | f | f | f | t | t | t | t | f Tables: "pub_test2.tbl1" Tables from schemas: diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out index 9c52890f1d..c2bd4d3f62 100644 --- a/src/test/regress/expected/subscription.out +++ b/src/test/regress/expected/subscription.out @@ -115,18 +115,18 @@ CREATE SUBSCRIPTION regress_testsub4 CONNECTION 'dbname=regress_doesnotexist' PU WARNING: subscription was created, but is not connected HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription. \dRs+ regress_testsub4 - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN -------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+---------- - regress_testsub4 | regress_subscription_user | f | {testpub} | f | off | d | f | none | f | off | dbname=regress_doesnotexist | 0/0 + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN +------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+---------- + regress_testsub4 | regress_subscription_user | f | {testpub} | f | off | d | f | none | f | t | off | dbname=regress_doesnotexist | 0/0 (1 row) ALTER SUBSCRIPTION regress_testsub4 SET (origin = any); \dRs+ regress_testsub4 - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN -------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+---------- - regress_testsub4 | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | off | dbname=regress_doesnotexist | 0/0 + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN +------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+---------- + regress_testsub4 | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | t | off | dbname=regress_doesnotexist | 0/0 (1 row) DROP SUBSCRIPTION regress_testsub3; @@ -144,10 +144,10 @@ ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar'; ERROR: invalid connection string syntax: missing "=" after "foobar" in connection info string \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN ------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+---------- - regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | off | dbname=regress_doesnotexist | 0/0 + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN +-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+---------- + regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | t | off | dbname=regress_doesnotexist | 0/0 (1 row) ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false); @@ -166,10 +166,10 @@ ERROR: unrecognized subscription parameter: "create_slot" -- ok ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/12345'); \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN ------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+------------------------------+---------- - regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | off | d | f | any | f | off | dbname=regress_doesnotexist2 | 0/12345 + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN +-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+------------------------------+---------- + regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | off | d | f | any | f | t | off | dbname=regress_doesnotexist2 | 0/12345 (1 row) -- ok - with lsn = NONE @@ -178,10 +178,10 @@ ALTER SUBSCRIPTION regress_testsub SKIP (lsn = NONE); ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/0'); ERROR: invalid WAL location (LSN): 0/0 \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN ------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+------------------------------+---------- - regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | off | d | f | any | f | off | dbname=regress_doesnotexist2 | 0/0 + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN +-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+------------------------------+---------- + regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | off | d | f | any | f | t | off | dbname=regress_doesnotexist2 | 0/0 (1 row) BEGIN; @@ -213,10 +213,10 @@ ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar); ERROR: invalid value for parameter "synchronous_commit": "foobar" HINT: Available values: local, remote_write, remote_apply, on, off. \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN ----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+------------------------------+---------- - regress_testsub_foo | regress_subscription_user | f | {testpub2,testpub3} | f | off | d | f | any | f | local | dbname=regress_doesnotexist2 | 0/0 + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN +---------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+------------------------------+---------- + regress_testsub_foo | regress_subscription_user | f | {testpub2,testpub3} | f | off | d | f | any | f | t | local | dbname=regress_doesnotexist2 | 0/0 (1 row) -- rename back to keep the rest simple @@ -245,19 +245,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB WARNING: subscription was created, but is not connected HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription. \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN ------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+---------- - regress_testsub | regress_subscription_user | f | {testpub} | t | off | d | f | any | f | off | dbname=regress_doesnotexist | 0/0 + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN +-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+---------- + regress_testsub | regress_subscription_user | f | {testpub} | t | off | d | f | any | f | t | off | dbname=regress_doesnotexist | 0/0 (1 row) ALTER SUBSCRIPTION regress_testsub SET (binary = false); ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE); \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN ------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+---------- - regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | off | dbname=regress_doesnotexist | 0/0 + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN +-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+---------- + regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | t | off | dbname=regress_doesnotexist | 0/0 (1 row) DROP SUBSCRIPTION regress_testsub; @@ -269,27 +269,27 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB WARNING: subscription was created, but is not connected HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription. \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN ------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+---------- - regress_testsub | regress_subscription_user | f | {testpub} | f | on | d | f | any | f | off | dbname=regress_doesnotexist | 0/0 + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN +-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+---------- + regress_testsub | regress_subscription_user | f | {testpub} | f | on | d | f | any | f | t | off | dbname=regress_doesnotexist | 0/0 (1 row) ALTER SUBSCRIPTION regress_testsub SET (streaming = parallel); \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN ------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+---------- - regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | f | off | dbname=regress_doesnotexist | 0/0 + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN +-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+---------- + regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | f | t | off | dbname=regress_doesnotexist | 0/0 (1 row) ALTER SUBSCRIPTION regress_testsub SET (streaming = false); ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE); \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN ------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+---------- - regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | off | dbname=regress_doesnotexist | 0/0 + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN +-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+---------- + regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | t | off | dbname=regress_doesnotexist | 0/0 (1 row) -- fail - publication already exists @@ -304,10 +304,10 @@ ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refr ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false); ERROR: publication "testpub1" is already in subscription "regress_testsub" \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN ------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+---------- - regress_testsub | regress_subscription_user | f | {testpub,testpub1,testpub2} | f | off | d | f | any | f | off | dbname=regress_doesnotexist | 0/0 + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN +-----------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+---------- + regress_testsub | regress_subscription_user | f | {testpub,testpub1,testpub2} | f | off | d | f | any | f | t | off | dbname=regress_doesnotexist | 0/0 (1 row) -- fail - publication used more than once @@ -322,10 +322,10 @@ ERROR: publication "testpub3" is not in subscription "regress_testsub" -- ok - delete publications ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false); \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN ------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+---------- - regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | off | dbname=regress_doesnotexist | 0/0 + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN +-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+---------- + regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | t | off | dbname=regress_doesnotexist | 0/0 (1 row) DROP SUBSCRIPTION regress_testsub; @@ -361,10 +361,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB WARNING: subscription was created, but is not connected HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription. \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN ------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+---------- - regress_testsub | regress_subscription_user | f | {testpub} | f | off | p | f | any | f | off | dbname=regress_doesnotexist | 0/0 + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN +-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+---------- + regress_testsub | regress_subscription_user | f | {testpub} | f | off | p | f | any | f | t | off | dbname=regress_doesnotexist | 0/0 (1 row) --fail - alter of two_phase option not supported. @@ -373,10 +373,10 @@ ERROR: unrecognized subscription parameter: "two_phase" -- but can alter streaming when two_phase enabled ALTER SUBSCRIPTION regress_testsub SET (streaming = true); \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN ------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+---------- - regress_testsub | regress_subscription_user | f | {testpub} | f | on | p | f | any | f | off | dbname=regress_doesnotexist | 0/0 + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN +-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+---------- + regress_testsub | regress_subscription_user | f | {testpub} | f | on | p | f | any | f | t | off | dbname=regress_doesnotexist | 0/0 (1 row) ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE); @@ -386,10 +386,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB WARNING: subscription was created, but is not connected HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription. \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN ------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+---------- - regress_testsub | regress_subscription_user | f | {testpub} | f | on | p | f | any | f | off | dbname=regress_doesnotexist | 0/0 + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN +-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+---------- + regress_testsub | regress_subscription_user | f | {testpub} | f | on | p | f | any | f | t | off | dbname=regress_doesnotexist | 0/0 (1 row) ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE); @@ -402,18 +402,18 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB WARNING: subscription was created, but is not connected HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription. \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN ------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+---------- - regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | off | dbname=regress_doesnotexist | 0/0 + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN +-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+---------- + regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | t | off | dbname=regress_doesnotexist | 0/0 (1 row) ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true); \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN ------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+---------- - regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | t | any | f | off | dbname=regress_doesnotexist | 0/0 + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN +-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+---------- + regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | t | any | f | t | off | dbname=regress_doesnotexist | 0/0 (1 row) ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE); -- 2.30.0.windows.2