>From e6c10991d8582d220953cf2f4d23dee1b3ef47d6 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Fri, 25 Apr 2014 16:43:53 -0300 Subject: [PATCH 10/29] deparse: Support ALTER .. {RENAME/OWNER/SCHEMA} --- src/backend/commands/alter.c | 1 - src/backend/tcop/deparse_utility.c | 483 ++++++++++++++++++++++++++++++++++++- src/backend/utils/adt/regproc.c | 101 +++++--- src/include/utils/builtins.h | 1 + 4 files changed, 553 insertions(+), 33 deletions(-) diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index 3ddd7ec..d28758c 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -446,7 +446,6 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt, Relation relation; Oid classId; Oid nspOid; - ObjectAddress address; address = get_object_address(stmt->objectType, stmt->object, diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index a586e55..c9a89b5 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -144,6 +144,7 @@ static void append_integer_object(ObjTree *tree, char *name, int64 value); static void append_float_object(ObjTree *tree, char *name, float8 value); static inline void append_premade_object(ObjTree *tree, ObjElem *elem); static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state); +static const char *stringify_objtype(ObjectType objtype); /* * Allocate a new object tree to store parameter values. @@ -1794,6 +1795,478 @@ deparse_CreateRangeStmt(Oid objectId, Node *parsetree) return range; } +/* + * Return the given object type as a string. + */ +static const char * +stringify_objtype(ObjectType objtype) +{ + switch (objtype) + { + case OBJECT_AGGREGATE: + return "AGGREGATE"; + case OBJECT_CAST: + return "CAST"; + case OBJECT_COLUMN: + return "COLUMN"; + case OBJECT_COLLATION: + return "COLLATION"; + case OBJECT_CONVERSION: + return "CONVERSION"; + case OBJECT_DATABASE: + return "DATABASE"; + case OBJECT_DOMAIN: + return "DOMAIN"; + case OBJECT_EVENT_TRIGGER: + return "EVENT TRIGGER"; + case OBJECT_EXTENSION: + return "EXTENSION"; + case OBJECT_FDW: + return "FOREIGN DATA WRAPPER"; + case OBJECT_FOREIGN_SERVER: + return "SERVER"; + case OBJECT_FOREIGN_TABLE: + return "FOREIGN TABLE"; + case OBJECT_FUNCTION: + return "FUNCTION"; + case OBJECT_INDEX: + return "INDEX"; + case OBJECT_LANGUAGE: + return "LANGUAGE"; + case OBJECT_LARGEOBJECT: + return "LARGE OBJECT"; + case OBJECT_MATVIEW: + return "MATERIALIZED VIEW"; + case OBJECT_OPCLASS: + return "OPERATOR CLASS"; + case OBJECT_OPERATOR: + return "OPERATOR"; + case OBJECT_OPFAMILY: + return "OPERATOR FAMILY"; + case OBJECT_ROLE: + return "ROLE"; + case OBJECT_RULE: + return "RULE"; + case OBJECT_SCHEMA: + return "SCHEMA"; + case OBJECT_SEQUENCE: + return "SEQUENCE"; + case OBJECT_TABLE: + return "TABLE"; + case OBJECT_TABLESPACE: + return "TABLESPACE"; + case OBJECT_TRIGGER: + return "TRIGGER"; + case OBJECT_TSCONFIGURATION: + return "TEXT SEARCH CONFIGURATION"; + case OBJECT_TSDICTIONARY: + return "TEXT SEARCH DICTIONARY"; + case OBJECT_TSPARSER: + return "TEXT SEARCH PARSER"; + case OBJECT_TSTEMPLATE: + return "TEXT SEARCH TEMPLATE"; + case OBJECT_TYPE: + return "TYPE"; + case OBJECT_USER_MAPPING: + return "USER MAPPING"; + case OBJECT_VIEW: + return "VIEW"; + + default: + elog(ERROR, "unsupported object type %d", objtype); + } +} + +static ObjTree * +deparse_RenameStmt(ObjectAddress address, Node *parsetree) +{ + RenameStmt *node = (RenameStmt *) parsetree; + ObjTree *renameStmt; + char *fmtstr; + Relation relation; + Oid schemaId; + + /* + * FIXME --- this code is missing support for inheritance behavioral flags, + * i.e. the "*" and ONLY elements. + */ + + /* + * In a 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. + * + * XXX what if there's another event trigger running concurrently that + * renames the schema or moves the object to another schema? Seems + * pretty far-fetched, but possible nonetheless. + */ + switch (node->renameType) + { + case OBJECT_TABLE: + case OBJECT_SEQUENCE: + case OBJECT_VIEW: + case OBJECT_MATVIEW: + case OBJECT_INDEX: + case OBJECT_FOREIGN_TABLE: + fmtstr = psprintf("ALTER %s %%{if_exists}s %%{identity}D RENAME TO %%{newname}I", + stringify_objtype(node->renameType)); + relation = relation_open(address.objectId, AccessShareLock); + schemaId = RelationGetNamespace(relation); + renameStmt = new_objtree_VA(fmtstr, 0); + append_object_object(renameStmt, "identity", + new_objtree_for_qualname(schemaId, + node->relation->relname)); + append_string_object(renameStmt, "if_exists", + node->missing_ok ? "IF EXISTS" : ""); + relation_close(relation, AccessShareLock); + break; + + case OBJECT_ATTRIBUTE: + case OBJECT_COLUMN: + relation = relation_open(address.objectId, AccessShareLock); + schemaId = RelationGetNamespace(relation); + + if (node->renameType == OBJECT_ATTRIBUTE) + { + fmtstr = "ALTER TYPE %{identity}D RENAME ATTRIBUTE %{colname}I TO %{newname}I %{cascade}s"; + renameStmt = new_objtree_VA(fmtstr, 0); + append_object_object(renameStmt, "cascade", + new_objtree_VA("CASCADE", 1, + "present", ObjTypeBool, + node->behavior == DROP_CASCADE)); + } + else + { + fmtstr = psprintf("ALTER %s %%{if_exists}s %%{identity}D RENAME COLUMN %%{colname}I TO %%{newname}I", + stringify_objtype(node->relationType)); + renameStmt = new_objtree_VA(fmtstr, 0); + } + + append_object_object(renameStmt, "identity", + new_objtree_for_qualname(schemaId, + node->relation->relname)); + append_string_object(renameStmt, "colname", node->subname); + + /* composite types do not support IF EXISTS */ + if (node->relationType != OBJECT_TYPE) + append_string_object(renameStmt, "if_exists", + node->missing_ok ? "IF EXISTS" : ""); + relation_close(relation, AccessShareLock); + + break; + + case OBJECT_SCHEMA: + { + renameStmt = + new_objtree_VA("ALTER SCHEMA %{identity}I RENAME TO %{newname}I", + 0); + append_string_object(renameStmt, "identity", node->subname); + } + break; + + case OBJECT_FDW: + case OBJECT_LANGUAGE: + case OBJECT_FOREIGN_SERVER: + { + fmtstr = psprintf("ALTER %s %%{identity}I RENAME TO %%{newname}I", + stringify_objtype(node->renameType)); + renameStmt = new_objtree_VA(fmtstr, 0); + append_string_object(renameStmt, "identity", + strVal(linitial(node->object))); + } + break; + + case OBJECT_COLLATION: + case OBJECT_CONVERSION: + case OBJECT_DOMAIN: + case OBJECT_TSDICTIONARY: + case OBJECT_TSPARSER: + case OBJECT_TSTEMPLATE: + case OBJECT_TSCONFIGURATION: + case OBJECT_TYPE: + { + HeapTuple objTup; + Relation catalog; + Datum objnsp; + bool isnull; + AttrNumber Anum_namespace; + ObjTree *ident; + + /* obtain object tuple */ + catalog = relation_open(address.classId, AccessShareLock); + objTup = get_catalog_object_by_oid(catalog, 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"); + + fmtstr = psprintf("ALTER %s %%{identity}D RENAME TO %%{newname}I", + stringify_objtype(node->renameType)); + renameStmt = new_objtree_VA(fmtstr, 0); + ident = new_objtree_for_qualname(DatumGetObjectId(objnsp), + strVal(llast(node->object))); + append_object_object(renameStmt, "identity", ident); + + relation_close(catalog, AccessShareLock); + } + break; + + case OBJECT_OPCLASS: + case OBJECT_OPFAMILY: + { + HeapTuple objTup; + HeapTuple amTup; + Relation catalog; + Datum objnsp; + bool isnull; + AttrNumber Anum_namespace; + Oid amoid; + ObjTree *ident; + + /* obtain object tuple */ + catalog = relation_open(address.classId, AccessShareLock); + objTup = get_catalog_object_by_oid(catalog, 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"); + + /* obtain AM tuple */ + if (node->renameType == OBJECT_OPCLASS) + amoid = ((Form_pg_opclass) GETSTRUCT(objTup))->opcmethod; + else + amoid = ((Form_pg_opfamily) GETSTRUCT(objTup))->opfmethod; + amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(amoid)); + if (!HeapTupleIsValid(amTup)) + elog(ERROR, "cache lookup failed for access method %u", amoid); + + + fmtstr = psprintf("ALTER %s %%{identity}D USING %%{amname}I RENAME TO %%{newname}I", + stringify_objtype(node->renameType)); + renameStmt = new_objtree_VA(fmtstr, 0); + + /* add the identity clauses */ + ident = new_objtree_for_qualname(DatumGetObjectId(objnsp), + strVal(llast(node->object))); + append_object_object(renameStmt, "identity", ident); + append_string_object(renameStmt, "amname", + pstrdup(NameStr(((Form_pg_am) GETSTRUCT(amTup))->amname))); + + ReleaseSysCache(amTup); + relation_close(catalog, AccessShareLock); + } + break; + + case OBJECT_AGGREGATE: + case OBJECT_FUNCTION: + { + char *ident; + HeapTuple proctup; + Form_pg_proc procform; + + proctup = SearchSysCache1(PROCOID, + ObjectIdGetDatum(address.objectId)); + if (!HeapTupleIsValid(proctup)) + elog(ERROR, "cache lookup failed for procedure %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(node->object))), + format_procedure_args(address.objectId, true)); + + fmtstr = psprintf("ALTER %s %%{identity}s RENAME TO %%{newname}I", + stringify_objtype(node->renameType)); + renameStmt = new_objtree_VA(fmtstr, 1, + "identity", ObjTypeString, ident); + + ReleaseSysCache(proctup); + } + break; + + case OBJECT_TABCONSTRAINT: + case OBJECT_DOMCONSTRAINT: + { + HeapTuple conTup; + Form_pg_constraint constrForm; + ObjTree *ident; + + conTup = SearchSysCache1(CONSTROID, address.objectId); + constrForm = (Form_pg_constraint) GETSTRUCT(conTup); + + if (node->renameType == OBJECT_TABCONSTRAINT) + { + fmtstr = "ALTER TABLE %{identity}D RENAME CONSTRAINT %{conname}I TO %{newname}I"; + ident = new_objtree_for_qualname_id(RelationRelationId, + constrForm->conrelid); + } + else + { + fmtstr = "ALTER DOMAIN %{identity}D RENAME CONSTRAINT %{conname}I TO %{newname}I"; + ident = new_objtree_for_qualname_id(TypeRelationId, + constrForm->contypid); + } + renameStmt = new_objtree_VA(fmtstr, 2, + "conname", ObjTypeString, node->subname, + "identity", ObjTypeObject, ident); + ReleaseSysCache(conTup); + } + 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, address.objectId); + rewrForm = (Form_pg_rewrite) GETSTRUCT(rewrTup); + + renameStmt = new_objtree_VA("ALTER RULE %{rulename}I ON %{identity}D RENAME TO %{newname}I", + 0); + append_string_object(renameStmt, "rulename", node->subname); + append_object_object(renameStmt, "identity", + new_objtree_for_qualname_id(RelationRelationId, + rewrForm->ev_class)); + 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, address.objectId); + trigForm = (Form_pg_trigger) GETSTRUCT(trigTup); + + renameStmt = new_objtree_VA("ALTER TRIGGER %{triggername}I ON %{identity}D RENAME TO %{newname}I", + 0); + append_string_object(renameStmt, "triggername", node->subname); + append_object_object(renameStmt, "identity", + new_objtree_for_qualname_id(RelationRelationId, + trigForm->tgrelid)); + relation_close(pg_trigger, 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, ObjectIdAttributeNumber, + 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 %u", address.objectId); + polForm = (Form_pg_policy) GETSTRUCT(polTup); + + renameStmt = new_objtree_VA("ALTER POLICY %{if_exists}s %{policyname}I on %{identity}D RENAME TO %{newname}I", + 0); + append_string_object(renameStmt, "policyname", node->subname); + append_object_object(renameStmt, "identity", + new_objtree_for_qualname_id(RelationRelationId, + polForm->polrelid)); + append_string_object(renameStmt, "if_exists", + node->missing_ok ? "IF EXISTS" : ""); + systable_endscan(scan); + relation_close(pg_policy, AccessShareLock); + } + break; + + default: + elog(ERROR, "unsupported object type %d", node->renameType); + } + + append_string_object(renameStmt, "newname", node->newname); + + return renameStmt; +} + +/* + * deparse an ALTER ... SET SCHEMA command. + */ +static ObjTree * +deparse_AlterObjectSchemaStmt(ObjectAddress address, Node *parsetree, + ObjectAddress oldschema) +{ + AlterObjectSchemaStmt *node = (AlterObjectSchemaStmt *) parsetree; + ObjTree *alterStmt; + char *fmt; + char *identity; + char *newschema; + char *oldschname; + char *ident; + + newschema = node->newschema; + + fmt = psprintf("ALTER %s %%{identity}s SET SCHEMA %%{newschema}I", + stringify_objtype(node->objectType)); + alterStmt = new_objtree_VA(fmt, 0); + append_string_object(alterStmt, "newschema", newschema); + + /* + * Since the command has already taken place from the point of view of + * catalogs, getObjectIdentity returns the object name with the already + * changed schema. The output of our deparsing must return the original + * schema name however, so we chop the schema name off the identity string + * and then prepend the quoted schema name. + * + * XXX This is pretty clunky. Can we do better? + */ + identity = getObjectIdentity(&address); + oldschname = get_namespace_name(oldschema.objectId); + if (!oldschname) + elog(ERROR, "cache lookup failed for schema with OID %u", oldschema.objectId); + ident = psprintf("%s%s", + quote_identifier(oldschname), + identity + strlen(quote_identifier(newschema))); + append_string_object(alterStmt, "identity", ident); + + return alterStmt; +} + +static ObjTree * +deparse_AlterOwnerStmt(ObjectAddress address, Node *parsetree) +{ + AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree; + ObjTree *ownerStmt; + char *fmt; + + fmt = psprintf("ALTER %s %%{identity}s OWNER TO %%{newowner}I", + stringify_objtype(node->objectType)); + ownerStmt = new_objtree_VA(fmt, 0); + append_string_object(ownerStmt, "newowner", + get_rolespec_name(node->newowner)); + + append_string_object(ownerStmt, "identity", + getObjectIdentity(&address)); + + return ownerStmt; +} + static inline ObjElem * deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata) { @@ -2241,8 +2714,6 @@ deparse_RuleStmt(Oid objectId, Node *parsetree) return ruleStmt; } - - /* * deparse_CreateSchemaStmt * deparse a CreateSchemaStmt @@ -2510,15 +2981,17 @@ deparse_simple_command(StashedCommand *cmd) break; case T_RenameStmt: - elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + command = deparse_RenameStmt(cmd->d.simple.address, parsetree); break; case T_AlterObjectSchemaStmt: - elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + command = deparse_AlterObjectSchemaStmt(cmd->d.simple.address, + parsetree, + cmd->d.simple.secondaryObject); break; case T_AlterOwnerStmt: - elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + command = deparse_AlterOwnerStmt(cmd->d.simple.address, parsetree); break; case T_CommentStmt: diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c index 3d1bb32..68447ca 100644 --- a/src/backend/utils/adt/regproc.c +++ b/src/backend/utils/adt/regproc.c @@ -42,7 +42,11 @@ #include "utils/tqual.h" static char *format_operator_internal(Oid operator_oid, bool force_qualify); -static char *format_procedure_internal(Oid procedure_oid, bool force_qualify); +static char *format_procedure_internal(Oid procedure_oid, bool force_qualify, + bool args_only); +static void format_procedure_args_internal(Form_pg_proc procform, + StringInfo buf, bool force_qualify); + static void parseNameAndArgTypes(const char *string, bool allowNone, List **names, int *nargs, Oid *argtypes); @@ -363,13 +367,36 @@ to_regprocedure(PG_FUNCTION_ARGS) char * format_procedure(Oid procedure_oid) { - return format_procedure_internal(procedure_oid, false); + return format_procedure_internal(procedure_oid, false, false); } char * format_procedure_qualified(Oid procedure_oid) { - return format_procedure_internal(procedure_oid, true); + return format_procedure_internal(procedure_oid, true, false); +} + +/* + * 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; } /* @@ -380,7 +407,7 @@ format_procedure_qualified(Oid procedure_oid) * qualified if the function is not in path. */ static char * -format_procedure_internal(Oid procedure_oid, bool force_qualify) +format_procedure_internal(Oid procedure_oid, bool force_qualify, bool args_only) { char *result; HeapTuple proctup; @@ -391,8 +418,6 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify) { Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup); char *proname = NameStr(procform->proname); - int nargs = procform->pronargs; - int i; char *nspname; StringInfoData buf; @@ -400,29 +425,24 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify) initStringInfo(&buf); - /* - * Would this proc be found (given the right args) by regprocedurein? - * If not, or if caller requests it, we need to qualify it. - */ - if (!force_qualify && FunctionIsVisible(procedure_oid)) - nspname = NULL; - else - nspname = get_namespace_name(procform->pronamespace); - - appendStringInfo(&buf, "%s(", - quote_qualified_identifier(nspname, proname)); - for (i = 0; i < nargs; i++) + if (!args_only) { - Oid thisargtype = procform->proargtypes.values[i]; - - if (i > 0) - appendStringInfoChar(&buf, ','); - appendStringInfoString(&buf, - force_qualify ? - format_type_be_qualified(thisargtype) : - format_type_be(thisargtype)); + /* + * Would this proc be found (given the right args) by + * regprocedurein? If not, or if caller requests it, we need to + * qualify it. + */ + if (!force_qualify && FunctionIsVisible(procedure_oid)) + nspname = NULL; + else + nspname = get_namespace_name(procform->pronamespace); + + appendStringInfo(&buf, "%s", + quote_qualified_identifier(nspname, proname)); } - appendStringInfoChar(&buf, ')'); + + /* add the attributes */ + format_procedure_args_internal(procform, &buf, force_qualify); result = buf.data; @@ -439,6 +459,33 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify) } /* + * Append the parenthised 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; + int nargs = procform->pronargs; + + appendStringInfoChar(buf, '('); + for (i = 0; i < nargs; i++) + { + Oid thisargtype = procform->proargtypes.values[i]; + + if (i > 0) + appendStringInfoChar(buf, ','); + appendStringInfoString(buf, + force_qualify ? + format_type_be_qualified(thisargtype) : + format_type_be(thisargtype)); + } + appendStringInfoChar(buf, ')'); +} + +/* * Output a objname/objargs representation for the procedure with the * given OID. If it doesn't exist, an error is thrown. * diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 470a325..dd539fe 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -644,6 +644,7 @@ extern char *format_procedure(Oid procedure_oid); extern char *format_procedure_qualified(Oid procedure_oid); extern void format_procedure_parts(Oid operator_oid, List **objnames, List **objargs); +extern char *format_procedure_args(Oid procedure_oid, bool force_qualify); extern char *format_operator(Oid operator_oid); extern char *format_operator_qualified(Oid operator_oid); extern void format_operator_parts(Oid operator_oid, List **objnames, -- 2.1.4