diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2011-02-08 16:08:41 -0500 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2011-02-08 16:13:22 -0500 |
commit | d9572c4e3b474031060189050e14ef384b94e001 (patch) | |
tree | 07646762f4086b94a69b9fc215734d2bccade5db /src | |
parent | 414c5a2ea65cbd38d79ffdf9b1fde7cc75c134e0 (diff) | |
download | postgresql-d9572c4e3b474031060189050e14ef384b94e001.tar.gz postgresql-d9572c4e3b474031060189050e14ef384b94e001.zip |
Core support for "extensions", which are packages of SQL objects.
This patch adds the server infrastructure to support extensions.
There is still one significant loose end, namely how to make it play nice
with pg_upgrade, so I am not yet committing the changes that would make
all the contrib modules depend on this feature.
In passing, fix a disturbingly large amount of breakage in
AlterObjectNamespace() and callers.
Dimitri Fontaine, reviewed by Anssi Kääriäinen,
Itagaki Takahiro, Tom Lane, and numerous others
Diffstat (limited to 'src')
57 files changed, 2908 insertions, 235 deletions
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index 7cffde17692..45aca8dd7f7 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -36,7 +36,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\ pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \ pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \ pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \ - pg_ts_parser.h pg_ts_template.h \ + pg_ts_parser.h pg_ts_template.h pg_extension.h \ pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \ pg_foreign_table.h \ pg_default_acl.h pg_seclabel.h pg_collation.h \ diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index ba0ea178ac0..5c5f750a069 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -34,6 +34,7 @@ #include "catalog/pg_database.h" #include "catalog/pg_default_acl.h" #include "catalog/pg_depend.h" +#include "catalog/pg_extension.h" #include "catalog/pg_foreign_data_wrapper.h" #include "catalog/pg_foreign_server.h" #include "catalog/pg_foreign_table.h" @@ -56,6 +57,7 @@ #include "commands/comment.h" #include "commands/dbcommands.h" #include "commands/defrem.h" +#include "commands/extension.h" #include "commands/proclang.h" #include "commands/schemacmds.h" #include "commands/seclabel.h" @@ -93,6 +95,7 @@ typedef struct #define DEPFLAG_NORMAL 0x0002 /* reached via normal dependency */ #define DEPFLAG_AUTO 0x0004 /* reached via auto dependency */ #define DEPFLAG_INTERNAL 0x0008 /* reached via internal dependency */ +#define DEPFLAG_EXTENSION 0x0010 /* reached via extension dependency */ /* expansible list of ObjectAddresses */ @@ -153,8 +156,8 @@ static const Oid object_classes[MAX_OCLASS] = { ForeignDataWrapperRelationId, /* OCLASS_FDW */ ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */ UserMappingRelationId, /* OCLASS_USER_MAPPING */ - ForeignTableRelationId, /* OCLASS_FOREIGN_TABLE */ - DefaultAclRelationId /* OCLASS_DEFACL */ + DefaultAclRelationId, /* OCLASS_DEFACL */ + ExtensionRelationId /* OCLASS_EXTENSION */ }; @@ -551,10 +554,12 @@ findDependentObjects(const ObjectAddress *object, /* no problem */ break; case DEPENDENCY_INTERNAL: + case DEPENDENCY_EXTENSION: /* * This object is part of the internal implementation of - * another object. We have three cases: + * another object, or is part of the extension that is the + * other object. We have three cases: * * 1. At the outermost recursion level, disallow the DROP. (We * just ereport here, rather than proceeding, since no other @@ -726,6 +731,9 @@ findDependentObjects(const ObjectAddress *object, case DEPENDENCY_INTERNAL: subflags = DEPFLAG_INTERNAL; break; + case DEPENDENCY_EXTENSION: + subflags = DEPFLAG_EXTENSION; + break; case DEPENDENCY_PIN: /* @@ -836,10 +844,12 @@ reportDependentObjects(const ObjectAddresses *targetObjects, /* * If, at any stage of the recursive search, we reached the object via - * an AUTO or INTERNAL dependency, then it's okay to delete it even in - * RESTRICT mode. + * an AUTO, INTERNAL, or EXTENSION dependency, then it's okay to + * delete it even in RESTRICT mode. */ - if (extra->flags & (DEPFLAG_AUTO | DEPFLAG_INTERNAL)) + if (extra->flags & (DEPFLAG_AUTO | + DEPFLAG_INTERNAL | + DEPFLAG_EXTENSION)) { /* * auto-cascades are reported at DEBUG2, not msglevel. We don't @@ -1154,6 +1164,10 @@ doDeletion(const ObjectAddress *object) RemoveDefaultACLById(object->objectId); break; + case OCLASS_EXTENSION: + RemoveExtensionById(object->objectId); + break; + default: elog(ERROR, "unrecognized object class: %u", object->classId); @@ -2074,12 +2088,11 @@ getObjectClass(const ObjectAddress *object) case UserMappingRelationId: return OCLASS_USER_MAPPING; - case ForeignTableRelationId: - Assert(object->objectSubId == 0); - return OCLASS_FOREIGN_TABLE; - case DefaultAclRelationId: return OCLASS_DEFACL; + + case ExtensionRelationId: + return OCLASS_EXTENSION; } /* shouldn't get here */ @@ -2687,6 +2700,18 @@ getObjectDescription(const ObjectAddress *object) break; } + case OCLASS_EXTENSION: + { + char *extname; + + extname = get_extension_name(object->objectId); + if (!extname) + elog(ERROR, "cache lookup failed for extension %u", + object->objectId); + appendStringInfo(&buffer, _("extension %s"), extname); + break; + } + default: appendStringInfo(&buffer, "unrecognized object %u %u %d", object->classId, diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 14c69f3faa8..d9b272a7122 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -1160,7 +1160,8 @@ heap_create_with_catalog(const char *relname, * entry, so we needn't record them here. Likewise, TOAST tables don't * need a namespace dependency (they live in a pinned namespace) nor an * owner dependency (they depend indirectly through the parent table), nor - * should they have any ACL entries. + * should they have any ACL entries. The same applies for extension + * dependencies. * * Also, skip this in bootstrap mode, since we don't make dependencies * while bootstrapping. @@ -1182,6 +1183,8 @@ heap_create_with_catalog(const char *relname, recordDependencyOnOwner(RelationRelationId, relid, ownerid); + recordDependencyOnCurrentExtension(&myself); + if (reloftypeid) { referenced.classId = TypeRelationId; diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index c4608f7f173..82989acc088 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -28,6 +28,7 @@ #include "catalog/pg_constraint.h" #include "catalog/pg_conversion.h" #include "catalog/pg_database.h" +#include "catalog/pg_extension.h" #include "catalog/pg_language.h" #include "catalog/pg_largeobject.h" #include "catalog/pg_largeobject_metadata.h" @@ -46,6 +47,7 @@ #include "catalog/pg_type.h" #include "commands/dbcommands.h" #include "commands/defrem.h" +#include "commands/extension.h" #include "commands/proclang.h" #include "commands/tablespace.h" #include "commands/trigger.h" @@ -129,6 +131,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs, address = get_object_address_relobject(objtype, objname, &relation); break; case OBJECT_DATABASE: + case OBJECT_EXTENSION: case OBJECT_TABLESPACE: case OBJECT_ROLE: case OBJECT_SCHEMA: @@ -267,6 +270,9 @@ get_object_address_unqualified(ObjectType objtype, List *qualname) case OBJECT_DATABASE: msg = gettext_noop("database name cannot be qualified"); break; + case OBJECT_EXTENSION: + msg = gettext_noop("extension name cannot be qualified"); + break; case OBJECT_TABLESPACE: msg = gettext_noop("tablespace name cannot be qualified"); break; @@ -299,6 +305,11 @@ get_object_address_unqualified(ObjectType objtype, List *qualname) address.objectId = get_database_oid(name, false); address.objectSubId = 0; break; + case OBJECT_EXTENSION: + address.classId = ExtensionRelationId; + address.objectId = get_extension_oid(name, false); + address.objectSubId = 0; + break; case OBJECT_TABLESPACE: address.classId = TableSpaceRelationId; address.objectId = get_tablespace_oid(name, false); @@ -643,6 +654,9 @@ object_exists(ObjectAddress address) case TSConfigRelationId: cache = TSCONFIGOID; break; + case ExtensionRelationId: + indexoid = ExtensionOidIndexId; + break; default: elog(ERROR, "unrecognized classid: %u", address.classId); } diff --git a/src/backend/catalog/pg_conversion.c b/src/backend/catalog/pg_conversion.c index b2f66d75ac5..1ef6a9d24e2 100644 --- a/src/backend/catalog/pg_conversion.c +++ b/src/backend/catalog/pg_conversion.c @@ -132,6 +132,9 @@ ConversionCreate(const char *conname, Oid connamespace, recordDependencyOnOwner(ConversionRelationId, HeapTupleGetOid(tup), conowner); + /* dependency on extension */ + recordDependencyOnCurrentExtension(&myself); + /* Post creation hook for new conversion */ InvokeObjectAccessHook(OAT_POST_CREATE, ConversionRelationId, HeapTupleGetOid(tup), 0); diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c index 370051d91e3..b2ce148d625 100644 --- a/src/backend/catalog/pg_depend.c +++ b/src/backend/catalog/pg_depend.c @@ -20,6 +20,8 @@ #include "catalog/indexing.h" #include "catalog/pg_constraint.h" #include "catalog/pg_depend.h" +#include "catalog/pg_extension.h" +#include "commands/extension.h" #include "miscadmin.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" @@ -123,15 +125,42 @@ recordMultipleDependencies(const ObjectAddress *depender, } /* + * If we are executing a CREATE EXTENSION operation, mark the given object + * as being a member of the extension. Otherwise, do nothing. + * + * This must be called during creation of any user-definable object type + * that could be a member of an extension. + */ +void +recordDependencyOnCurrentExtension(const ObjectAddress *object) +{ + if (creating_extension) + { + ObjectAddress extension; + + extension.classId = ExtensionRelationId; + extension.objectId = CurrentExtensionObject; + extension.objectSubId = 0; + + recordDependencyOn(object, &extension, DEPENDENCY_EXTENSION); + } +} + +/* * deleteDependencyRecordsFor -- delete all records with given depender * classId/objectId. Returns the number of records deleted. * * This is used when redefining an existing object. Links leading to the * object do not change, and links leading from it will be recreated * (possibly with some differences from before). + * + * If skipExtensionDeps is true, we do not delete any dependencies that + * show that the given object is a member of an extension. This avoids + * needing a lot of extra logic to fetch and recreate that dependency. */ long -deleteDependencyRecordsFor(Oid classId, Oid objectId) +deleteDependencyRecordsFor(Oid classId, Oid objectId, + bool skipExtensionDeps) { long count = 0; Relation depRel; @@ -155,6 +184,10 @@ deleteDependencyRecordsFor(Oid classId, Oid objectId) while (HeapTupleIsValid(tup = systable_getnext(scan))) { + if (skipExtensionDeps && + ((Form_pg_depend) GETSTRUCT(tup))->deptype == DEPENDENCY_EXTENSION) + continue; + simple_heap_delete(depRel, &tup->t_self); count++; } @@ -321,6 +354,59 @@ isObjectPinned(const ObjectAddress *object, Relation rel) /* + * Find the extension containing the specified object, if any + * + * Returns the OID of the extension, or InvalidOid if the object does not + * belong to any extension. + * + * Extension membership is marked by an EXTENSION dependency from the object + * to the extension. Note that the result will be indeterminate if pg_depend + * contains links from this object to more than one extension ... but that + * should never happen. + */ +Oid +getExtensionOfObject(Oid classId, Oid objectId) +{ + Oid result = InvalidOid; + Relation depRel; + ScanKeyData key[2]; + SysScanDesc scan; + HeapTuple tup; + + depRel = heap_open(DependRelationId, AccessShareLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_classid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(classId)); + ScanKeyInit(&key[1], + Anum_pg_depend_objid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(objectId)); + + scan = systable_beginscan(depRel, DependDependerIndexId, true, + SnapshotNow, 2, key); + + while (HeapTupleIsValid((tup = systable_getnext(scan)))) + { + Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup); + + if (depform->refclassid == ExtensionRelationId && + depform->deptype == DEPENDENCY_EXTENSION) + { + result = depform->refobjid; + break; /* no need to keep scanning */ + } + } + + systable_endscan(scan); + + heap_close(depRel, AccessShareLock); + + return result; +} + +/* * Detect whether a sequence is marked as "owned" by a column * * An ownership marker is an AUTO dependency from the sequence to the diff --git a/src/backend/catalog/pg_namespace.c b/src/backend/catalog/pg_namespace.c index c78aa019bff..172f99196cc 100644 --- a/src/backend/catalog/pg_namespace.c +++ b/src/backend/catalog/pg_namespace.c @@ -38,6 +38,7 @@ NamespaceCreate(const char *nspName, Oid ownerId) Datum values[Natts_pg_namespace]; NameData nname; TupleDesc tupDesc; + ObjectAddress myself; int i; /* sanity checks */ @@ -73,9 +74,17 @@ NamespaceCreate(const char *nspName, Oid ownerId) heap_close(nspdesc, RowExclusiveLock); - /* Record dependency on owner */ + /* Record dependencies */ + myself.classId = NamespaceRelationId; + myself.objectId = nspoid; + myself.objectSubId = 0; + + /* dependency on owner */ recordDependencyOnOwner(NamespaceRelationId, nspoid, ownerId); + /* dependency on extension */ + recordDependencyOnCurrentExtension(&myself); + /* Post creation hook for new schema */ InvokeObjectAccessHook(OAT_POST_CREATE, NamespaceRelationId, nspoid, 0); diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c index c70483a7390..ccd0fe1997d 100644 --- a/src/backend/catalog/pg_operator.c +++ b/src/backend/catalog/pg_operator.c @@ -777,7 +777,7 @@ makeOperatorDependencies(HeapTuple tuple) myself.objectSubId = 0; /* In case we are updating a shell, delete any existing entries */ - deleteDependencyRecordsFor(myself.classId, myself.objectId); + deleteDependencyRecordsFor(myself.classId, myself.objectId, false); deleteSharedDependencyRecordsFor(myself.classId, myself.objectId, 0); /* Dependency on namespace */ @@ -855,4 +855,7 @@ makeOperatorDependencies(HeapTuple tuple) /* Dependency on owner */ recordDependencyOnOwner(OperatorRelationId, HeapTupleGetOid(tuple), oper->oprowner); + + /* Dependency on extension */ + recordDependencyOnCurrentExtension(&myself); } diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index 2ab87d2df5b..3f3877da286 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -562,10 +562,11 @@ ProcedureCreate(const char *procedureName, * Create dependencies for the new function. If we are updating an * existing function, first delete any existing pg_depend entries. * (However, since we are not changing ownership or permissions, the - * shared dependencies do *not* need to change, and we leave them alone.) + * shared dependencies do *not* need to change, and we leave them alone. + * We also don't change any pre-existing extension-membership dependency.) */ if (is_update) - deleteDependencyRecordsFor(ProcedureRelationId, retval); + deleteDependencyRecordsFor(ProcedureRelationId, retval, true); myself.classId = ProcedureRelationId; myself.objectId = retval; @@ -615,6 +616,10 @@ ProcedureCreate(const char *procedureName, nnewmembers, newmembers); } + /* dependency on extension */ + if (!is_update) + recordDependencyOnCurrentExtension(&myself); + heap_freetuple(tup); /* Post creation hook for new function */ diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c index 8ceaab1fb12..9b574179ff9 100644 --- a/src/backend/catalog/pg_type.c +++ b/src/backend/catalog/pg_type.c @@ -481,7 +481,7 @@ TypeCreate(Oid newTypeOid, * * If rebuild is true, we remove existing dependencies and rebuild them * from scratch. This is needed for ALTER TYPE, and also when replacing - * a shell type. + * a shell type. We don't remove/rebuild extension dependencies, though. */ void GenerateTypeDependencies(Oid typeNamespace, @@ -507,7 +507,7 @@ GenerateTypeDependencies(Oid typeNamespace, if (rebuild) { - deleteDependencyRecordsFor(TypeRelationId, typeObjectId); + deleteDependencyRecordsFor(TypeRelationId, typeObjectId, true); deleteSharedDependencyRecordsFor(TypeRelationId, typeObjectId, 0); } @@ -521,7 +521,7 @@ GenerateTypeDependencies(Oid typeNamespace, * For a relation rowtype (that's not a composite type), we should skip * these because we'll depend on them indirectly through the pg_class * entry. Likewise, skip for implicit arrays since we'll depend on them - * through the element type. + * through the element type. The same goes for extension membership. */ if ((!OidIsValid(relationOid) || relationKind == RELKIND_COMPOSITE_TYPE) && !isImplicitArray) @@ -532,6 +532,10 @@ GenerateTypeDependencies(Oid typeNamespace, recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); recordDependencyOnOwner(TypeRelationId, typeObjectId, owner); + + /* dependency on extension */ + if (!rebuild) + recordDependencyOnCurrentExtension(&myself); } /* Normal dependencies on the I/O functions */ diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 4fa1453b14f..987026c8358 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -153,6 +153,13 @@ CREATE VIEW pg_locks AS CREATE VIEW pg_cursors AS SELECT * FROM pg_cursor() AS C; +CREATE VIEW pg_available_extensions AS + SELECT E.name, E.version, X.extversion AS installed, + N.nspname AS schema, E.relocatable, E.comment + FROM pg_available_extensions() AS E + LEFT JOIN pg_extension AS X ON E.name = X.extname + LEFT JOIN pg_namespace AS N on N.oid = X.extnamespace; + CREATE VIEW pg_prepared_xacts AS SELECT P.transaction, P.gid, P.prepared, U.rolname AS owner, D.datname AS database diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile index 9d2a7322457..0aadbc56adb 100644 --- a/src/backend/commands/Makefile +++ b/src/backend/commands/Makefile @@ -14,7 +14,8 @@ include $(top_builddir)/src/Makefile.global OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \ constraint.o conversioncmds.o copy.o \ - dbcommands.o define.o discard.o explain.o foreigncmds.o functioncmds.o \ + dbcommands.o define.o discard.o explain.o extension.o \ + foreigncmds.o functioncmds.o \ indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \ portalcmds.o prepare.o proclang.o \ schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \ diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index 1c6ae0243e4..2c9340accf1 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -23,6 +23,7 @@ #include "commands/conversioncmds.h" #include "commands/dbcommands.h" #include "commands/defrem.h" +#include "commands/extension.h" #include "commands/proclang.h" #include "commands/schemacmds.h" #include "commands/tablecmds.h" @@ -188,6 +189,10 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt) AlterConversionNamespace(stmt->object, stmt->newschema); break; + case OBJECT_EXTENSION: + AlterExtensionNamespace(stmt->object, stmt->newschema); + break; + case OBJECT_FUNCTION: AlterFunctionNamespace(stmt->object, stmt->objarg, false, stmt->newschema); @@ -242,87 +247,204 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt) } /* + * Change an object's namespace given its classOid and object Oid. + * + * Objects that don't have a namespace should be ignored. + * + * This function is currently used only by ALTER EXTENSION SET SCHEMA, + * so it only needs to cover object types that can be members of an + * extension, and it doesn't have to deal with certain special cases + * such as not wanting to process array types --- those should never + * be direct members of an extension anyway. + * + * Returns the OID of the object's previous namespace, or InvalidOid if + * object doesn't have a schema. + */ +Oid +AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid) +{ + Oid oldNspOid = InvalidOid; + ObjectAddress dep; + + dep.classId = classId; + dep.objectId = objid; + dep.objectSubId = 0; + + switch (getObjectClass(&dep)) + { + case OCLASS_CLASS: + { + Relation rel; + Relation classRel; + + rel = relation_open(objid, AccessExclusiveLock); + oldNspOid = RelationGetNamespace(rel); + + classRel = heap_open(RelationRelationId, RowExclusiveLock); + + AlterRelationNamespaceInternal(classRel, + objid, + oldNspOid, + nspOid, + true); + + heap_close(classRel, RowExclusiveLock); + + relation_close(rel, NoLock); + break; + } + + case OCLASS_PROC: + oldNspOid = AlterFunctionNamespace_oid(objid, nspOid); + break; + + case OCLASS_TYPE: + oldNspOid = AlterTypeNamespace_oid(objid, nspOid); + break; + + case OCLASS_CONVERSION: + oldNspOid = AlterConversionNamespace_oid(objid, nspOid); + break; + + case OCLASS_OPERATOR: + oldNspOid = AlterOperatorNamespace_oid(objid, nspOid); + break; + + case OCLASS_OPCLASS: + oldNspOid = AlterOpClassNamespace_oid(objid, nspOid); + break; + + case OCLASS_OPFAMILY: + oldNspOid = AlterOpFamilyNamespace_oid(objid, nspOid); + break; + + case OCLASS_TSPARSER: + oldNspOid = AlterTSParserNamespace_oid(objid, nspOid); + break; + + case OCLASS_TSDICT: + oldNspOid = AlterTSDictionaryNamespace_oid(objid, nspOid); + break; + + case OCLASS_TSTEMPLATE: + oldNspOid = AlterTSTemplateNamespace_oid(objid, nspOid); + break; + + case OCLASS_TSCONFIG: + oldNspOid = AlterTSConfigurationNamespace_oid(objid, nspOid); + break; + + default: + break; + } + + return oldNspOid; +} + +/* * Generic function to change the namespace of a given object, for simple - * cases (won't work for tables or functions, objects which have more than 2 - * key-attributes to use when searching for their syscache entries --- we - * don't want nor need to get this generic here). + * cases (won't work for tables, nor other cases where we need to do more + * than change the namespace column of a single catalog entry). * * The AlterFooNamespace() calls just above will call a function whose job * is to lookup the arguments for the generic function here. * - * Relation must already by open, it's the responsibility of the caller to - * close it. + * rel: catalog relation containing object (RowExclusiveLock'd by caller) + * oidCacheId: syscache that indexes this catalog by OID + * nameCacheId: syscache that indexes this catalog by name and namespace + * (pass -1 if there is none) + * objid: OID of object to change the namespace of + * nspOid: OID of new namespace + * Anum_name: column number of catalog's name column + * Anum_namespace: column number of catalog's namespace column + * Anum_owner: column number of catalog's owner column, or -1 if none + * acl_kind: ACL type for object, or -1 if none assigned + * + * If the object does not have an owner or permissions, pass -1 for + * Anum_owner and acl_kind. In this case the calling user must be superuser. + * + * Returns the OID of the object's previous namespace. */ -void -AlterObjectNamespace(Relation rel, int cacheId, - Oid classId, Oid objid, Oid nspOid, +Oid +AlterObjectNamespace(Relation rel, int oidCacheId, int nameCacheId, + Oid objid, Oid nspOid, int Anum_name, int Anum_namespace, int Anum_owner, - AclObjectKind acl_kind, - bool superuser_only) + AclObjectKind acl_kind) { + Oid classId = RelationGetRelid(rel); Oid oldNspOid; Datum name, namespace; bool isnull; - HeapTuple tup, newtup = NULL; + HeapTuple tup, newtup; Datum *values; bool *nulls; bool *replaces; - tup = SearchSysCacheCopy1(cacheId, ObjectIdGetDatum(objid)); + tup = SearchSysCacheCopy1(oidCacheId, ObjectIdGetDatum(objid)); if (!HeapTupleIsValid(tup)) /* should not happen */ - elog(ERROR, "cache lookup failed for object %u: %s", - objid, getObjectDescriptionOids(classId, objid)); + elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"", + objid, RelationGetRelationName(rel)); - name = heap_getattr(tup, Anum_name, rel->rd_att, &isnull); - namespace = heap_getattr(tup, Anum_namespace, rel->rd_att, &isnull); + name = heap_getattr(tup, Anum_name, RelationGetDescr(rel), &isnull); + Assert(!isnull); + namespace = heap_getattr(tup, Anum_namespace, RelationGetDescr(rel), &isnull); + Assert(!isnull); oldNspOid = DatumGetObjectId(namespace); /* Check basic namespace related issues */ CheckSetNamespace(oldNspOid, nspOid, classId, objid); - /* check for duplicate name (more friendly than unique-index failure) */ - if (SearchSysCacheExists2(cacheId, name, ObjectIdGetDatum(nspOid))) - ereport(ERROR, - (errcode(ERRCODE_DUPLICATE_OBJECT), - errmsg("%s already exists in schema \"%s\"", - getObjectDescriptionOids(classId, objid), - get_namespace_name(nspOid)))); - - /* Superusers can always do it */ + /* Permission checks ... superusers can always do it */ if (!superuser()) { Datum owner; Oid ownerId; AclResult aclresult; - if (superuser_only) + /* Fail if object does not have an explicit owner */ + if (Anum_owner <= 0) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be superuser to SET SCHEMA of %s", getObjectDescriptionOids(classId, objid))))); /* Otherwise, must be owner of the existing object */ - owner = heap_getattr(tup, Anum_owner, rel->rd_att, &isnull); + owner = heap_getattr(tup, Anum_owner, RelationGetDescr(rel), &isnull); + Assert(!isnull); ownerId = DatumGetObjectId(owner); if (!has_privs_of_role(GetUserId(), ownerId)) aclcheck_error(ACLCHECK_NOT_OWNER, acl_kind, NameStr(*(DatumGetName(name)))); - /* owner must have CREATE privilege on namespace */ - aclresult = pg_namespace_aclcheck(oldNspOid, GetUserId(), ACL_CREATE); + /* User must have CREATE privilege on new namespace */ + aclresult = pg_namespace_aclcheck(nspOid, GetUserId(), ACL_CREATE); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, - get_namespace_name(oldNspOid)); + get_namespace_name(nspOid)); } - /* Prepare to update tuple */ - values = palloc0(rel->rd_att->natts * sizeof(Datum)); - nulls = palloc0(rel->rd_att->natts * sizeof(bool)); - replaces = palloc0(rel->rd_att->natts * sizeof(bool)); - values[Anum_namespace - 1] = nspOid; + /* + * Check for duplicate name (more friendly than unique-index failure). + * Since this is just a friendliness check, we can just skip it in cases + * where there isn't a suitable syscache available. + */ + if (nameCacheId >= 0 && + SearchSysCacheExists2(nameCacheId, name, ObjectIdGetDatum(nspOid))) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("%s already exists in schema \"%s\"", + getObjectDescriptionOids(classId, objid), + get_namespace_name(nspOid)))); + + /* Build modified tuple */ + values = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(Datum)); + nulls = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(bool)); + replaces = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(bool)); + values[Anum_namespace - 1] = ObjectIdGetDatum(nspOid); replaces[Anum_namespace - 1] = true; - newtup = heap_modify_tuple(tup, rel->rd_att, values, nulls, replaces); + newtup = heap_modify_tuple(tup, RelationGetDescr(rel), + values, nulls, replaces); /* Perform actual update */ simple_heap_update(rel, &tup->t_self, newtup); @@ -336,6 +458,8 @@ AlterObjectNamespace(Relation rel, int cacheId, /* update dependencies to point to the new schema */ changeDependencyFor(classId, objid, NamespaceRelationId, oldNspOid, nspOid); + + return oldNspOid; } diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 59a439413e1..4c4f356e790 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -1277,7 +1277,8 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, if (relform1->reltoastrelid) { count = deleteDependencyRecordsFor(RelationRelationId, - relform1->reltoastrelid); + relform1->reltoastrelid, + false); if (count != 1) elog(ERROR, "expected one dependency record for TOAST table, found %ld", count); @@ -1285,7 +1286,8 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, if (relform2->reltoastrelid) { count = deleteDependencyRecordsFor(RelationRelationId, - relform2->reltoastrelid); + relform2->reltoastrelid, + false); if (count != 1) elog(ERROR, "expected one dependency record for TOAST table, found %ld", count); diff --git a/src/backend/commands/comment.c b/src/backend/commands/comment.c index 2e8c4df9272..bbb3f344093 100644 --- a/src/backend/commands/comment.c +++ b/src/backend/commands/comment.c @@ -143,6 +143,12 @@ CommentObject(CommentStmt *stmt) (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to comment on procedural language"))); break; + case OBJECT_EXTENSION: + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to comment on extension"))); + break; case OBJECT_OPCLASS: if (!pg_opclass_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPCLASS, diff --git a/src/backend/commands/conversioncmds.c b/src/backend/commands/conversioncmds.c index da024df5f0c..b5e4420ca8d 100644 --- a/src/backend/commands/conversioncmds.c +++ b/src/backend/commands/conversioncmds.c @@ -345,12 +345,35 @@ AlterConversionNamespace(List *name, const char *newschema) /* get schema OID */ nspOid = LookupCreationNamespace(newschema); - AlterObjectNamespace(rel, CONVOID, ConversionRelationId, convOid, nspOid, + AlterObjectNamespace(rel, CONVOID, CONNAMENSP, + convOid, nspOid, Anum_pg_conversion_conname, Anum_pg_conversion_connamespace, Anum_pg_conversion_conowner, - ACL_KIND_CONVERSION, - false); + ACL_KIND_CONVERSION); - heap_close(rel, NoLock); + heap_close(rel, RowExclusiveLock); +} + +/* + * Change conversion schema, by oid + */ +Oid +AlterConversionNamespace_oid(Oid convOid, Oid newNspOid) +{ + Oid oldNspOid; + Relation rel; + + rel = heap_open(ConversionRelationId, RowExclusiveLock); + + oldNspOid = AlterObjectNamespace(rel, CONVOID, CONNAMENSP, + convOid, newNspOid, + Anum_pg_conversion_conname, + Anum_pg_conversion_connamespace, + Anum_pg_conversion_conowner, + ACL_KIND_CONVERSION); + + heap_close(rel, RowExclusiveLock); + + return oldNspOid; } diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c new file mode 100644 index 00000000000..50032031976 --- /dev/null +++ b/src/backend/commands/extension.c @@ -0,0 +1,1401 @@ +/*------------------------------------------------------------------------- + * + * extension.c + * Commands to manipulate extensions + * + * Extensions in PostgreSQL allow management of collections of SQL objects. + * + * All we need internally to manage an extension is an OID so that the + * dependent objects can be associated with it. An extension is created by + * populating the pg_extension catalog from a "control" file. + * The extension control file is parsed with the same parser we use for + * postgresql.conf and recovery.conf. An extension also has an installation + * script file, containing SQL commands to create the extension's objects. + * + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/commands/extension.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <dirent.h> +#include <unistd.h> + +#include "access/sysattr.h" +#include "access/xact.h" +#include "catalog/dependency.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/pg_depend.h" +#include "catalog/pg_extension.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_type.h" +#include "commands/alter.h" +#include "commands/comment.h" +#include "commands/extension.h" +#include "commands/trigger.h" +#include "executor/executor.h" +#include "funcapi.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "tcop/tcopprot.h" +#include "tcop/utility.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/guc.h" +#include "utils/lsyscache.h" +#include "utils/snapmgr.h" +#include "utils/tqual.h" + + +bool creating_extension = false; +Oid CurrentExtensionObject = InvalidOid; + +/* + * Internal data structure to hold the results of parsing a control file + */ +typedef struct ExtensionControlFile +{ + char *name; /* name of the extension */ + char *script; /* filename of the installation script */ + char *version; /* version ID, if any */ + char *comment; /* comment, if any */ + char *schema; /* target schema (allowed if !relocatable) */ + bool relocatable; /* is ALTER EXTENSION SET SCHEMA supported? */ + int encoding; /* encoding of the script file, or -1 */ + List *requires; /* names of prerequisite extensions */ +} ExtensionControlFile; + + +/* + * get_extension_oid - given an extension name, look up the OID + * + * If missing_ok is false, throw an error if extension name not found. If + * true, just return InvalidOid. + */ +Oid +get_extension_oid(const char *extname, bool missing_ok) +{ + Oid result; + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + rel = heap_open(ExtensionRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + Anum_pg_extension_extname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(extname)); + + scandesc = systable_beginscan(rel, ExtensionNameIndexId, true, + SnapshotNow, 1, entry); + + tuple = systable_getnext(scandesc); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + result = HeapTupleGetOid(tuple); + else + result = InvalidOid; + + systable_endscan(scandesc); + + heap_close(rel, AccessShareLock); + + if (!OidIsValid(result) && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("extension \"%s\" does not exist", + extname))); + + return result; +} + +/* + * get_extension_name - given an extension OID, look up the name + * + * Returns a palloc'd string, or NULL if no such extension. + */ +char * +get_extension_name(Oid ext_oid) +{ + char *result; + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + rel = heap_open(ExtensionRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(ext_oid)); + + scandesc = systable_beginscan(rel, ExtensionOidIndexId, true, + SnapshotNow, 1, entry); + + tuple = systable_getnext(scandesc); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + result = pstrdup(NameStr(((Form_pg_extension) GETSTRUCT(tuple))->extname)); + else + result = NULL; + + systable_endscan(scandesc); + + heap_close(rel, AccessShareLock); + + return result; +} + +/* + * get_extension_schema - given an extension OID, fetch its extnamespace + * + * Returns InvalidOid if no such extension. + */ +static Oid +get_extension_schema(Oid ext_oid) +{ + Oid result; + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + rel = heap_open(ExtensionRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(ext_oid)); + + scandesc = systable_beginscan(rel, ExtensionOidIndexId, true, + SnapshotNow, 1, entry); + + tuple = systable_getnext(scandesc); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + result = ((Form_pg_extension) GETSTRUCT(tuple))->extnamespace; + else + result = InvalidOid; + + systable_endscan(scandesc); + + heap_close(rel, AccessShareLock); + + return result; +} + +/* + * Utility functions to handle extension-related path names + */ +static bool +is_extension_control_filename(const char *filename) +{ + const char *extension = strrchr(filename, '.'); + + return (extension != NULL) && (strcmp(extension, ".control") == 0); +} + +static char * +get_extension_control_directory(void) +{ + char sharepath[MAXPGPATH]; + char *result; + + get_share_path(my_exec_path, sharepath); + result = (char *) palloc(MAXPGPATH); + snprintf(result, MAXPGPATH, "%s/contrib", sharepath); + + return result; +} + +static char * +get_extension_control_filename(const char *extname) +{ + char sharepath[MAXPGPATH]; + char *result; + + get_share_path(my_exec_path, sharepath); + result = (char *) palloc(MAXPGPATH); + snprintf(result, MAXPGPATH, "%s/contrib/%s.control", sharepath, extname); + + return result; +} + +/* + * Given a relative pathname such as "name.sql", return the full path to + * the script file. If given an absolute name, just return it. + */ +static char * +get_extension_absolute_path(const char *filename) +{ + char sharepath[MAXPGPATH]; + char *result; + + if (is_absolute_path(filename)) + return pstrdup(filename); + + get_share_path(my_exec_path, sharepath); + result = (char *) palloc(MAXPGPATH); + snprintf(result, MAXPGPATH, "%s/contrib/%s", sharepath, filename); + + return result; +} + + +/* + * Read the control file for the specified extension. + * + * The control file is supposed to be very short, half a dozen lines, and + * reading it is only allowed to superuser, so we don't worry about + * memory allocation risks here. Also note that we don't worry about + * what encoding it's in; all values are expected to be ASCII. + */ +static ExtensionControlFile * +read_extension_control_file(const char *extname) +{ + char *filename = get_extension_control_filename(extname); + FILE *file; + ExtensionControlFile *control; + ConfigVariable *item, + *head = NULL, + *tail = NULL; + + /* + * Parse the file content, using GUC's file parsing code + */ + if ((file = AllocateFile(filename, "r")) == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open extension control file \"%s\": %m", + filename))); + + ParseConfigFp(file, filename, 0, ERROR, &head, &tail); + + FreeFile(file); + + /* + * Set up default values. Pointer fields are initially null. + */ + control = (ExtensionControlFile *) palloc0(sizeof(ExtensionControlFile)); + control->name = pstrdup(extname); + control->relocatable = false; + control->encoding = -1; + + /* + * Convert the ConfigVariable list into ExtensionControlFile entries. + */ + for (item = head; item != NULL; item = item->next) + { + if (strcmp(item->name, "script") == 0) + { + control->script = pstrdup(item->value); + } + else if (strcmp(item->name, "version") == 0) + { + control->version = pstrdup(item->value); + } + else if (strcmp(item->name, "comment") == 0) + { + control->comment = pstrdup(item->value); + } + else if (strcmp(item->name, "schema") == 0) + { + control->schema = pstrdup(item->value); + } + else if (strcmp(item->name, "relocatable") == 0) + { + if (!parse_bool(item->value, &control->relocatable)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter \"%s\" requires a Boolean value", + item->name))); + } + else if (strcmp(item->name, "encoding") == 0) + { + control->encoding = pg_valid_server_encoding(item->value); + if (control->encoding < 0) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("\"%s\" is not a valid encoding name", + item->value))); + } + else if (strcmp(item->name, "requires") == 0) + { + /* Need a modifiable copy of string */ + char *rawnames = pstrdup(item->value); + + /* Parse string into list of identifiers */ + if (!SplitIdentifierString(rawnames, ',', &control->requires)) + { + /* syntax error in name list */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter \"%s\" must be a list of extension names", + item->name))); + } + } + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized parameter \"%s\" in file \"%s\"", + item->name, filename))); + } + + FreeConfigVariables(head); + + if (control->relocatable && control->schema != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("parameter \"schema\" cannot be specified when \"relocatable\" is true"))); + + /* + * script defaults to ${extension-name}.sql + */ + if (control->script == NULL) + { + char script[MAXPGPATH]; + + snprintf(script, MAXPGPATH, "%s.sql", control->name); + control->script = pstrdup(script); + } + + return control; +} + +/* + * Read the SQL script into a string, and convert to database encoding + */ +static char * +read_extension_script_file(const ExtensionControlFile *control, + const char *filename) +{ + int src_encoding; + int dest_encoding = GetDatabaseEncoding(); + bytea *content; + char *src_str; + char *dest_str; + int len; + + content = read_binary_file(filename, 0, -1); + + /* use database encoding if not given */ + if (control->encoding < 0) + src_encoding = dest_encoding; + else + src_encoding = control->encoding; + + /* make sure that source string is valid in the expected encoding */ + len = VARSIZE_ANY_EXHDR(content); + src_str = VARDATA_ANY(content); + pg_verify_mbstr_len(src_encoding, src_str, len, false); + + /* convert the encoding to the database encoding */ + dest_str = (char *) pg_do_encoding_conversion((unsigned char *) src_str, + len, + src_encoding, + dest_encoding); + + /* if no conversion happened, we have to arrange for null termination */ + if (dest_str == src_str) + { + dest_str = (char *) palloc(len + 1); + memcpy(dest_str, src_str, len); + dest_str[len] = '\0'; + } + + return dest_str; +} + +/* + * Execute given SQL string. + * + * filename is used only to report errors. + * + * Note: it's tempting to just use SPI to execute the string, but that does + * not work very well. The really serious problem is that SPI will parse, + * analyze, and plan the whole string before executing any of it; of course + * this fails if there are any plannable statements referring to objects + * created earlier in the script. A lesser annoyance is that SPI insists + * on printing the whole string as errcontext in case of any error, and that + * could be very long. + */ +static void +execute_sql_string(const char *sql, const char *filename) +{ + List *raw_parsetree_list; + DestReceiver *dest; + ListCell *lc1; + + /* + * Parse the SQL string into a list of raw parse trees. + */ + raw_parsetree_list = pg_parse_query(sql); + + /* All output from SELECTs goes to the bit bucket */ + dest = CreateDestReceiver(DestNone); + + /* + * Do parse analysis, rule rewrite, planning, and execution for each raw + * parsetree. We must fully execute each query before beginning parse + * analysis on the next one, since there may be interdependencies. + */ + foreach(lc1, raw_parsetree_list) + { + Node *parsetree = (Node *) lfirst(lc1); + List *stmt_list; + ListCell *lc2; + + stmt_list = pg_analyze_and_rewrite(parsetree, + sql, + NULL, + 0); + stmt_list = pg_plan_queries(stmt_list, 0, NULL); + + foreach(lc2, stmt_list) + { + Node *stmt = (Node *) lfirst(lc2); + + if (IsA(stmt, TransactionStmt)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("transaction control statements are not allowed within an extension script"))); + + CommandCounterIncrement(); + + PushActiveSnapshot(GetTransactionSnapshot()); + + if (IsA(stmt, PlannedStmt) && + ((PlannedStmt *) stmt)->utilityStmt == NULL) + { + QueryDesc *qdesc; + + qdesc = CreateQueryDesc((PlannedStmt *) stmt, + sql, + GetActiveSnapshot(), NULL, + dest, NULL, 0); + + AfterTriggerBeginQuery(); + ExecutorStart(qdesc, 0); + ExecutorRun(qdesc, ForwardScanDirection, 0); + AfterTriggerEndQuery(qdesc->estate); + ExecutorEnd(qdesc); + + FreeQueryDesc(qdesc); + } + else + { + ProcessUtility(stmt, + sql, + NULL, + false, /* not top level */ + dest, + NULL); + } + + PopActiveSnapshot(); + } + } + + /* Be sure to advance the command counter after the last script command */ + CommandCounterIncrement(); +} + +/* + * Execute the extension's script file + */ +static void +execute_extension_script(Oid extensionOid, ExtensionControlFile *control, + List *requiredSchemas, + const char *schemaName, Oid schemaOid) +{ + char *filename = get_extension_absolute_path(control->script); + char *save_client_min_messages = NULL, + *save_log_min_messages = NULL, + *save_search_path; + StringInfoData pathbuf; + ListCell *lc; + + /* + * Force client_min_messages and log_min_messages to be at least WARNING, + * so that we won't spam the user with useless NOTICE messages from common + * script actions like creating shell types. + * + * We use the equivalent of SET LOCAL to ensure the setting is undone + * upon error. + */ + if (client_min_messages < WARNING) + { + save_client_min_messages = + pstrdup(GetConfigOption("client_min_messages", false)); + (void) set_config_option("client_min_messages", "warning", + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_LOCAL, true); + } + + if (log_min_messages < WARNING) + { + save_log_min_messages = + pstrdup(GetConfigOption("log_min_messages", false)); + (void) set_config_option("log_min_messages", "warning", + PGC_SUSET, PGC_S_SESSION, + GUC_ACTION_LOCAL, true); + } + + /* + * Set up the search path to contain the target schema, then the schemas + * of any prerequisite extensions, and nothing else. In particular this + * makes the target schema be the default creation target namespace. + * + * Note: it might look tempting to use PushOverrideSearchPath for this, + * but we cannot do that. We have to actually set the search_path GUC + * in case the extension script examines or changes it. + */ + save_search_path = pstrdup(GetConfigOption("search_path", false)); + + initStringInfo(&pathbuf); + appendStringInfoString(&pathbuf, quote_identifier(schemaName)); + foreach(lc, requiredSchemas) + { + Oid reqschema = lfirst_oid(lc); + char *reqname = get_namespace_name(reqschema); + + if (reqname) + appendStringInfo(&pathbuf, ", %s", quote_identifier(reqname)); + } + + (void) set_config_option("search_path", pathbuf.data, + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_LOCAL, true); + + /* + * Set creating_extension and related variables so that + * recordDependencyOnCurrentExtension and other functions do the right + * things. On failure, ensure we reset these variables. + */ + creating_extension = true; + CurrentExtensionObject = extensionOid; + PG_TRY(); + { + char *sql = read_extension_script_file(control, filename); + + /* + * If it's not relocatable, substitute the target schema name for + * occcurrences of @extschema@. + * + * For a relocatable extension, we just run the script as-is. + * There cannot be any need for @extschema@, else it wouldn't + * be relocatable. + */ + if (!control->relocatable) + { + const char *qSchemaName = quote_identifier(schemaName); + + sql = text_to_cstring( + DatumGetTextPP( + DirectFunctionCall3(replace_text, + CStringGetTextDatum(sql), + CStringGetTextDatum("@extschema@"), + CStringGetTextDatum(qSchemaName)))); + + } + + execute_sql_string(sql, filename); + } + PG_CATCH(); + { + creating_extension = false; + CurrentExtensionObject = InvalidOid; + PG_RE_THROW(); + } + PG_END_TRY(); + + creating_extension = false; + CurrentExtensionObject = InvalidOid; + + /* + * Restore GUC variables for the remainder of the current transaction. + * Again use SET LOCAL, so we won't affect the session value. + */ + (void) set_config_option("search_path", save_search_path, + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_LOCAL, true); + + if (save_client_min_messages != NULL) + (void) set_config_option("client_min_messages", save_client_min_messages, + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_LOCAL, true); + if (save_log_min_messages != NULL) + (void) set_config_option("log_min_messages", save_log_min_messages, + PGC_SUSET, PGC_S_SESSION, + GUC_ACTION_LOCAL, true); +} + +/* + * CREATE EXTENSION + */ +void +CreateExtension(CreateExtensionStmt *stmt) +{ + DefElem *d_schema = NULL; + char *schemaName; + Oid schemaOid; + Oid extowner = GetUserId(); + ExtensionControlFile *control; + List *requiredExtensions; + List *requiredSchemas; + Relation rel; + Datum values[Natts_pg_extension]; + bool nulls[Natts_pg_extension]; + HeapTuple tuple; + Oid extensionOid; + ObjectAddress myself; + ObjectAddress nsp; + ListCell *lc; + + /* Must be super user */ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to create extension \"%s\"", + stmt->extname), + errhint("Must be superuser to create an extension."))); + + /* + * We use global variables to track the extension being created, so we + * can create only one extension at the same time. + */ + if (creating_extension) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("nested CREATE EXTENSION is not supported"))); + + /* + * Check for duplicate extension name. The unique index on + * pg_extension.extname would catch this anyway, and serves as a backstop + * in case of race conditions; but this is a friendlier error message. + */ + if (get_extension_oid(stmt->extname, true) != InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("extension \"%s\" already exists", stmt->extname))); + + /* + * Read the control file. Note we assume that it does not contain + * any non-ASCII data, so there is no need to worry about encoding + * at this point. + */ + control = read_extension_control_file(stmt->extname); + + /* + * Read the statement option list + */ + foreach(lc, stmt->options) + { + DefElem *defel = (DefElem *) lfirst(lc); + + if (strcmp(defel->defname, "schema") == 0) + { + if (d_schema) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + d_schema = defel; + } + else + elog(ERROR, "unrecognized option: %s", defel->defname); + } + + /* + * Determine the target schema to install the extension into + */ + if (d_schema && d_schema->arg) + { + /* + * User given schema, CREATE EXTENSION ... WITH SCHEMA ... + * + * It's an error to give a schema different from control->schema if + * control->schema is specified. + */ + schemaName = strVal(d_schema->arg); + + if (control->schema != NULL && + strcmp(control->schema, schemaName) != 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("extension \"%s\" must be installed in schema \"%s\"", + control->name, + control->schema))); + + /* If the user is giving us the schema name, it must exist already */ + schemaOid = get_namespace_oid(schemaName, false); + } + else if (control->schema != NULL) + { + /* + * The extension is not relocatable and the author gave us a schema + * for it. We create the schema here if it does not already exist. + */ + schemaName = control->schema; + schemaOid = get_namespace_oid(schemaName, true); + + if (schemaOid == InvalidOid) + { + schemaOid = NamespaceCreate(schemaName, extowner); + /* Advance cmd counter to make the namespace visible */ + CommandCounterIncrement(); + } + } + else + { + /* + * Else, use the current default creation namespace, which is the + * first explicit entry in the search_path. + */ + List *search_path = fetch_search_path(false); + + if (search_path == NIL) /* probably can't happen */ + elog(ERROR, "there is no default creation target"); + schemaOid = linitial_oid(search_path); + schemaName = get_namespace_name(schemaOid); + if (schemaName == NULL) /* recently-deleted namespace? */ + elog(ERROR, "there is no default creation target"); + + list_free(search_path); + } + + /* + * If we didn't already know user is superuser, we would probably want + * to do pg_namespace_aclcheck(schemaOid, extowner, ACL_CREATE) here. + */ + + /* + * Look up the prerequisite extensions, and build lists of their OIDs + * and the OIDs of their target schemas. + */ + requiredExtensions = NIL; + requiredSchemas = NIL; + foreach(lc, control->requires) + { + char *curreq = (char *) lfirst(lc); + Oid reqext; + Oid reqschema; + + /* + * We intentionally don't use get_extension_oid's default error + * message here, because it would be confusing in this context. + */ + reqext = get_extension_oid(curreq, true); + if (!OidIsValid(reqext)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("required extension \"%s\" is not installed", + curreq))); + reqschema = get_extension_schema(reqext); + requiredExtensions = lappend_oid(requiredExtensions, reqext); + requiredSchemas = lappend_oid(requiredSchemas, reqschema); + } + + /* + * Insert new tuple into pg_extension. + */ + rel = heap_open(ExtensionRelationId, RowExclusiveLock); + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + values[Anum_pg_extension_extname - 1] = + DirectFunctionCall1(namein, CStringGetDatum(control->name)); + values[Anum_pg_extension_extowner - 1] = ObjectIdGetDatum(extowner); + values[Anum_pg_extension_extnamespace - 1] = ObjectIdGetDatum(schemaOid); + values[Anum_pg_extension_extrelocatable - 1] = BoolGetDatum(control->relocatable); + + if (control->version == NULL) + nulls[Anum_pg_extension_extversion - 1] = true; + else + values[Anum_pg_extension_extversion - 1] = + CStringGetTextDatum(control->version); + + nulls[Anum_pg_extension_extconfig - 1] = true; + nulls[Anum_pg_extension_extcondition - 1] = true; + + tuple = heap_form_tuple(rel->rd_att, values, nulls); + + extensionOid = simple_heap_insert(rel, tuple); + CatalogUpdateIndexes(rel, tuple); + + heap_freetuple(tuple); + heap_close(rel, RowExclusiveLock); + + /* + * Apply any comment on extension + */ + if (control->comment != NULL) + CreateComments(extensionOid, ExtensionRelationId, 0, control->comment); + + /* + * Record dependencies on owner, schema, and prerequisite extensions + */ + recordDependencyOnOwner(ExtensionRelationId, extensionOid, extowner); + + myself.classId = ExtensionRelationId; + myself.objectId = extensionOid; + myself.objectSubId = 0; + + nsp.classId = NamespaceRelationId; + nsp.objectId = schemaOid; + nsp.objectSubId = 0; + + recordDependencyOn(&myself, &nsp, DEPENDENCY_NORMAL); + + foreach(lc, requiredExtensions) + { + Oid reqext = lfirst_oid(lc); + ObjectAddress otherext; + + otherext.classId = ExtensionRelationId; + otherext.objectId = reqext; + otherext.objectSubId = 0; + + recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL); + } + + /* + * Finally, execute the extension script to create the member objects + */ + execute_extension_script(extensionOid, control, requiredSchemas, + schemaName, schemaOid); +} + + +/* + * RemoveExtensions + * Implements DROP EXTENSION. + */ +void +RemoveExtensions(DropStmt *drop) +{ + ObjectAddresses *objects; + ListCell *cell; + + /* + * First we identify all the extensions, then we delete them in a single + * performMultipleDeletions() call. This is to avoid unwanted DROP + * RESTRICT errors if one of the extensions depends on another. + */ + objects = new_object_addresses(); + + foreach(cell, drop->objects) + { + List *names = (List *) lfirst(cell); + char *extensionName; + Oid extensionId; + ObjectAddress object; + + if (list_length(names) != 1) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("extension name cannot be qualified"))); + extensionName = strVal(linitial(names)); + + extensionId = get_extension_oid(extensionName, drop->missing_ok); + + if (!OidIsValid(extensionId)) + { + ereport(NOTICE, + (errmsg("extension \"%s\" does not exist, skipping", + extensionName))); + continue; + } + + /* + * Permission check. For now, insist on superuser-ness; later we + * might want to relax that to being owner of the extension. + */ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to drop extension \"%s\"", + extensionName), + errhint("Must be superuser to drop an extension."))); + + object.classId = ExtensionRelationId; + object.objectId = extensionId; + object.objectSubId = 0; + + add_exact_object_address(&object, objects); + } + + /* + * Do the deletions. Objects contained in the extension(s) are removed by + * means of their dependency links to the extensions. + */ + performMultipleDeletions(objects, drop->behavior); + + free_object_addresses(objects); +} + + +/* + * Guts of extension deletion. + * + * All we need do here is remove the pg_extension tuple itself. Everything + * else is taken care of by the dependency infrastructure. + */ +void +RemoveExtensionById(Oid extId) +{ + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + rel = heap_open(ExtensionRelationId, RowExclusiveLock); + + ScanKeyInit(&entry[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(extId)); + scandesc = systable_beginscan(rel, ExtensionOidIndexId, true, + SnapshotNow, 1, entry); + + tuple = systable_getnext(scandesc); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + simple_heap_delete(rel, &tuple->t_self); + + systable_endscan(scandesc); + + heap_close(rel, RowExclusiveLock); +} + +/* + * This function lists the extensions available in the control directory + * (each of which might or might not actually be installed). We parse each + * available control file and report the interesting fields. + * + * The system view pg_available_extensions provides a user interface to this + * SRF, adding information about whether the extensions are installed in the + * current DB. + */ +Datum +pg_available_extensions(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + char *location; + DIR *dir; + struct dirent *de; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to list available extensions")))); + + /* check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not " \ + "allowed in this context"))); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; + oldcontext = MemoryContextSwitchTo(per_query_ctx); + + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + + MemoryContextSwitchTo(oldcontext); + + location = get_extension_control_directory(); + dir = AllocateDir(location); + + /* + * If the control directory doesn't exist, we want to silently return + * an empty set. Any other error will be reported by ReadDir. + */ + if (dir == NULL && errno == ENOENT) + { + /* do nothing */ + } + else + { + while ((de = ReadDir(dir, location)) != NULL) + { + ExtensionControlFile *control; + char *extname; + Datum values[4]; + bool nulls[4]; + + if (!is_extension_control_filename(de->d_name)) + continue; + + /* extract extension name from 'name.control' filename */ + extname = pstrdup(de->d_name); + *strrchr(extname, '.') = '\0'; + + control = read_extension_control_file(extname); + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + /* name */ + values[0] = DirectFunctionCall1(namein, + CStringGetDatum(control->name)); + /* version */ + if (control->version == NULL) + nulls[1] = true; + else + values[1] = CStringGetTextDatum(control->version); + /* relocatable */ + values[2] = BoolGetDatum(control->relocatable); + /* comment */ + if (control->comment == NULL) + nulls[3] = true; + else + values[3] = CStringGetTextDatum(control->comment); + + tuplestore_putvalues(tupstore, tupdesc, values, nulls); + } + + FreeDir(dir); + } + + /* clean up and return the tuplestore */ + tuplestore_donestoring(tupstore); + + return (Datum) 0; +} + +/* + * pg_extension_config_dump + * + * Record information about a configuration table that belongs to an + * extension being created, but whose contents should be dumped in whole + * or in part during pg_dump. + */ +Datum +pg_extension_config_dump(PG_FUNCTION_ARGS) +{ + Oid tableoid = PG_GETARG_OID(0); + text *wherecond = PG_GETARG_TEXT_P(1); + char *tablename; + Relation extRel; + ScanKeyData key[1]; + SysScanDesc extScan; + HeapTuple extTup; + Datum arrayDatum; + Datum elementDatum; + int arrayIndex; + bool isnull; + Datum repl_val[Natts_pg_extension]; + bool repl_null[Natts_pg_extension]; + bool repl_repl[Natts_pg_extension]; + ArrayType *a; + + /* + * We only allow this to be called from an extension's SQL script. + * We shouldn't need any permissions check beyond that. + */ + if (!creating_extension) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("pg_extension_config_dump() can only be called " + "from a SQL script executed by CREATE EXTENSION"))); + + /* + * Check that the table exists and is a member of the extension being + * created. This ensures that we don't need to register a dependency + * to protect the extconfig entry. + */ + tablename = get_rel_name(tableoid); + if (tablename == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("OID %u does not refer to a table", tableoid))); + if (getExtensionOfObject(RelationRelationId, tableoid) != + CurrentExtensionObject) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("table \"%s\" is not a member of the extension being created", + tablename))); + + /* + * Add the table OID and WHERE condition to the extension's extconfig + * and extcondition arrays. + */ + + /* Find the pg_extension tuple */ + extRel = heap_open(ExtensionRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(CurrentExtensionObject)); + + extScan = systable_beginscan(extRel, ExtensionOidIndexId, true, + SnapshotNow, 1, key); + + extTup = systable_getnext(extScan); + + if (!HeapTupleIsValid(extTup)) /* should not happen */ + elog(ERROR, "extension with oid %u does not exist", + CurrentExtensionObject); + + memset(repl_val, 0, sizeof(repl_val)); + memset(repl_null, false, sizeof(repl_null)); + memset(repl_repl, false, sizeof(repl_repl)); + + /* Build or modify the extconfig value */ + elementDatum = ObjectIdGetDatum(tableoid); + + arrayDatum = heap_getattr(extTup, Anum_pg_extension_extconfig, + RelationGetDescr(extRel), &isnull); + if (isnull) + { + a = construct_array(&elementDatum, 1, + OIDOID, + sizeof(Oid), true, 'i'); + } + else + { + a = DatumGetArrayTypeP(arrayDatum); + Assert(ARR_ELEMTYPE(a) == OIDOID); + Assert(ARR_NDIM(a) == 1); + Assert(ARR_LBOUND(a)[0] == 1); + + arrayIndex = ARR_DIMS(a)[0] + 1; /* add after end */ + + a = array_set(a, 1, &arrayIndex, + elementDatum, + false, + -1 /* varlena array */ , + sizeof(Oid) /* OID's typlen */ , + true /* OID's typbyval */ , + 'i' /* OID's typalign */ ); + } + repl_val[Anum_pg_extension_extconfig - 1] = PointerGetDatum(a); + repl_repl[Anum_pg_extension_extconfig - 1] = true; + + /* Build or modify the extcondition value */ + elementDatum = PointerGetDatum(wherecond); + + arrayDatum = heap_getattr(extTup, Anum_pg_extension_extcondition, + RelationGetDescr(extRel), &isnull); + if (isnull) + { + a = construct_array(&elementDatum, 1, + TEXTOID, + -1, false, 'i'); + } + else + { + a = DatumGetArrayTypeP(arrayDatum); + Assert(ARR_ELEMTYPE(a) == TEXTOID); + Assert(ARR_NDIM(a) == 1); + Assert(ARR_LBOUND(a)[0] == 1); + + arrayIndex = ARR_DIMS(a)[0] + 1; /* add after end */ + + a = array_set(a, 1, &arrayIndex, + elementDatum, + false, + -1 /* varlena array */ , + -1 /* TEXT's typlen */ , + false /* TEXT's typbyval */ , + 'i' /* TEXT's typalign */ ); + } + repl_val[Anum_pg_extension_extcondition - 1] = PointerGetDatum(a); + repl_repl[Anum_pg_extension_extcondition - 1] = true; + + extTup = heap_modify_tuple(extTup, RelationGetDescr(extRel), + repl_val, repl_null, repl_repl); + + simple_heap_update(extRel, &extTup->t_self, extTup); + CatalogUpdateIndexes(extRel, extTup); + + systable_endscan(extScan); + + heap_close(extRel, RowExclusiveLock); + + PG_RETURN_VOID(); +} + +/* + * Execute ALTER EXTENSION SET SCHEMA + */ +void +AlterExtensionNamespace(List *names, const char *newschema) +{ + char *extensionName; + Oid extensionOid; + Oid nspOid; + Oid oldNspOid = InvalidOid; + Relation extRel; + ScanKeyData key[2]; + SysScanDesc extScan; + HeapTuple extTup; + Form_pg_extension extForm; + Relation depRel; + SysScanDesc depScan; + HeapTuple depTup; + + if (list_length(names) != 1) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("extension name cannot be qualified"))); + extensionName = strVal(linitial(names)); + + extensionOid = get_extension_oid(extensionName, false); + + nspOid = LookupCreationNamespace(newschema); + + /* this might later become an ownership test */ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to use ALTER EXTENSION")))); + + /* Locate the pg_extension tuple */ + extRel = heap_open(ExtensionRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(extensionOid)); + + extScan = systable_beginscan(extRel, ExtensionOidIndexId, true, + SnapshotNow, 1, key); + + extTup = systable_getnext(extScan); + + if (!HeapTupleIsValid(extTup)) /* should not happen */ + elog(ERROR, "extension with oid %u does not exist", extensionOid); + + /* Copy tuple so we can modify it below */ + extTup = heap_copytuple(extTup); + extForm = (Form_pg_extension) GETSTRUCT(extTup); + + systable_endscan(extScan); + + /* + * If the extension is already in the target schema, just silently + * do nothing. + */ + if (extForm->extnamespace == nspOid) + { + heap_close(extRel, RowExclusiveLock); + return; + } + + /* Check extension is supposed to be relocatable */ + if (!extForm->extrelocatable) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("extension \"%s\" does not support SET SCHEMA", + NameStr(extForm->extname)))); + + /* + * Scan pg_depend to find objects that depend directly on the extension, + * and alter each one's schema. + */ + depRel = heap_open(DependRelationId, AccessShareLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_refclassid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(ExtensionRelationId)); + ScanKeyInit(&key[1], + Anum_pg_depend_refobjid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(extensionOid)); + + depScan = systable_beginscan(depRel, DependReferenceIndexId, true, + SnapshotNow, 2, key); + + while (HeapTupleIsValid(depTup = systable_getnext(depScan))) + { + Form_pg_depend pg_depend = (Form_pg_depend) GETSTRUCT(depTup); + ObjectAddress dep; + Oid dep_oldNspOid; + + /* + * Ignore non-membership dependencies. (Currently, the only other + * case we could see here is a normal dependency from another + * extension.) + */ + if (pg_depend->deptype != DEPENDENCY_EXTENSION) + continue; + + dep.classId = pg_depend->classid; + dep.objectId = pg_depend->objid; + dep.objectSubId = pg_depend->objsubid; + + if (dep.objectSubId != 0) /* should not happen */ + elog(ERROR, "extension should not have a sub-object dependency"); + + dep_oldNspOid = AlterObjectNamespace_oid(dep.classId, + dep.objectId, + nspOid); + + /* + * Remember previous namespace of first object that has one + */ + if (oldNspOid == InvalidOid && dep_oldNspOid != InvalidOid) + oldNspOid = dep_oldNspOid; + + /* + * If not all the objects had the same old namespace (ignoring any + * that are not in namespaces), complain. + */ + if (dep_oldNspOid != InvalidOid && dep_oldNspOid != oldNspOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("extension \"%s\" does not support SET SCHEMA", + NameStr(extForm->extname)), + errdetail("%s is not in the extension's schema \"%s\"", + getObjectDescription(&dep), + get_namespace_name(oldNspOid)))); + } + + systable_endscan(depScan); + + relation_close(depRel, AccessShareLock); + + /* Now adjust pg_extension.extnamespace */ + extForm->extnamespace = nspOid; + + simple_heap_update(extRel, &extTup->t_self, extTup); + CatalogUpdateIndexes(extRel, extTup); + + heap_close(extRel, RowExclusiveLock); + + /* update dependencies to point to the new schema */ + changeDependencyFor(ExtensionRelationId, extensionOid, + NamespaceRelationId, oldNspOid, nspOid); +} diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c index 3a0ea9a6323..a2b5358e16f 100644 --- a/src/backend/commands/foreigncmds.c +++ b/src/backend/commands/foreigncmds.c @@ -342,6 +342,8 @@ CreateForeignDataWrapper(CreateFdwStmt *stmt) Oid fdwvalidator; Datum fdwoptions; Oid ownerId; + ObjectAddress myself; + ObjectAddress referenced; /* Must be super user */ if (!superuser()) @@ -401,15 +403,13 @@ CreateForeignDataWrapper(CreateFdwStmt *stmt) heap_freetuple(tuple); + /* record dependencies */ + myself.classId = ForeignDataWrapperRelationId; + myself.objectId = fdwId; + myself.objectSubId = 0; + if (fdwvalidator) { - ObjectAddress myself; - ObjectAddress referenced; - - myself.classId = ForeignDataWrapperRelationId; - myself.objectId = fdwId; - myself.objectSubId = 0; - referenced.classId = ProcedureRelationId; referenced.objectId = fdwvalidator; referenced.objectSubId = 0; @@ -418,6 +418,9 @@ CreateForeignDataWrapper(CreateFdwStmt *stmt) recordDependencyOnOwner(ForeignDataWrapperRelationId, fdwId, ownerId); + /* dependency on extension */ + recordDependencyOnCurrentExtension(&myself); + /* Post creation hook for new foreign data wrapper */ InvokeObjectAccessHook(OAT_POST_CREATE, ForeignDataWrapperRelationId, fdwId, 0); @@ -691,7 +694,7 @@ CreateForeignServer(CreateForeignServerStmt *stmt) heap_freetuple(tuple); - /* Add dependency on FDW and owner */ + /* record dependencies */ myself.classId = ForeignServerRelationId; myself.objectId = srvId; myself.objectSubId = 0; @@ -703,6 +706,9 @@ CreateForeignServer(CreateForeignServerStmt *stmt) recordDependencyOnOwner(ForeignServerRelationId, srvId, ownerId); + /* dependency on extension */ + recordDependencyOnCurrentExtension(&myself); + /* Post creation hook for new foreign server */ InvokeObjectAccessHook(OAT_POST_CREATE, ForeignServerRelationId, srvId, 0); @@ -974,8 +980,13 @@ CreateUserMapping(CreateUserMappingStmt *stmt) recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); if (OidIsValid(useId)) + { /* Record the mapped user dependency */ recordDependencyOnOwner(UserMappingRelationId, umId, useId); + } + + /* dependency on extension */ + recordDependencyOnCurrentExtension(&myself); /* Post creation hook for new user mapping */ InvokeObjectAccessHook(OAT_POST_CREATE, UserMappingRelationId, umId, 0); diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index dad65ee8ffa..3f25b3bf02a 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -1762,6 +1762,9 @@ CreateCast(CreateCastStmt *stmt) recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } + /* dependency on extension */ + recordDependencyOnCurrentExtension(&myself); + /* Post creation hook for new cast */ InvokeObjectAccessHook(OAT_POST_CREATE, CastRelationId, myself.objectId, 0); @@ -1875,13 +1878,7 @@ AlterFunctionNamespace(List *name, List *argtypes, bool isagg, const char *newschema) { Oid procOid; - Oid oldNspOid; Oid nspOid; - HeapTuple tup; - Relation procRel; - Form_pg_proc proc; - - procRel = heap_open(ProcedureRelationId, RowExclusiveLock); /* get function OID */ if (isagg) @@ -1889,20 +1886,33 @@ AlterFunctionNamespace(List *name, List *argtypes, bool isagg, else procOid = LookupFuncNameTypeNames(name, argtypes, false); - /* check permissions on function */ - if (!pg_proc_ownercheck(procOid, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, - NameListToString(name)); + /* get schema OID and check its permissions */ + nspOid = LookupCreationNamespace(newschema); + + AlterFunctionNamespace_oid(procOid, nspOid); +} + +Oid +AlterFunctionNamespace_oid(Oid procOid, Oid nspOid) +{ + Oid oldNspOid; + HeapTuple tup; + Relation procRel; + Form_pg_proc proc; + + procRel = heap_open(ProcedureRelationId, RowExclusiveLock); tup = SearchSysCacheCopy1(PROCOID, ObjectIdGetDatum(procOid)); if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for function %u", procOid); proc = (Form_pg_proc) GETSTRUCT(tup); - oldNspOid = proc->pronamespace; + /* check permissions on function */ + if (!pg_proc_ownercheck(procOid, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, + NameStr(proc->proname)); - /* get schema OID and check its permissions */ - nspOid = LookupCreationNamespace(newschema); + oldNspOid = proc->pronamespace; /* common checks on switching namespaces */ CheckSetNamespace(oldNspOid, nspOid, ProcedureRelationId, procOid); @@ -1916,7 +1926,7 @@ AlterFunctionNamespace(List *name, List *argtypes, bool isagg, (errcode(ERRCODE_DUPLICATE_FUNCTION), errmsg("function \"%s\" already exists in schema \"%s\"", NameStr(proc->proname), - newschema))); + get_namespace_name(nspOid)))); /* OK, modify the pg_proc row */ @@ -1930,11 +1940,13 @@ AlterFunctionNamespace(List *name, List *argtypes, bool isagg, if (changeDependencyFor(ProcedureRelationId, procOid, NamespaceRelationId, oldNspOid, nspOid) != 1) elog(ERROR, "failed to change schema dependency for function \"%s\"", - NameListToString(name)); + NameStr(proc->proname)); heap_freetuple(tup); heap_close(procRel, RowExclusiveLock); + + return oldNspOid; } diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c index 662b9420387..68072dd4218 100644 --- a/src/backend/commands/opclasscmds.c +++ b/src/backend/commands/opclasscmds.c @@ -309,6 +309,9 @@ CreateOpFamily(char *amname, char *opfname, Oid namespaceoid, Oid amoid) /* dependency on owner */ recordDependencyOnOwner(OperatorFamilyRelationId, opfamilyoid, GetUserId()); + /* dependency on extension */ + recordDependencyOnCurrentExtension(&myself); + /* Post creation hook for new operator family */ InvokeObjectAccessHook(OAT_POST_CREATE, OperatorFamilyRelationId, opfamilyoid, 0); @@ -709,6 +712,9 @@ DefineOpClass(CreateOpClassStmt *stmt) /* dependency on owner */ recordDependencyOnOwner(OperatorClassRelationId, opclassoid, GetUserId()); + /* dependency on extension */ + recordDependencyOnCurrentExtension(&myself); + /* Post creation hook for new operator class */ InvokeObjectAccessHook(OAT_POST_CREATE, OperatorClassRelationId, opclassoid, 0); @@ -1997,28 +2003,48 @@ AlterOpClassNamespace(List *name, char *access_method, const char *newschema) { Oid amOid; Relation rel; - Oid oid; + Oid opclassOid; Oid nspOid; amOid = get_am_oid(access_method, false); rel = heap_open(OperatorClassRelationId, RowExclusiveLock); - /* Look up the opclass. */ - oid = get_opclass_oid(amOid, name, false); + /* Look up the opclass */ + opclassOid = get_opclass_oid(amOid, name, false); /* get schema OID */ nspOid = LookupCreationNamespace(newschema); - AlterObjectNamespace(rel, CLAOID, OperatorClassRelationId, - oid, nspOid, - Anum_pg_opfamily_opfname, - Anum_pg_opfamily_opfnamespace, - Anum_pg_opfamily_opfowner, - ACL_KIND_OPCLASS, - false); + AlterObjectNamespace(rel, CLAOID, -1, + opclassOid, nspOid, + Anum_pg_opclass_opcname, + Anum_pg_opclass_opcnamespace, + Anum_pg_opclass_opcowner, + ACL_KIND_OPCLASS); - heap_close(rel, NoLock); + heap_close(rel, RowExclusiveLock); +} + +Oid +AlterOpClassNamespace_oid(Oid opclassOid, Oid newNspOid) +{ + Oid oldNspOid; + Relation rel; + + rel = heap_open(OperatorClassRelationId, RowExclusiveLock); + + oldNspOid = + AlterObjectNamespace(rel, CLAOID, -1, + opclassOid, newNspOid, + Anum_pg_opclass_opcname, + Anum_pg_opclass_opcnamespace, + Anum_pg_opclass_opcowner, + ACL_KIND_OPCLASS); + + heap_close(rel, RowExclusiveLock); + + return oldNspOid; } /* @@ -2186,26 +2212,46 @@ AlterOpFamilyNamespace(List *name, char *access_method, const char *newschema) { Oid amOid; Relation rel; + Oid opfamilyOid; Oid nspOid; - Oid oid; amOid = get_am_oid(access_method, false); rel = heap_open(OperatorFamilyRelationId, RowExclusiveLock); /* Look up the opfamily */ - oid = get_opfamily_oid(amOid, name, false); + opfamilyOid = get_opfamily_oid(amOid, name, false); /* get schema OID */ nspOid = LookupCreationNamespace(newschema); - AlterObjectNamespace(rel, OPFAMILYOID, OperatorFamilyRelationId, - oid, nspOid, + AlterObjectNamespace(rel, OPFAMILYOID, -1, + opfamilyOid, nspOid, Anum_pg_opfamily_opfname, Anum_pg_opfamily_opfnamespace, Anum_pg_opfamily_opfowner, - ACL_KIND_OPFAMILY, - false); + ACL_KIND_OPFAMILY); - heap_close(rel, NoLock); + heap_close(rel, RowExclusiveLock); +} + +Oid +AlterOpFamilyNamespace_oid(Oid opfamilyOid, Oid newNspOid) +{ + Oid oldNspOid; + Relation rel; + + rel = heap_open(OperatorFamilyRelationId, RowExclusiveLock); + + oldNspOid = + AlterObjectNamespace(rel, OPFAMILYOID, -1, + opfamilyOid, newNspOid, + Anum_pg_opfamily_opfname, + Anum_pg_opfamily_opfnamespace, + Anum_pg_opfamily_opfowner, + ACL_KIND_OPFAMILY); + + heap_close(rel, RowExclusiveLock); + + return oldNspOid; } diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c index 35bb76162d2..b4374a62f4f 100644 --- a/src/backend/commands/operatorcmds.c +++ b/src/backend/commands/operatorcmds.c @@ -477,12 +477,32 @@ AlterOperatorNamespace(List *names, List *argtypes, const char *newschema) /* get schema OID */ nspOid = LookupCreationNamespace(newschema); - AlterObjectNamespace(rel, OPEROID, OperatorRelationId, operOid, nspOid, + AlterObjectNamespace(rel, OPEROID, -1, + operOid, nspOid, Anum_pg_operator_oprname, Anum_pg_operator_oprnamespace, Anum_pg_operator_oprowner, - ACL_KIND_OPER, - false); + ACL_KIND_OPER); - heap_close(rel, NoLock); + heap_close(rel, RowExclusiveLock); +} + +Oid +AlterOperatorNamespace_oid(Oid operOid, Oid newNspOid) +{ + Oid oldNspOid; + Relation rel; + + rel = heap_open(OperatorRelationId, RowExclusiveLock); + + oldNspOid = AlterObjectNamespace(rel, OPEROID, -1, + operOid, newNspOid, + Anum_pg_operator_oprname, + Anum_pg_operator_oprnamespace, + Anum_pg_operator_oprowner, + ACL_KIND_OPER); + + heap_close(rel, RowExclusiveLock); + + return oldNspOid; } diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c index 3860105b266..b36f31ee6d5 100644 --- a/src/backend/commands/proclang.c +++ b/src/backend/commands/proclang.c @@ -388,20 +388,25 @@ create_proc_lang(const char *languageName, bool replace, * Create dependencies for the new language. If we are updating an * existing language, first delete any existing pg_depend entries. * (However, since we are not changing ownership or permissions, the - * shared dependencies do *not* need to change, and we leave them alone.) + * shared dependencies do *not* need to change, and we leave them alone. + * We also don't change any pre-existing extension-membership dependency.) */ myself.classId = LanguageRelationId; myself.objectId = HeapTupleGetOid(tup); myself.objectSubId = 0; if (is_update) - deleteDependencyRecordsFor(myself.classId, myself.objectId); + deleteDependencyRecordsFor(myself.classId, myself.objectId, true); /* dependency on owner of language */ if (!is_update) recordDependencyOnOwner(myself.classId, myself.objectId, languageOwner); + /* dependency on extension */ + if (!is_update) + recordDependencyOnCurrentExtension(&myself); + /* dependency on the PL handler function */ referenced.classId = ProcedureRelationId; referenced.objectId = handlerOid; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index c0a4e6f954a..f67e9b9b162 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -6851,6 +6851,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, case OCLASS_FOREIGN_SERVER: case OCLASS_USER_MAPPING: case OCLASS_DEFACL: + case OCLASS_EXTENSION: /* * We don't expect any of these sorts of objects to depend on diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c index 7afd2f896ed..81f129dff6b 100644 --- a/src/backend/commands/tsearchcmds.c +++ b/src/backend/commands/tsearchcmds.c @@ -135,6 +135,9 @@ makeParserDependencies(HeapTuple tuple) referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + /* dependency on extension */ + recordDependencyOnCurrentExtension(&myself); + /* dependencies on functions */ referenced.classId = ProcedureRelationId; referenced.objectSubId = 0; @@ -414,12 +417,33 @@ AlterTSParserNamespace(List *name, const char *newschema) /* get schema OID */ nspOid = LookupCreationNamespace(newschema); - AlterObjectNamespace(rel, TSPARSEROID, TSParserRelationId, prsId, nspOid, + AlterObjectNamespace(rel, TSPARSEROID, TSPARSERNAMENSP, + prsId, nspOid, Anum_pg_ts_parser_prsname, Anum_pg_ts_parser_prsnamespace, - -1, -1, true); + -1, -1); - heap_close(rel, NoLock); + heap_close(rel, RowExclusiveLock); +} + +Oid +AlterTSParserNamespace_oid(Oid prsId, Oid newNspOid) +{ + Oid oldNspOid; + Relation rel; + + rel = heap_open(TSParserRelationId, RowExclusiveLock); + + oldNspOid = + AlterObjectNamespace(rel, TSPARSEROID, TSPARSERNAMENSP, + prsId, newNspOid, + Anum_pg_ts_parser_prsname, + Anum_pg_ts_parser_prsnamespace, + -1, -1); + + heap_close(rel, RowExclusiveLock); + + return oldNspOid; } /* ---------------------- TS Dictionary commands -----------------------*/ @@ -447,6 +471,9 @@ makeDictionaryDependencies(HeapTuple tuple) /* dependency on owner */ recordDependencyOnOwner(myself.classId, myself.objectId, dict->dictowner); + /* dependency on extension */ + recordDependencyOnCurrentExtension(&myself); + /* dependency on template */ referenced.classId = TSTemplateRelationId; referenced.objectId = dict->dicttemplate; @@ -668,14 +695,35 @@ AlterTSDictionaryNamespace(List *name, const char *newschema) /* get schema OID */ nspOid = LookupCreationNamespace(newschema); - AlterObjectNamespace(rel, TSDICTOID, TSDictionaryRelationId, dictId, nspOid, + AlterObjectNamespace(rel, TSDICTOID, TSDICTNAMENSP, + dictId, nspOid, Anum_pg_ts_dict_dictname, Anum_pg_ts_dict_dictnamespace, Anum_pg_ts_dict_dictowner, - ACL_KIND_TSDICTIONARY, - true); + ACL_KIND_TSDICTIONARY); - heap_close(rel, NoLock); + heap_close(rel, RowExclusiveLock); +} + +Oid +AlterTSDictionaryNamespace_oid(Oid dictId, Oid newNspOid) +{ + Oid oldNspOid; + Relation rel; + + rel = heap_open(TSDictionaryRelationId, RowExclusiveLock); + + oldNspOid = + AlterObjectNamespace(rel, TSDICTOID, TSDICTNAMENSP, + dictId, newNspOid, + Anum_pg_ts_dict_dictname, + Anum_pg_ts_dict_dictnamespace, + Anum_pg_ts_dict_dictowner, + ACL_KIND_TSDICTIONARY); + + heap_close(rel, RowExclusiveLock); + + return oldNspOid; } /* @@ -1012,6 +1060,9 @@ makeTSTemplateDependencies(HeapTuple tuple) referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + /* dependency on extension */ + recordDependencyOnCurrentExtension(&myself); + /* dependencies on functions */ referenced.classId = ProcedureRelationId; referenced.objectSubId = 0; @@ -1177,13 +1228,33 @@ AlterTSTemplateNamespace(List *name, const char *newschema) /* get schema OID */ nspOid = LookupCreationNamespace(newschema); - AlterObjectNamespace(rel, TSTEMPLATEOID, TSTemplateRelationId, + AlterObjectNamespace(rel, TSTEMPLATEOID, TSTEMPLATENAMENSP, tmplId, nspOid, Anum_pg_ts_template_tmplname, Anum_pg_ts_template_tmplnamespace, - -1, -1, true); + -1, -1); - heap_close(rel, NoLock); + heap_close(rel, RowExclusiveLock); +} + +Oid +AlterTSTemplateNamespace_oid(Oid tmplId, Oid newNspOid) +{ + Oid oldNspOid; + Relation rel; + + rel = heap_open(TSTemplateRelationId, RowExclusiveLock); + + oldNspOid = + AlterObjectNamespace(rel, TSTEMPLATEOID, TSTEMPLATENAMENSP, + tmplId, newNspOid, + Anum_pg_ts_template_tmplname, + Anum_pg_ts_template_tmplnamespace, + -1, -1); + + heap_close(rel, RowExclusiveLock); + + return oldNspOid; } /* @@ -1313,10 +1384,10 @@ makeConfigurationDependencies(HeapTuple tuple, bool removeOld, myself.objectId = HeapTupleGetOid(tuple); myself.objectSubId = 0; - /* for ALTER case, first flush old dependencies */ + /* for ALTER case, first flush old dependencies, except extension deps */ if (removeOld) { - deleteDependencyRecordsFor(myself.classId, myself.objectId); + deleteDependencyRecordsFor(myself.classId, myself.objectId, true); deleteSharedDependencyRecordsFor(myself.classId, myself.objectId, 0); } @@ -1336,6 +1407,10 @@ makeConfigurationDependencies(HeapTuple tuple, bool removeOld, /* dependency on owner */ recordDependencyOnOwner(myself.classId, myself.objectId, cfg->cfgowner); + /* dependency on extension */ + if (!removeOld) + recordDependencyOnCurrentExtension(&myself); + /* dependency on parser */ referenced.classId = TSParserRelationId; referenced.objectId = cfg->cfgparser; @@ -1603,14 +1678,35 @@ AlterTSConfigurationNamespace(List *name, const char *newschema) /* get schema OID */ nspOid = LookupCreationNamespace(newschema); - AlterObjectNamespace(rel, TSCONFIGOID, TSConfigRelationId, cfgId, nspOid, + AlterObjectNamespace(rel, TSCONFIGOID, TSCONFIGNAMENSP, + cfgId, nspOid, Anum_pg_ts_config_cfgname, Anum_pg_ts_config_cfgnamespace, Anum_pg_ts_config_cfgowner, - ACL_KIND_TSCONFIGURATION, - false); + ACL_KIND_TSCONFIGURATION); - heap_close(rel, NoLock); + heap_close(rel, RowExclusiveLock); +} + +Oid +AlterTSConfigurationNamespace_oid(Oid cfgId, Oid newNspOid) +{ + Oid oldNspOid; + Relation rel; + + rel = heap_open(TSConfigRelationId, RowExclusiveLock); + + oldNspOid = + AlterObjectNamespace(rel, TSCONFIGOID, TSCONFIGNAMENSP, + cfgId, newNspOid, + Anum_pg_ts_config_cfgname, + Anum_pg_ts_config_cfgnamespace, + Anum_pg_ts_config_cfgowner, + ACL_KIND_TSCONFIGURATION); + + heap_close(rel, RowExclusiveLock); + + return oldNspOid; } /* diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 25d0f3596e1..fb9d67a30a5 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -2780,20 +2780,27 @@ AlterTypeNamespace(List *names, const char *newschema) TypeName *typename; Oid typeOid; Oid nspOid; - Oid elemOid; /* Make a TypeName so we can use standard type lookup machinery */ typename = makeTypeNameFromNameList(names); typeOid = typenameTypeId(NULL, typename); + /* get schema OID and check its permissions */ + nspOid = LookupCreationNamespace(newschema); + + AlterTypeNamespace_oid(typeOid, nspOid); +} + +Oid +AlterTypeNamespace_oid(Oid typeOid, Oid nspOid) +{ + Oid elemOid; + /* check permissions on type */ if (!pg_type_ownercheck(typeOid, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE, format_type_be(typeOid)); - /* get schema OID and check its permissions */ - nspOid = LookupCreationNamespace(newschema); - /* don't allow direct alteration of array types */ elemOid = get_element_type(typeOid); if (OidIsValid(elemOid) && get_array_type(elemOid) == typeOid) @@ -2805,7 +2812,7 @@ AlterTypeNamespace(List *names, const char *newschema) format_type_be(elemOid)))); /* and do the work */ - AlterTypeNamespaceInternal(typeOid, nspOid, false, true); + return AlterTypeNamespaceInternal(typeOid, nspOid, false, true); } /* @@ -2820,8 +2827,10 @@ AlterTypeNamespace(List *names, const char *newschema) * If errorOnTableType is TRUE, the function errors out if the type is * a table type. ALTER TABLE has to be used to move a table to a new * namespace. + * + * Returns the type's old namespace OID. */ -void +Oid AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, bool isImplicitArray, bool errorOnTableType) @@ -2928,4 +2937,6 @@ AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, /* Recursively alter the associated array type, if any */ if (OidIsValid(arrayOid)) AlterTypeNamespaceInternal(arrayOid, nspOid, true, true); + + return oldNspOid; } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 9b2c874d6d0..851186146dd 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3239,6 +3239,17 @@ _copyAlterTableSpaceOptionsStmt(AlterTableSpaceOptionsStmt *from) return newnode; } +static CreateExtensionStmt * +_copyCreateExtensionStmt(CreateExtensionStmt *from) +{ + CreateExtensionStmt *newnode = makeNode(CreateExtensionStmt); + + COPY_STRING_FIELD(extname); + COPY_NODE_FIELD(options); + + return newnode; +} + static CreateFdwStmt * _copyCreateFdwStmt(CreateFdwStmt *from) { @@ -4238,6 +4249,9 @@ copyObject(void *from) case T_AlterTableSpaceOptionsStmt: retval = _copyAlterTableSpaceOptionsStmt(from); break; + case T_CreateExtensionStmt: + retval = _copyCreateExtensionStmt(from); + break; case T_CreateFdwStmt: retval = _copyCreateFdwStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 837eafaaccb..00d23ccfa56 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1646,6 +1646,15 @@ _equalAlterTableSpaceOptionsStmt(AlterTableSpaceOptionsStmt *a, } static bool +_equalCreateExtensionStmt(CreateExtensionStmt *a, CreateExtensionStmt *b) +{ + COMPARE_STRING_FIELD(extname); + COMPARE_NODE_FIELD(options); + + return true; +} + +static bool _equalCreateFdwStmt(CreateFdwStmt *a, CreateFdwStmt *b) { COMPARE_STRING_FIELD(fdwname); @@ -2845,6 +2854,9 @@ equal(void *a, void *b) case T_AlterTableSpaceOptionsStmt: retval = _equalAlterTableSpaceOptionsStmt(a, b); break; + case T_CreateExtensionStmt: + retval = _equalCreateExtensionStmt(a, b); + break; case T_CreateFdwStmt: retval = _equalCreateFdwStmt(a, b); break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index a1bcf02f5be..4c4536b9be3 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -191,7 +191,7 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_ AlterDefaultPrivilegesStmt DefACLAction AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt - CreateDomainStmt CreateGroupStmt CreateOpClassStmt + CreateDomainStmt CreateExtensionStmt CreateGroupStmt CreateOpClassStmt CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt @@ -227,9 +227,9 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_ %type <dbehavior> opt_drop_behavior %type <list> createdb_opt_list alterdb_opt_list copy_opt_list - transaction_mode_list + transaction_mode_list create_extension_opt_list %type <defelt> createdb_opt_item alterdb_opt_item copy_opt_item - transaction_mode_item + transaction_mode_item create_extension_opt_item %type <ival> opt_lock lock_type cast_context %type <ival> vacuum_option_list vacuum_option_elem @@ -492,7 +492,8 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_ DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EXCEPT - EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT + EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN + EXTENSION EXTERNAL EXTRACT FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS @@ -692,6 +693,7 @@ stmt : | CreateCastStmt | CreateConversionStmt | CreateDomainStmt + | CreateExtensionStmt | CreateFdwStmt | CreateForeignServerStmt | CreateForeignTableStmt @@ -3218,6 +3220,37 @@ DropTableSpaceStmt: DROP TABLESPACE name /***************************************************************************** * * QUERY: + * CREATE EXTENSION extension + * [ WITH ] [ SCHEMA [=] schema ] + * + *****************************************************************************/ + +CreateExtensionStmt: CREATE EXTENSION name opt_with create_extension_opt_list + { + CreateExtensionStmt *n = makeNode(CreateExtensionStmt); + n->extname = $3; + n->options = $5; + $$ = (Node *) n; + } + ; + +create_extension_opt_list: + create_extension_opt_list create_extension_opt_item + { $$ = lappend($1, $2); } + | /* EMPTY */ + { $$ = NIL; } + ; + +create_extension_opt_item: + SCHEMA opt_equal name + { + $$ = makeDefElem("schema", (Node *)makeString($3)); + } + ; + +/***************************************************************************** + * + * QUERY: * CREATE FOREIGN DATA WRAPPER name [ VALIDATOR name ] * *****************************************************************************/ @@ -4340,11 +4373,12 @@ drop_type: TABLE { $$ = OBJECT_TABLE; } | SEQUENCE { $$ = OBJECT_SEQUENCE; } | VIEW { $$ = OBJECT_VIEW; } | INDEX { $$ = OBJECT_INDEX; } - | TYPE_P { $$ = OBJECT_TYPE; } | FOREIGN TABLE { $$ = OBJECT_FOREIGN_TABLE; } + | TYPE_P { $$ = OBJECT_TYPE; } | DOMAIN_P { $$ = OBJECT_DOMAIN; } | CONVERSION_P { $$ = OBJECT_CONVERSION; } | SCHEMA { $$ = OBJECT_SCHEMA; } + | EXTENSION { $$ = OBJECT_EXTENSION; } | TEXT_P SEARCH PARSER { $$ = OBJECT_TSPARSER; } | TEXT_P SEARCH DICTIONARY { $$ = OBJECT_TSDICTIONARY; } | TEXT_P SEARCH TEMPLATE { $$ = OBJECT_TSTEMPLATE; } @@ -4398,7 +4432,7 @@ opt_restart_seqs: * * COMMENT ON [ [ DATABASE | DOMAIN | INDEX | SEQUENCE | TABLE | TYPE | VIEW | * CONVERSION | LANGUAGE | OPERATOR CLASS | LARGE OBJECT | - * CAST | COLUMN | SCHEMA | TABLESPACE | ROLE | + * CAST | COLUMN | SCHEMA | TABLESPACE | EXTENSION | ROLE | * TEXT SEARCH PARSER | TEXT SEARCH DICTIONARY | * TEXT SEARCH TEMPLATE | TEXT SEARCH CONFIGURATION | * FOREIGN TABLE ] <objname> | @@ -4577,6 +4611,7 @@ comment_type: | VIEW { $$ = OBJECT_VIEW; } | CONVERSION_P { $$ = OBJECT_CONVERSION; } | TABLESPACE { $$ = OBJECT_TABLESPACE; } + | EXTENSION { $$ = OBJECT_EXTENSION; } | ROLE { $$ = OBJECT_ROLE; } | FOREIGN TABLE { $$ = OBJECT_FOREIGN_TABLE; } ; @@ -6271,6 +6306,14 @@ AlterObjectSchemaStmt: n->newschema = $6; $$ = (Node *)n; } + | ALTER EXTENSION any_name SET SCHEMA name + { + AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt); + n->objectType = OBJECT_EXTENSION; + n->object = $3; + n->newschema = $6; + $$ = (Node *)n; + } | ALTER FUNCTION function_with_argtypes SET SCHEMA name { AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt); @@ -11462,6 +11505,7 @@ unreserved_keyword: | EXCLUSIVE | EXECUTE | EXPLAIN + | EXTENSION | EXTERNAL | FAMILY | FIRST_P diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c index 99c397b4f20..fecc4e27fa3 100644 --- a/src/backend/rewrite/rewriteDefine.c +++ b/src/backend/rewrite/rewriteDefine.c @@ -143,7 +143,7 @@ InsertRule(char *rulname, /* If replacing, get rid of old dependencies and make new ones */ if (is_update) - deleteDependencyRecordsFor(RewriteRelationId, rewriteObjectId); + deleteDependencyRecordsFor(RewriteRelationId, rewriteObjectId, false); /* * Install dependency on rule's relation to ensure it will go away on diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index af2eba01d61..10a4438995f 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -32,6 +32,7 @@ #include "commands/defrem.h" #include "commands/discard.h" #include "commands/explain.h" +#include "commands/extension.h" #include "commands/lockcmds.h" #include "commands/portalcmds.h" #include "commands/prepare.h" @@ -210,6 +211,7 @@ check_xact_readonly(Node *parsetree) case T_ReassignOwnedStmt: case T_AlterTSDictionaryStmt: case T_AlterTSConfigurationStmt: + case T_CreateExtensionStmt: case T_CreateFdwStmt: case T_AlterFdwStmt: case T_DropFdwStmt: @@ -594,6 +596,10 @@ standard_ProcessUtility(Node *parsetree, AlterTableSpaceOptions((AlterTableSpaceOptionsStmt *) parsetree); break; + case T_CreateExtensionStmt: + CreateExtension((CreateExtensionStmt *) parsetree); + break; + case T_CreateFdwStmt: CreateForeignDataWrapper((CreateFdwStmt *) parsetree); break; @@ -673,6 +679,10 @@ standard_ProcessUtility(Node *parsetree, RemoveTSConfigurations(stmt); break; + case OBJECT_EXTENSION: + RemoveExtensions(stmt); + break; + default: elog(ERROR, "unrecognized drop object type: %d", (int) stmt->removeType); @@ -1544,6 +1554,10 @@ CreateCommandTag(Node *parsetree) tag = "ALTER TABLESPACE"; break; + case T_CreateExtensionStmt: + tag = "CREATE EXTENSION"; + break; + case T_CreateFdwStmt: tag = "CREATE FOREIGN DATA WRAPPER"; break; @@ -1626,6 +1640,9 @@ CreateCommandTag(Node *parsetree) case OBJECT_FOREIGN_TABLE: tag = "DROP FOREIGN TABLE"; break; + case OBJECT_EXTENSION: + tag = "DROP EXTENSION"; + break; default: tag = "???"; } @@ -1741,6 +1758,9 @@ CreateCommandTag(Node *parsetree) case OBJECT_DOMAIN: tag = "ALTER DOMAIN"; break; + case OBJECT_EXTENSION: + tag = "ALTER EXTENSION"; + break; case OBJECT_OPERATOR: tag = "ALTER OPERATOR"; break; @@ -2382,6 +2402,10 @@ GetCommandLogLevel(Node *parsetree) lev = LOGSTMT_DDL; break; + case T_CreateExtensionStmt: + lev = LOGSTMT_DDL; + break; + case T_CreateFdwStmt: case T_AlterFdwStmt: case T_DropFdwStmt: diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c index 93bc401c2d1..c3ec98aa5e2 100644 --- a/src/backend/utils/adt/genfile.c +++ b/src/backend/utils/adt/genfile.c @@ -82,22 +82,16 @@ convert_and_check_filename(text *arg) /* * Read a section of a file, returning it as bytea * - * We read the whole of the file when bytes_to_read is nagative. + * Caller is responsible for all permissions checking. + * + * We read the whole of the file when bytes_to_read is negative. */ -static bytea * -read_binary_file(text *filename_t, int64 seek_offset, int64 bytes_to_read) +bytea * +read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read) { bytea *buf; size_t nbytes; FILE *file; - char *filename; - - if (!superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - (errmsg("must be superuser to read files")))); - - filename = convert_and_check_filename(filename_t); if (bytes_to_read < 0) { @@ -146,7 +140,6 @@ read_binary_file(text *filename_t, int64 seek_offset, int64 bytes_to_read) SET_VARSIZE(buf, nbytes + VARHDRSZ); FreeFile(file); - pfree(filename); return buf; } @@ -156,9 +149,11 @@ read_binary_file(text *filename_t, int64 seek_offset, int64 bytes_to_read) * in the database encoding. */ static text * -read_text_file(text *filename, int64 seek_offset, int64 bytes_to_read) +read_text_file(const char *filename, int64 seek_offset, int64 bytes_to_read) { - bytea *buf = read_binary_file(filename, seek_offset, bytes_to_read); + bytea *buf; + + buf = read_binary_file(filename, seek_offset, bytes_to_read); /* Make sure the input is valid */ pg_verifymbstr(VARDATA(buf), VARSIZE(buf) - VARHDRSZ, false); @@ -176,13 +171,21 @@ pg_read_file(PG_FUNCTION_ARGS) text *filename_t = PG_GETARG_TEXT_P(0); int64 seek_offset = PG_GETARG_INT64(1); int64 bytes_to_read = PG_GETARG_INT64(2); + char *filename; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to read files")))); + + filename = convert_and_check_filename(filename_t); if (bytes_to_read < 0) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("requested length cannot be negative"))); - PG_RETURN_TEXT_P(read_text_file(filename_t, seek_offset, bytes_to_read)); + PG_RETURN_TEXT_P(read_text_file(filename, seek_offset, bytes_to_read)); } /* @@ -192,8 +195,16 @@ Datum pg_read_file_all(PG_FUNCTION_ARGS) { text *filename_t = PG_GETARG_TEXT_P(0); + char *filename; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to read files")))); + + filename = convert_and_check_filename(filename_t); - PG_RETURN_TEXT_P(read_text_file(filename_t, 0, -1)); + PG_RETURN_TEXT_P(read_text_file(filename, 0, -1)); } /* @@ -205,13 +216,21 @@ pg_read_binary_file(PG_FUNCTION_ARGS) text *filename_t = PG_GETARG_TEXT_P(0); int64 seek_offset = PG_GETARG_INT64(1); int64 bytes_to_read = PG_GETARG_INT64(2); + char *filename; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to read files")))); + + filename = convert_and_check_filename(filename_t); if (bytes_to_read < 0) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("requested length cannot be negative"))); - PG_RETURN_BYTEA_P(read_binary_file(filename_t, seek_offset, bytes_to_read)); + PG_RETURN_BYTEA_P(read_binary_file(filename, seek_offset, bytes_to_read)); } /* @@ -221,8 +240,16 @@ Datum pg_read_binary_file_all(PG_FUNCTION_ARGS) { text *filename_t = PG_GETARG_TEXT_P(0); + char *filename; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to read files")))); + + filename = convert_and_check_filename(filename_t); - PG_RETURN_BYTEA_P(read_binary_file(filename_t, 0, -1)); + PG_RETURN_BYTEA_P(read_binary_file(filename, 0, -1)); } /* diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c index 55cc3126e1e..cc0db663200 100644 --- a/src/bin/pg_dump/common.c +++ b/src/bin/pg_dump/common.c @@ -84,6 +84,7 @@ getSchemaData(int *numTablesPtr) RuleInfo *ruleinfo; ProcLangInfo *proclanginfo; CastInfo *castinfo; + ExtensionInfo *extinfo; OpclassInfo *opcinfo; OpfamilyInfo *opfinfo; ConvInfo *convinfo; @@ -100,6 +101,7 @@ getSchemaData(int *numTablesPtr) int numRules; int numProcLangs; int numCasts; + int numExtensions; int numOpclasses; int numOpfamilies; int numConversions; @@ -197,6 +199,11 @@ getSchemaData(int *numTablesPtr) write_msg(NULL, "reading type casts\n"); castinfo = getCasts(&numCasts); + /* this must be after getTables */ + if (g_verbose) + write_msg(NULL, "reading extensions\n"); + extinfo = getExtensions(&numExtensions); + /* Link tables to parents, mark parents of target tables interesting */ if (g_verbose) write_msg(NULL, "finding inheritance relationships\n"); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 49c570016ad..dec96bc0253 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -161,6 +161,7 @@ static int findSecLabels(Archive *fout, Oid classoid, Oid objoid, static int collectSecLabels(Archive *fout, SecLabelItem **items); static void dumpDumpableObject(Archive *fout, DumpableObject *dobj); static void dumpNamespace(Archive *fout, NamespaceInfo *nspinfo); +static void dumpExtension(Archive *fout, ExtensionInfo *extinfo); static void dumpType(Archive *fout, TypeInfo *tyinfo); static void dumpBaseType(Archive *fout, TypeInfo *tyinfo); static void dumpEnumType(Archive *fout, TypeInfo *tyinfo); @@ -204,6 +205,7 @@ static void dumpACL(Archive *fout, CatalogId objCatId, DumpId objDumpId, static void getDependencies(void); static void getDomainConstraints(TypeInfo *tyinfo); static void getTableData(TableInfo *tblinfo, int numTables, bool oids); +static void makeTableDataInfo(TableInfo *tbinfo, bool oids); static void getTableDataFKConstraints(void); static char *format_function_arguments(FuncInfo *finfo, char *funcargs); static char *format_function_arguments_old(FuncInfo *finfo, int nallargs, @@ -764,6 +766,9 @@ main(int argc, char **argv) /* * Collect dependency data to assist in ordering the objects. + * + * (In 9.1 and later, this also marks extension member objects as + * not to be dumped.) */ getDependencies(); @@ -1232,6 +1237,23 @@ dumpTableData_copy(Archive *fout, void *dcontext) classname), column_list); } + else if (tdinfo->filtercond) + { + /* Note: this syntax is only supported in 8.2 and up */ + appendPQExpBufferStr(q, "COPY (SELECT "); + /* klugery to get rid of parens in column list */ + if (strlen(column_list) > 2) + { + appendPQExpBufferStr(q, column_list + 1); + q->data[q->len - 1] = ' '; + } + else + appendPQExpBufferStr(q, "* "); + appendPQExpBuffer(q, "FROM %s %s) TO stdout;", + fmtQualifiedId(tbinfo->dobj.namespace->dobj.name, + classname), + tdinfo->filtercond); + } else { appendPQExpBuffer(q, "COPY %s %s TO stdout;", @@ -1356,6 +1378,8 @@ dumpTableData_insert(Archive *fout, void *dcontext) fmtQualifiedId(tbinfo->dobj.namespace->dobj.name, classname)); } + if (tdinfo->filtercond) + appendPQExpBuffer(q, " %s", tdinfo->filtercond); res = PQexec(g_conn, q->data); check_sql_result(res, g_conn, q->data, PGRES_COMMAND_OK); @@ -1480,10 +1504,15 @@ static void dumpTableData(Archive *fout, TableDataInfo *tdinfo) { TableInfo *tbinfo = tdinfo->tdtable; - PQExpBuffer copyBuf = createPQExpBuffer(); + PQExpBuffer copyBuf; DataDumperPtr dumpFn; char *copyStmt; + if (!tdinfo->dobj.dump) + return; + + copyBuf = createPQExpBuffer(); + if (!dump_inserts) { /* Dump/restore using COPY */ @@ -1539,30 +1568,40 @@ getTableData(TableInfo *tblinfo, int numTables, bool oids) && no_unlogged_table_data) continue; - if (tblinfo[i].dobj.dump) - { - TableDataInfo *tdinfo; + if (tblinfo[i].dobj.dump && tblinfo[i].dataObj == NULL) + makeTableDataInfo(&(tblinfo[i]), oids); + } +} - tdinfo = (TableDataInfo *) malloc(sizeof(TableDataInfo)); +/* + * Make a dumpable object for the data of this specific table + */ +static void +makeTableDataInfo(TableInfo *tbinfo, bool oids) +{ + TableDataInfo *tdinfo; - tdinfo->dobj.objType = DO_TABLE_DATA; + tdinfo = (TableDataInfo *) malloc(sizeof(TableDataInfo)); - /* - * Note: use tableoid 0 so that this object won't be mistaken for - * something that pg_depend entries apply to. - */ - tdinfo->dobj.catId.tableoid = 0; - tdinfo->dobj.catId.oid = tblinfo[i].dobj.catId.oid; - AssignDumpId(&tdinfo->dobj); - tdinfo->dobj.name = tblinfo[i].dobj.name; - tdinfo->dobj.namespace = tblinfo[i].dobj.namespace; - tdinfo->tdtable = &(tblinfo[i]); - tdinfo->oids = oids; - addObjectDependency(&tdinfo->dobj, tblinfo[i].dobj.dumpId); - - tblinfo[i].dataObj = tdinfo; - } - } + tdinfo->dobj.objType = DO_TABLE_DATA; + + /* + * Note: use tableoid 0 so that this object won't be mistaken for + * something that pg_depend entries apply to. + */ + tdinfo->dobj.catId.tableoid = 0; + tdinfo->dobj.catId.oid = tbinfo->dobj.catId.oid; + AssignDumpId(&tdinfo->dobj); + tdinfo->dobj.name = tbinfo->dobj.name; + tdinfo->dobj.namespace = tbinfo->dobj.namespace; + tdinfo->dobj.dump = true; + tdinfo->tdtable = tbinfo; + tdinfo->oids = oids; + tdinfo->ext_config = false; /* might get set later */ + tdinfo->filtercond = NULL; /* might get set later */ + addObjectDependency(&tdinfo->dobj, tbinfo->dobj.dumpId); + + tbinfo->dataObj = tdinfo; } /* @@ -2585,6 +2624,123 @@ findNamespace(Oid nsoid, Oid objoid) } /* + * getExtensions: + * read all extensions in the system catalogs and return them in the + * ExtensionInfo* structure + * + * numExtensions is set to the number of extensions read in + */ +ExtensionInfo * +getExtensions(int *numExtensions) +{ + PGresult *res; + int ntups; + int i; + int j; + PQExpBuffer query; + ExtensionInfo *extinfo; + int i_tableoid; + int i_oid; + int i_extname; + int i_nspname; + int i_extconfig; + int i_extcondition; + + /* + * Before 9.1, there are no extensions. + */ + if (g_fout->remoteVersion < 90100) + { + *numExtensions = 0; + return NULL; + } + + query = createPQExpBuffer(); + + /* Make sure we are in proper schema */ + selectSourceSchema("pg_catalog"); + + appendPQExpBuffer(query, "SELECT x.tableoid, x.oid, " + "x.extname, n.nspname, x.extconfig, x.extcondition " + "FROM pg_extension x " + "JOIN pg_namespace n ON n.oid = x.extnamespace"); + + res = PQexec(g_conn, query->data); + check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + + extinfo = (ExtensionInfo *) malloc(ntups * sizeof(ExtensionInfo)); + + i_tableoid = PQfnumber(res, "tableoid"); + i_oid = PQfnumber(res, "oid"); + i_extname = PQfnumber(res, "extname"); + i_nspname = PQfnumber(res, "nspname"); + i_extconfig = PQfnumber(res, "extconfig"); + i_extcondition = PQfnumber(res, "extcondition"); + + for (i = 0; i < ntups; i++) + { + char *extconfig; + char *extcondition; + char **extconfigarray = NULL; + char **extconditionarray = NULL; + int nconfigitems; + int nconditionitems; + + extinfo[i].dobj.objType = DO_EXTENSION; + extinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid)); + extinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid)); + AssignDumpId(&extinfo[i].dobj); + extinfo[i].dobj.name = strdup(PQgetvalue(res, i, i_extname)); + extinfo[i].namespace = strdup(PQgetvalue(res, i, i_nspname)); + + /* For the moment, all extensions are considered dumpable */ + extinfo->dobj.dump = true; + + /* + * Find and mark any configuration tables for this extension. + * + * Note that we create TableDataInfo objects even in schemaOnly mode, + * ie, user data in a configuration table is treated like schema data. + * This seems appropriate since system data in a config table would + * get reloaded by CREATE EXTENSION. + */ + extconfig = PQgetvalue(res, i, i_extconfig); + extcondition = PQgetvalue(res, i, i_extcondition); + if (parsePGArray(extconfig, &extconfigarray, &nconfigitems) && + parsePGArray(extcondition, &extconditionarray, &nconditionitems) && + nconfigitems == nconditionitems) + { + for (j = 0; j < nconfigitems; j++) + { + TableInfo *configtbl; + + configtbl = findTableByOid(atooid(extconfigarray[j])); + if (configtbl && configtbl->dataObj == NULL) + { + makeTableDataInfo(configtbl, false); + configtbl->dataObj->ext_config = true; + if (strlen(extconditionarray[j]) > 0) + configtbl->dataObj->filtercond = strdup(extconditionarray[j]); + } + } + } + if (extconfigarray) + free(extconfigarray); + if (extconditionarray) + free(extconditionarray); + } + + PQclear(res); + destroyPQExpBuffer(query); + + *numExtensions = ntups; + + return extinfo; +} + +/* * getTypes: * read all types in the system catalogs and return them in the * TypeInfo* structure @@ -5044,6 +5200,9 @@ getProcLangs(int *numProcLangs) else planginfo[i].lanowner = strdup(""); + /* Assume it should be dumped (getDependencies may override this) */ + planginfo[i].dobj.dump = true; + if (g_fout->remoteVersion < 70300) { /* @@ -5151,6 +5310,9 @@ getCasts(int *numCasts) castinfo[i].castcontext = *(PQgetvalue(res, i, i_castcontext)); castinfo[i].castmethod = *(PQgetvalue(res, i, i_castmethod)); + /* Assume it should be dumped (getDependencies may override this) */ + castinfo[i].dobj.dump = true; + /* * Try to name cast as concatenation of typnames. This is only used * for purposes of sorting. If we fail to find either type, the name @@ -6585,6 +6747,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj) case DO_NAMESPACE: dumpNamespace(fout, (NamespaceInfo *) dobj); break; + case DO_EXTENSION: + dumpExtension(fout, (ExtensionInfo *) dobj); + break; case DO_TYPE: dumpType(fout, (TypeInfo *) dobj); break; @@ -6735,6 +6900,56 @@ dumpNamespace(Archive *fout, NamespaceInfo *nspinfo) } /* + * dumpExtension + * writes out to fout the queries to recreate an extension + */ +static void +dumpExtension(Archive *fout, ExtensionInfo *extinfo) +{ + PQExpBuffer q; + PQExpBuffer delq; + char *qextname; + + /* Skip if not to be dumped */ + if (!extinfo->dobj.dump || dataOnly) + return; + + q = createPQExpBuffer(); + delq = createPQExpBuffer(); + + qextname = strdup(fmtId(extinfo->dobj.name)); + + appendPQExpBuffer(delq, "DROP EXTENSION %s;\n", qextname); + + appendPQExpBuffer(q, "CREATE EXTENSION %s WITH SCHEMA %s;\n", + qextname, fmtId(extinfo->namespace)); + + ArchiveEntry(fout, extinfo->dobj.catId, extinfo->dobj.dumpId, + extinfo->dobj.name, + NULL, NULL, + "", + false, "EXTENSION", SECTION_PRE_DATA, + q->data, delq->data, NULL, + extinfo->dobj.dependencies, extinfo->dobj.nDeps, + NULL, NULL); + + /* Dump Extension Comments and Security Labels */ + resetPQExpBuffer(q); + appendPQExpBuffer(q, "EXTENSION %s", qextname); + dumpComment(fout, q->data, + NULL, "", + extinfo->dobj.catId, 0, extinfo->dobj.dumpId); + dumpSecLabel(fout, q->data, + NULL, "", + extinfo->dobj.catId, 0, extinfo->dobj.dumpId); + + free(qextname); + + destroyPQExpBuffer(q); + destroyPQExpBuffer(delq); +} + +/* * dumpType * writes out to fout the queries to recreate a user-defined type */ @@ -7696,7 +7911,8 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang) FuncInfo *inlineInfo = NULL; FuncInfo *validatorInfo = NULL; - if (dataOnly) + /* Skip if not to be dumped */ + if (!plang->dobj.dump || dataOnly) return; /* @@ -8418,7 +8634,8 @@ dumpCast(Archive *fout, CastInfo *cast) TypeInfo *sourceInfo; TypeInfo *targetInfo; - if (dataOnly) + /* Skip if not to be dumped */ + if (!cast->dobj.dump || dataOnly) return; if (OidIsValid(cast->castfunc)) @@ -12747,6 +12964,24 @@ getDependencies(void) else /* normal case */ addObjectDependency(dobj, refdobj->dumpId); + + /* + * If it's an extension-membership dependency, mark the member + * object as not to be dumped. We still need the dependency links, + * though, to ensure that sorting is done correctly. + */ + if (deptype == 'e') + { + dobj->dump = false; + if (dobj->objType == DO_TABLE) + { + /* Mark the data as not to be dumped either, unless config */ + TableDataInfo *tdinfo = ((TableInfo *) dobj)->dataObj; + + if (tdinfo && !tdinfo->ext_config) + tdinfo->dobj.dump = false; + } + } } PQclear(res); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 43fd1ade27f..f0e9ae1e064 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -89,6 +89,7 @@ typedef enum { /* When modifying this enum, update priority tables in pg_dump_sort.c! */ DO_NAMESPACE, + DO_EXTENSION, DO_TYPE, DO_SHELL_TYPE, DO_FUNC, @@ -139,6 +140,12 @@ typedef struct _namespaceInfo char *nspacl; } NamespaceInfo; +typedef struct _extensionInfo +{ + DumpableObject dobj; + char *namespace; /* schema containing extension's objects */ +} ExtensionInfo; + typedef struct _typeInfo { DumpableObject dobj; @@ -288,6 +295,8 @@ typedef struct _tableDataInfo DumpableObject dobj; TableInfo *tdtable; /* link to table to dump */ bool oids; /* include OIDs in data? */ + bool ext_config; /* is table an extension config table? */ + char *filtercond; /* WHERE condition to limit rows dumped */ } TableDataInfo; typedef struct _indxInfo @@ -513,6 +522,7 @@ extern void sortDumpableObjectsByTypeOid(DumpableObject **objs, int numObjs); * version specific routines */ extern NamespaceInfo *getNamespaces(int *numNamespaces); +extern ExtensionInfo *getExtensions(int *numExtensions); extern TypeInfo *getTypes(int *numTypes); extern FuncInfo *getFuncs(int *numFuncs); extern AggInfo *getAggregates(int *numAggregates); diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index fe5cf56e695..f1c1c65e6cf 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -22,13 +22,14 @@ static const char *modulename = gettext_noop("sorter"); * Sort priority for object types when dumping a pre-7.3 database. * Objects are sorted by priority levels, and within an equal priority level * by OID. (This is a relatively crude hack to provide semi-reasonable - * behavior for old databases without full dependency info.) Note: text - * search, foreign-data, and default ACL objects can't really happen here, + * behavior for old databases without full dependency info.) Note: extensions, + * text search, foreign-data, and default ACL objects can't really happen here, * so the rather bogus priorities for them don't matter. */ static const int oldObjectTypePriority[] = { 1, /* DO_NAMESPACE */ + 1, /* DO_EXTENSION */ 2, /* DO_TYPE */ 2, /* DO_SHELL_TYPE */ 2, /* DO_FUNC */ @@ -66,34 +67,35 @@ static const int oldObjectTypePriority[] = static const int newObjectTypePriority[] = { 1, /* DO_NAMESPACE */ - 3, /* DO_TYPE */ - 3, /* DO_SHELL_TYPE */ - 4, /* DO_FUNC */ - 5, /* DO_AGG */ - 6, /* DO_OPERATOR */ - 7, /* DO_OPCLASS */ - 7, /* DO_OPFAMILY */ - 9, /* DO_CONVERSION */ - 16, /* DO_TABLE */ - 18, /* DO_ATTRDEF */ - 23, /* DO_INDEX */ - 24, /* DO_RULE */ - 25, /* DO_TRIGGER */ - 22, /* DO_CONSTRAINT */ - 26, /* DO_FK_CONSTRAINT */ + 3, /* DO_EXTENSION */ + 4, /* DO_TYPE */ + 4, /* DO_SHELL_TYPE */ + 5, /* DO_FUNC */ + 6, /* DO_AGG */ + 7, /* DO_OPERATOR */ + 8, /* DO_OPCLASS */ + 8, /* DO_OPFAMILY */ + 10, /* DO_CONVERSION */ + 17, /* DO_TABLE */ + 19, /* DO_ATTRDEF */ + 24, /* DO_INDEX */ + 25, /* DO_RULE */ + 26, /* DO_TRIGGER */ + 23, /* DO_CONSTRAINT */ + 27, /* DO_FK_CONSTRAINT */ 2, /* DO_PROCLANG */ - 8, /* DO_CAST */ - 20, /* DO_TABLE_DATA */ - 17, /* DO_DUMMY_TYPE */ - 10, /* DO_TSPARSER */ - 12, /* DO_TSDICT */ - 11, /* DO_TSTEMPLATE */ - 13, /* DO_TSCONFIG */ - 14, /* DO_FDW */ - 15, /* DO_FOREIGN_SERVER */ - 27, /* DO_DEFAULT_ACL */ - 19, /* DO_BLOB */ - 21 /* DO_BLOB_DATA */ + 9, /* DO_CAST */ + 21, /* DO_TABLE_DATA */ + 18, /* DO_DUMMY_TYPE */ + 11, /* DO_TSPARSER */ + 13, /* DO_TSDICT */ + 12, /* DO_TSTEMPLATE */ + 14, /* DO_TSCONFIG */ + 15, /* DO_FDW */ + 16, /* DO_FOREIGN_SERVER */ + 28, /* DO_DEFAULT_ACL */ + 20, /* DO_BLOB */ + 22 /* DO_BLOB_DATA */ }; @@ -1023,6 +1025,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize) "SCHEMA %s (ID %d OID %u)", obj->name, obj->dumpId, obj->catId.oid); return; + case DO_EXTENSION: + snprintf(buf, bufsize, + "EXTENSION %s (ID %d OID %u)", + obj->name, obj->dumpId, obj->catId.oid); + return; case DO_TYPE: snprintf(buf, bufsize, "TYPE %s (ID %d OID %u)", diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 301dc11bcc2..a80678c2c3f 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -495,6 +495,12 @@ exec_command(const char *cmd, break; } break; + case 'x': /* Extensions */ + if (show_verbose) + success = listExtensionContents(pattern); + else + success = listExtensions(pattern); + break; default: status = PSQL_CMD_UNKNOWN; } diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index d6c6cf1f15e..0342eb55bdc 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -38,6 +38,7 @@ static bool describeOneTSConfig(const char *oid, const char *nspname, const char *cfgname, const char *pnspname, const char *prsname); static void printACLColumn(PQExpBuffer buf, const char *colname); +static bool listOneExtensionContents(const char *extname, const char *oid); /*---------------- @@ -3671,7 +3672,7 @@ listForeignTables(const char *pattern, bool verbose) if (pset.sversion < 90100) { - fprintf(stderr, _("The server (version %d.%d) does not support foreign table.\n"), + fprintf(stderr, _("The server (version %d.%d) does not support foreign tables.\n"), pset.sversion / 10000, (pset.sversion / 100) % 100); return true; } @@ -3719,6 +3720,167 @@ listForeignTables(const char *pattern, bool verbose) } /* + * \dx + * + * Briefly describes installed extensions. + */ +bool +listExtensions(const char *pattern) +{ + PQExpBufferData buf; + PGresult *res; + printQueryOpt myopt = pset.popt; + + if (pset.sversion < 90100) + { + fprintf(stderr, _("The server (version %d.%d) does not support extensions.\n"), + pset.sversion / 10000, (pset.sversion / 100) % 100); + return true; + } + + initPQExpBuffer(&buf); + printfPQExpBuffer(&buf, + "SELECT e.extname AS \"%s\", " + "e.extversion AS \"%s\", n.nspname AS \"%s\", c.description AS \"%s\"\n" + "FROM pg_catalog.pg_extension e " + "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace " + "LEFT JOIN pg_catalog.pg_description c ON c.objoid = e.oid " + "AND c.classoid = 'pg_catalog.pg_extension'::pg_catalog.regclass\n", + gettext_noop("Name"), + gettext_noop("Version"), + gettext_noop("Schema"), + gettext_noop("Description")); + + processSQLNamePattern(pset.db, &buf, pattern, + false, false, + NULL, "e.extname", NULL, + NULL); + + appendPQExpBuffer(&buf, "ORDER BY 1;"); + + res = PSQLexec(buf.data, false); + termPQExpBuffer(&buf); + if (!res) + return false; + + myopt.nullPrint = NULL; + myopt.title = _("List of installed extensions"); + myopt.translate_header = true; + + printQuery(res, &myopt, pset.queryFout, pset.logfile); + + PQclear(res); + return true; +} + +/* + * \dx+ + * + * List contents of installed extensions. + */ +bool +listExtensionContents(const char *pattern) +{ + PQExpBufferData buf; + PGresult *res; + int i; + + if (pset.sversion < 90100) + { + fprintf(stderr, _("The server (version %d.%d) does not support extensions.\n"), + pset.sversion / 10000, (pset.sversion / 100) % 100); + return true; + } + + initPQExpBuffer(&buf); + printfPQExpBuffer(&buf, + "SELECT e.extname, e.oid\n" + "FROM pg_catalog.pg_extension e\n"); + + processSQLNamePattern(pset.db, &buf, pattern, + false, false, + NULL, "e.extname", NULL, + NULL); + + appendPQExpBuffer(&buf, "ORDER BY 1;"); + + res = PSQLexec(buf.data, false); + termPQExpBuffer(&buf); + if (!res) + return false; + + if (PQntuples(res) == 0) + { + if (!pset.quiet) + { + if (pattern) + fprintf(stderr, _("Did not find any extension named \"%s\".\n"), + pattern); + else + fprintf(stderr, _("Did not find any extensions.\n")); + } + PQclear(res); + return false; + } + + for (i = 0; i < PQntuples(res); i++) + { + const char *extname; + const char *oid; + + extname = PQgetvalue(res, i, 0); + oid = PQgetvalue(res, i, 1); + + if (!listOneExtensionContents(extname, oid)) + { + PQclear(res); + return false; + } + if (cancel_pressed) + { + PQclear(res); + return false; + } + } + + PQclear(res); + return true; +} + +static bool +listOneExtensionContents(const char *extname, const char *oid) +{ + PQExpBufferData buf; + PGresult *res; + char title[1024]; + printQueryOpt myopt = pset.popt; + + initPQExpBuffer(&buf); + printfPQExpBuffer(&buf, + "SELECT pg_catalog.pg_describe_object(classid, objid, 0) AS \"%s\"\n" + "FROM pg_catalog.pg_depend\n" + "WHERE refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass AND refobjid = '%s' AND deptype = 'e'\n" + "ORDER BY 1;", + gettext_noop("Object Description"), + oid); + + res = PSQLexec(buf.data, false); + termPQExpBuffer(&buf); + if (!res) + return false; + + myopt.nullPrint = NULL; + snprintf(title, sizeof(title), _("Objects in extension \"%s\""), extname); + myopt.title = title; + myopt.translate_header = true; + + printQuery(res, &myopt, pset.queryFout, pset.logfile); + + PQclear(res); + return true; +} + +/* * printACLColumn * * Helper function for consistently formatting ACL (privilege) columns. diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h index 4e80bcf41f8..4b690b3b707 100644 --- a/src/bin/psql/describe.h +++ b/src/bin/psql/describe.h @@ -87,4 +87,10 @@ extern bool listForeignTables(const char *pattern, bool verbose); /* \dL */ extern bool listLanguages(const char *pattern, bool verbose, bool showSystem); +/* \dx */ +extern bool listExtensions(const char *pattern); + +/* \dx+ */ +extern bool listExtensionContents(const char *pattern); + #endif /* DESCRIBE_H */ diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c index 2647b1081fa..c44079e0343 100644 --- a/src/bin/psql/help.c +++ b/src/bin/psql/help.c @@ -222,6 +222,7 @@ slashUsage(unsigned short int pager) fprintf(output, _(" \\du[+] [PATTERN] list roles\n")); fprintf(output, _(" \\dv[S+] [PATTERN] list views\n")); fprintf(output, _(" \\dE[S+] [PATTERN] list foreign tables\n")); + fprintf(output, _(" \\dx[+] [PATTERN] list extensions\n")); fprintf(output, _(" \\l[+] list all databases\n")); fprintf(output, _(" \\sf[+] FUNCNAME show a function's definition\n")); fprintf(output, _(" \\z [PATTERN] same as \\dp\n")); diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 84c68a7bff2..1c9623de1e5 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -578,6 +578,16 @@ static const SchemaQuery Query_for_list_of_views = { " FROM pg_catalog.pg_proc "\ " WHERE proname='%s'" +#define Query_for_list_of_extensions \ +" SELECT pg_catalog.quote_ident(extname) "\ +" FROM pg_catalog.pg_extension "\ +" WHERE substring(pg_catalog.quote_ident(extname),1,%d)='%s'" + +#define Query_for_list_of_available_extensions \ +" SELECT pg_catalog.quote_ident(name) "\ +" FROM pg_catalog.pg_available_extensions "\ +" WHERE substring(pg_catalog.quote_ident(name),1,%d)='%s' AND installed IS NULL" + /* * This is a list of all "things" in Pgsql, which can show up after CREATE or * DROP; and there is also a query to get a list of them. @@ -606,6 +616,7 @@ static const pgsql_thing_t words_after_create[] = { {"DATABASE", Query_for_list_of_databases}, {"DICTIONARY", Query_for_list_of_ts_dictionaries, NULL, true}, {"DOMAIN", NULL, &Query_for_list_of_domains}, + {"EXTENSION", Query_for_list_of_extensions}, {"FOREIGN DATA WRAPPER", NULL, NULL}, {"FOREIGN TABLE", NULL, NULL}, {"FUNCTION", NULL, &Query_for_list_of_functions}, @@ -775,9 +786,12 @@ psql_completion(char *text, int start, int end) pg_strcasecmp(prev3_wd, "TABLE") != 0) { static const char *const list_ALTER[] = - {"AGGREGATE", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION", - "GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "OPERATOR", "ROLE", "SCHEMA", "SERVER", "SEQUENCE", "TABLE", - "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE", "USER", "USER MAPPING FOR", "VIEW", NULL}; + {"AGGREGATE", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN", + "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION", + "GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "OPERATOR", + "ROLE", "SCHEMA", "SERVER", "SEQUENCE", "TABLE", + "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE", + "USER", "USER MAPPING FOR", "VIEW", NULL}; COMPLETE_WITH_LIST(list_ALTER); } @@ -838,6 +852,11 @@ psql_completion(char *text, int start, int end) COMPLETE_WITH_LIST(list_ALTERDATABASE); } + /* ALTER EXTENSION <name> */ + else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && + pg_strcasecmp(prev2_wd, "EXTENSION") == 0) + COMPLETE_WITH_CONST("SET SCHEMA"); + /* ALTER FOREIGN */ else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 && pg_strcasecmp(prev_wd, "FOREIGN") == 0) @@ -1579,6 +1598,16 @@ psql_completion(char *text, int start, int end) pg_strcasecmp(prev_wd, "TEMPLATE") == 0) COMPLETE_WITH_QUERY(Query_for_list_of_template_databases); + /* CREATE EXTENSION */ + /* Complete with available extensions rather than installed ones. */ + else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 && + pg_strcasecmp(prev_wd, "EXTENSION") == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions); + /* CREATE EXTENSION <name> */ + else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 && + pg_strcasecmp(prev2_wd, "EXTENSION") == 0) + COMPLETE_WITH_CONST("WITH SCHEMA"); + /* CREATE FOREIGN */ else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 && pg_strcasecmp(prev_wd, "FOREIGN") == 0) @@ -1922,6 +1951,7 @@ psql_completion(char *text, int start, int end) else if ((pg_strcasecmp(prev3_wd, "DROP") == 0 && (pg_strcasecmp(prev2_wd, "CONVERSION") == 0 || pg_strcasecmp(prev2_wd, "DOMAIN") == 0 || + pg_strcasecmp(prev2_wd, "EXTENSION") == 0 || pg_strcasecmp(prev2_wd, "FUNCTION") == 0 || pg_strcasecmp(prev2_wd, "INDEX") == 0 || pg_strcasecmp(prev2_wd, "LANGUAGE") == 0 || diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 3defe0641f2..019cd8fab5a 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201102083 +#define CATALOG_VERSION_NO 201102084 #endif diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index c6ab313edf8..4d7ff8853d1 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -50,6 +50,12 @@ * Example: a trigger that's created to enforce a foreign-key constraint * is made internally dependent on the constraint's pg_constraint entry. * + * DEPENDENCY_EXTENSION ('e'): the dependent object is a member of the + * extension that is the referenced object. The dependent object can be + * dropped only via DROP EXTENSION on the referenced object. Functionally + * this dependency type acts the same as an internal dependency, but it's + * kept separate for clarity and to simplify pg_dump. + * * DEPENDENCY_PIN ('p'): there is no dependent object; this type of entry * is a signal that the system itself depends on the referenced object, * and so that object must never be deleted. Entries of this type are @@ -64,6 +70,7 @@ typedef enum DependencyType DEPENDENCY_NORMAL = 'n', DEPENDENCY_AUTO = 'a', DEPENDENCY_INTERNAL = 'i', + DEPENDENCY_EXTENSION = 'e', DEPENDENCY_PIN = 'p' } DependencyType; @@ -137,8 +144,8 @@ typedef enum ObjectClass OCLASS_FDW, /* pg_foreign_data_wrapper */ OCLASS_FOREIGN_SERVER, /* pg_foreign_server */ OCLASS_USER_MAPPING, /* pg_user_mapping */ - OCLASS_FOREIGN_TABLE, /* pg_foreign_table */ OCLASS_DEFACL, /* pg_default_acl */ + OCLASS_EXTENSION, /* pg_extension */ MAX_OCLASS /* MUST BE LAST */ } ObjectClass; @@ -193,12 +200,17 @@ extern void recordMultipleDependencies(const ObjectAddress *depender, int nreferenced, DependencyType behavior); -extern long deleteDependencyRecordsFor(Oid classId, Oid objectId); +extern void recordDependencyOnCurrentExtension(const ObjectAddress *object); + +extern long deleteDependencyRecordsFor(Oid classId, Oid objectId, + bool skipExtensionDeps); extern long changeDependencyFor(Oid classId, Oid objectId, Oid refClassId, Oid oldRefObjectId, Oid newRefObjectId); +extern Oid getExtensionOfObject(Oid classId, Oid objectId); + extern bool sequenceIsOwned(Oid seqId, Oid *tableId, int32 *colId); extern void markSequenceUnowned(Oid seqId); diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 866942cf9a3..4118e645424 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -294,6 +294,12 @@ DECLARE_UNIQUE_INDEX(pg_db_role_setting_databaseid_rol_index, 2965, on pg_db_rol DECLARE_UNIQUE_INDEX(pg_seclabel_object_index, 3597, on pg_seclabel using btree(objoid oid_ops, classoid oid_ops, objsubid int4_ops, provider text_ops)); #define SecLabelObjectIndexId 3597 +DECLARE_UNIQUE_INDEX(pg_extension_oid_index, 3080, on pg_extension using btree(oid oid_ops)); +#define ExtensionOidIndexId 3080 + +DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(extname name_ops)); +#define ExtensionNameIndexId 3081 + /* last step of initialization script: build the indexes declared above */ BUILD_INDICES diff --git a/src/include/catalog/pg_extension.h b/src/include/catalog/pg_extension.h new file mode 100644 index 00000000000..0ad47b01a4b --- /dev/null +++ b/src/include/catalog/pg_extension.h @@ -0,0 +1,72 @@ +/*------------------------------------------------------------------------- + * + * pg_extension.h + * definition of the system "extension" relation (pg_extension) + * along with the relation's initial contents. + * + * + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_extension.h + * + * NOTES + * the genbki.pl script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_EXTENSION_H +#define PG_EXTENSION_H + +#include "catalog/genbki.h" + +/* ---------------- + * pg_extension definition. cpp turns this into + * typedef struct FormData_pg_extension + * ---------------- + */ +#define ExtensionRelationId 3079 + +CATALOG(pg_extension,3079) +{ + NameData extname; /* extension name */ + Oid extowner; /* extension owner */ + Oid extnamespace; /* namespace of contained objects */ + bool extrelocatable; /* if true, allow ALTER EXTENSION SET SCHEMA */ + + /* + * VARIABLE LENGTH FIELDS start here. These fields may be NULL, too. + */ + text extversion; /* extension version ID, if any */ + Oid extconfig[1]; /* dumpable configuration tables */ + text extcondition[1]; /* WHERE clauses for config tables */ +} FormData_pg_extension; + +/* ---------------- + * Form_pg_extension corresponds to a pointer to a tuple with + * the format of pg_extension relation. + * ---------------- + */ +typedef FormData_pg_extension *Form_pg_extension; + +/* ---------------- + * compiler constants for pg_extension + * ---------------- + */ + +#define Natts_pg_extension 7 +#define Anum_pg_extension_extname 1 +#define Anum_pg_extension_extowner 2 +#define Anum_pg_extension_extnamespace 3 +#define Anum_pg_extension_extrelocatable 4 +#define Anum_pg_extension_extversion 5 +#define Anum_pg_extension_extconfig 6 +#define Anum_pg_extension_extcondition 7 + +/* ---------------- + * pg_extension has no initial contents + * ---------------- + */ + +#endif /* PG_EXTENSION_H */ diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 836574355cd..9e6ec3dda50 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -4873,6 +4873,12 @@ DESCR("record greater than or equal"); DATA(insert OID = 2987 ( btrecordcmp PGNSP PGUID 12 1 0 0 f f f t f i 2 0 23 "2249 2249" _null_ _null_ _null_ _null_ btrecordcmp _null_ _null_ _null_ )); DESCR("btree less-equal-greater"); +/* Extensions */ +DATA(insert OID = 3082 ( pg_available_extensions PGNSP PGUID 12 10 100 0 f f f t t s 0 0 2249 "" "{19,25,16,25}" "{o,o,o,o}" "{name,version,relocatable,comment}" _null_ pg_available_extensions _null_ _null_ _null_ )); +DESCR("list available extensions"); +DATA(insert OID = 3083 ( pg_extension_config_dump PGNSP PGUID 12 1 0 0 f f f t f v 2 0 2278 "2205 25" _null_ _null_ _null_ _null_ pg_extension_config_dump _null_ _null_ _null_ )); +DESCR("flag an extension's table contents to be emitted by pg_dump"); + /* SQL-spec window functions */ DATA(insert OID = 3100 ( row_number PGNSP PGUID 12 1 0 0 f t f f f i 0 0 20 "" _null_ _null_ _null_ _null_ window_row_number _null_ _null_ _null_ )); DESCR("row number within partition"); diff --git a/src/include/commands/alter.h b/src/include/commands/alter.h index 74d5132636c..21731685f5c 100644 --- a/src/include/commands/alter.h +++ b/src/include/commands/alter.h @@ -20,11 +20,11 @@ extern void ExecRenameStmt(RenameStmt *stmt); extern void ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt); -extern void AlterObjectNamespace(Relation rel, int cacheId, - Oid classId, Oid objid, Oid nspId, - int Anum_name, int Anum_namespace, int Anum_owner, - AclObjectKind acl_kind, - bool superuser_only); +extern Oid AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid); +extern Oid AlterObjectNamespace(Relation rel, int oidCacheId, int nameCacheId, + Oid objid, Oid nspOid, + int Anum_name, int Anum_namespace, int Anum_owner, + AclObjectKind acl_kind); extern void ExecAlterOwnerStmt(AlterOwnerStmt *stmt); #endif /* ALTER_H */ diff --git a/src/include/commands/conversioncmds.h b/src/include/commands/conversioncmds.h index 6156c4a94e6..f77023ffe32 100644 --- a/src/include/commands/conversioncmds.h +++ b/src/include/commands/conversioncmds.h @@ -23,5 +23,6 @@ extern void RenameConversion(List *name, const char *newname); extern void AlterConversionOwner(List *name, Oid newOwnerId); extern void AlterConversionOwner_oid(Oid conversionOid, Oid newOwnerId); extern void AlterConversionNamespace(List *name, const char *newschema); +extern Oid AlterConversionNamespace_oid(Oid convOid, Oid newNspOid); #endif /* CONVERSIONCMDS_H */ diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 01f271bff43..157ee394614 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -66,6 +66,7 @@ extern void DropCast(DropCastStmt *stmt); extern void DropCastById(Oid castOid); extern void AlterFunctionNamespace(List *name, List *argtypes, bool isagg, const char *newschema); +extern Oid AlterFunctionNamespace_oid(Oid procOid, Oid nspOid); extern void ExecuteDoStmt(DoStmt *stmt); extern Oid get_cast_oid(Oid sourcetypeid, Oid targettypeid, bool missing_ok); @@ -77,6 +78,7 @@ extern void AlterOperatorOwner(List *name, TypeName *typeName1, TypeName *typename2, Oid newOwnerId); extern void AlterOperatorOwner_oid(Oid operOid, Oid newOwnerId); extern void AlterOperatorNamespace(List *names, List *argtypes, const char *newschema); +extern Oid AlterOperatorNamespace_oid(Oid operOid, Oid newNspOid); /* commands/aggregatecmds.c */ extern void DefineAggregate(List *name, List *args, bool oldstyle, @@ -100,9 +102,11 @@ extern void RenameOpFamily(List *name, const char *access_method, const char *ne extern void AlterOpClassOwner(List *name, const char *access_method, Oid newOwnerId); extern void AlterOpClassOwner_oid(Oid opclassOid, Oid newOwnerId); extern void AlterOpClassNamespace(List *name, char *access_method, const char *newschema); +extern Oid AlterOpClassNamespace_oid(Oid opclassOid, Oid newNspOid); extern void AlterOpFamilyOwner(List *name, const char *access_method, Oid newOwnerId); extern void AlterOpFamilyOwner_oid(Oid opfamilyOid, Oid newOwnerId); extern void AlterOpFamilyNamespace(List *name, char *access_method, const char *newschema); +extern Oid AlterOpFamilyNamespace_oid(Oid opfamilyOid, Oid newNspOid); extern Oid get_am_oid(const char *amname, bool missing_ok); extern Oid get_opclass_oid(Oid amID, List *opclassname, bool missing_ok); extern Oid get_opfamily_oid(Oid amID, List *opfamilyname, bool missing_ok); @@ -111,6 +115,7 @@ extern Oid get_opfamily_oid(Oid amID, List *opfamilyname, bool missing_ok); extern void DefineTSParser(List *names, List *parameters); extern void RenameTSParser(List *oldname, const char *newname); extern void AlterTSParserNamespace(List *name, const char *newschema); +extern Oid AlterTSParserNamespace_oid(Oid prsId, Oid newNspOid); extern void RemoveTSParsers(DropStmt *drop); extern void RemoveTSParserById(Oid prsId); @@ -121,10 +126,12 @@ extern void RemoveTSDictionaryById(Oid dictId); extern void AlterTSDictionary(AlterTSDictionaryStmt *stmt); extern void AlterTSDictionaryOwner(List *name, Oid newOwnerId); extern void AlterTSDictionaryNamespace(List *name, const char *newschema); +extern Oid AlterTSDictionaryNamespace_oid(Oid dictId, Oid newNspOid); extern void DefineTSTemplate(List *names, List *parameters); extern void RenameTSTemplate(List *oldname, const char *newname); extern void AlterTSTemplateNamespace(List *name, const char *newschema); +extern Oid AlterTSTemplateNamespace_oid(Oid tmplId, Oid newNspOid); extern void RemoveTSTemplates(DropStmt *stmt); extern void RemoveTSTemplateById(Oid tmplId); @@ -135,6 +142,7 @@ extern void RemoveTSConfigurationById(Oid cfgId); extern void AlterTSConfiguration(AlterTSConfigurationStmt *stmt); extern void AlterTSConfigurationOwner(List *name, Oid newOwnerId); extern void AlterTSConfigurationNamespace(List *name, const char *newschema); +extern Oid AlterTSConfigurationNamespace_oid(Oid cfgId, Oid newNspOid); extern text *serialize_deflist(List *deflist); extern List *deserialize_deflist(Datum txt); diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h new file mode 100644 index 00000000000..10d08935a00 --- /dev/null +++ b/src/include/commands/extension.h @@ -0,0 +1,40 @@ +/*------------------------------------------------------------------------- + * + * extension.h + * Extension management commands (create/drop extension). + * + * + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/commands/extension.h + * + *------------------------------------------------------------------------- + */ +#ifndef EXTENSION_H +#define EXTENSION_H + +#include "nodes/parsenodes.h" + + +/* + * creating_extension is only true while running a CREATE EXTENSION command. + * It instructs recordDependencyOnCurrentExtension() to register a dependency + * on the current pg_extension object for each SQL object created by its + * installation script. + */ +extern bool creating_extension; +extern Oid CurrentExtensionObject; + + +extern void CreateExtension(CreateExtensionStmt *stmt); + +extern void RemoveExtensions(DropStmt *stmt); +extern void RemoveExtensionById(Oid extId); + +extern Oid get_extension_oid(const char *extname, bool missing_ok); +extern char *get_extension_name(Oid ext_oid); + +extern void AlterExtensionNamespace(List *names, const char *newschema); + +#endif /* EXTENSION_H */ diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h index b13363aaf7e..1b20296934f 100644 --- a/src/include/commands/typecmds.h +++ b/src/include/commands/typecmds.h @@ -41,7 +41,8 @@ extern void AlterTypeOwner(List *names, Oid newOwnerId); extern void AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId, bool hasDependEntry); extern void AlterTypeNamespace(List *names, const char *newschema); -extern void AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, +extern Oid AlterTypeNamespace_oid(Oid typeOid, Oid nspOid); +extern Oid AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, bool isImplicitArray, bool errorOnTableType); diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 63b9dd80be9..1ca5f1ef9a1 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -355,6 +355,7 @@ typedef enum NodeTag T_AlterTableSpaceOptionsStmt, T_SecLabelStmt, T_CreateForeignTableStmt, + T_CreateExtensionStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index b68fe10990a..5de4dbd5ec1 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1073,6 +1073,7 @@ typedef enum ObjectType OBJECT_CONVERSION, OBJECT_DATABASE, OBJECT_DOMAIN, + OBJECT_EXTENSION, OBJECT_FDW, OBJECT_FOREIGN_SERVER, OBJECT_FOREIGN_TABLE, @@ -1534,6 +1535,18 @@ typedef struct AlterTableSpaceOptionsStmt } AlterTableSpaceOptionsStmt; /* ---------------------- + * Create Extension Statement + * ---------------------- + */ + +typedef struct CreateExtensionStmt +{ + NodeTag type; + char *extname; + List *options; /* List of DefElem nodes */ +} CreateExtensionStmt; + +/* ---------------------- * Create/Drop FOREIGN DATA WRAPPER Statements * ---------------------- */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 7dad00197eb..4939b493bc2 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -150,6 +150,7 @@ PG_KEYWORD("exclusive", EXCLUSIVE, UNRESERVED_KEYWORD) PG_KEYWORD("execute", EXECUTE, UNRESERVED_KEYWORD) PG_KEYWORD("exists", EXISTS, COL_NAME_KEYWORD) PG_KEYWORD("explain", EXPLAIN, UNRESERVED_KEYWORD) +PG_KEYWORD("extension", EXTENSION, UNRESERVED_KEYWORD) PG_KEYWORD("external", EXTERNAL, UNRESERVED_KEYWORD) PG_KEYWORD("extract", EXTRACT, COL_NAME_KEYWORD) PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD) diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 540d16b844a..d17eb67b1be 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -440,6 +440,8 @@ extern Datum pg_relation_filenode(PG_FUNCTION_ARGS); extern Datum pg_relation_filepath(PG_FUNCTION_ARGS); /* genfile.c */ +extern bytea *read_binary_file(const char *filename, + int64 seek_offset, int64 bytes_to_read); extern Datum pg_stat_file(PG_FUNCTION_ARGS); extern Datum pg_read_file(PG_FUNCTION_ARGS); extern Datum pg_read_file_all(PG_FUNCTION_ARGS); @@ -1063,6 +1065,10 @@ extern Datum pg_describe_object(PG_FUNCTION_ARGS); /* commands/constraint.c */ extern Datum unique_key_recheck(PG_FUNCTION_ARGS); +/* commands/extension.c */ +extern Datum pg_available_extensions(PG_FUNCTION_ARGS); +extern Datum pg_extension_config_dump(PG_FUNCTION_ARGS); + /* commands/prepare.c */ extern Datum pg_prepared_statement(PG_FUNCTION_ARGS); diff --git a/src/makefiles/pgxs.mk b/src/makefiles/pgxs.mk index 912578a84dc..655c5f9e382 100644 --- a/src/makefiles/pgxs.mk +++ b/src/makefiles/pgxs.mk @@ -17,16 +17,17 @@ # # Set one of these three variables to specify what is built: # -# MODULES -- list of shared objects to be built from source files with -# same stem (do not include suffix in this list) -# MODULE_big -- a shared object to build from multiple source files +# MODULES -- list of shared-library objects to be built from source files +# with same stem (do not include library suffixes in this list) +# MODULE_big -- a shared library to build from multiple source files # (list object files in OBJS) -# PROGRAM -- a binary program to build (list object files in OBJS) +# PROGRAM -- an executable program to build (list object files in OBJS) # # The following variables can also be set: # -# MODULEDIR -- subdirectory into which DATA and DOCS files should be -# installed (if not set, default is "contrib") +# MODULEDIR -- subdirectory into which EXTENSION, DATA and DOCS files +# should be installed (if not set, default is "contrib") +# EXTENSION -- name of extension (there must be a $EXTENSION.control file) # DATA -- random files to install into $PREFIX/share/$MODULEDIR # DATA_built -- random files to install into $PREFIX/share/$MODULEDIR, # which need to be built first @@ -82,7 +83,7 @@ ifdef PG_CPPFLAGS override CPPFLAGS := $(PG_CPPFLAGS) $(CPPFLAGS) endif -all: $(PROGRAM) $(DATA_built) $(SCRIPTS_built) $(addsuffix $(DLSUFFIX), $(MODULES)) +all: $(PROGRAM) $(DATA_built) $(SCRIPTS_built) $(addsuffix $(DLSUFFIX), $(MODULES)) $(addsuffix .control, $(EXTENSION)) ifdef MODULE_big # shared library parameters @@ -95,8 +96,8 @@ endif # MODULE_big install: all installdirs -ifneq (,$(DATA)$(DATA_built)) - @for file in $(addprefix $(srcdir)/, $(DATA)) $(DATA_built); do \ +ifneq (,$(DATA)$(DATA_built)$(EXTENSION)) + @for file in $(addprefix $(srcdir)/, $(DATA)) $(DATA_built) $(addsuffix .control, $(EXTENSION)); do \ echo "$(INSTALL_DATA) $$file '$(DESTDIR)$(datadir)/$(datamoduledir)'"; \ $(INSTALL_DATA) $$file '$(DESTDIR)$(datadir)/$(datamoduledir)'; \ done @@ -167,8 +168,8 @@ endif # MODULE_big uninstall: -ifneq (,$(DATA)$(DATA_built)) - rm -f $(addprefix '$(DESTDIR)$(datadir)/$(datamoduledir)'/, $(notdir $(DATA) $(DATA_built))) +ifneq (,$(DATA)$(DATA_built)$(EXTENSION)) + rm -f $(addprefix '$(DESTDIR)$(datadir)/$(datamoduledir)'/, $(notdir $(DATA) $(DATA_built) $(addsuffix .control, $(EXTENSION)))) endif ifneq (,$(DATA_TSEARCH)) rm -f $(addprefix '$(DESTDIR)$(datadir)/tsearch_data'/, $(notdir $(DATA_TSEARCH))) diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 72e5630b1f1..8ddc116d665 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1279,6 +1279,7 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem viewname | definition -----------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- iexit | SELECT ih.name, ih.thepath, interpt_pp(ih.thepath, r.thepath) AS exit FROM ihighway ih, ramp r WHERE (ih.thepath ## r.thepath); + pg_available_extensions | SELECT e.name, e.version, x.extversion AS installed, n.nspname AS schema, e.relocatable, e.comment FROM ((pg_available_extensions() e(name, version, relocatable, comment) LEFT JOIN pg_extension x ON ((e.name = x.extname))) LEFT JOIN pg_namespace n ON ((n.oid = x.extnamespace))); pg_cursors | SELECT c.name, c.statement, c.is_holdable, c.is_binary, c.is_scrollable, c.creation_time FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time); pg_group | SELECT pg_authid.rolname AS groname, pg_authid.oid AS grosysid, ARRAY(SELECT pg_auth_members.member FROM pg_auth_members WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist FROM pg_authid WHERE (NOT pg_authid.rolcanlogin); pg_indexes | SELECT n.nspname AS schemaname, c.relname AS tablename, i.relname AS indexname, t.spcname AS tablespace, pg_get_indexdef(i.oid) AS indexdef FROM ((((pg_index x JOIN pg_class c ON ((c.oid = x.indrelid))) JOIN pg_class i ON ((i.oid = x.indexrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace))) WHERE ((c.relkind = 'r'::"char") AND (i.relkind = 'i'::"char")); @@ -1336,7 +1337,7 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem shoelace_obsolete | SELECT shoelace.sl_name, shoelace.sl_avail, shoelace.sl_color, shoelace.sl_len, shoelace.sl_unit, shoelace.sl_len_cm FROM shoelace WHERE (NOT (EXISTS (SELECT shoe.shoename FROM shoe WHERE (shoe.slcolor = shoelace.sl_color)))); street | SELECT r.name, r.thepath, c.cname FROM ONLY road r, real_city c WHERE (c.outline ## r.thepath); toyemp | SELECT emp.name, emp.age, emp.location, (12 * emp.salary) AS annualsal FROM emp; -(58 rows) +(59 rows) SELECT tablename, rulename, definition FROM pg_rules ORDER BY tablename, rulename; diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index d378e2641fa..59e1bdb8070 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -101,6 +101,7 @@ SELECT relname, relhasindex pg_depend | t pg_description | t pg_enum | t + pg_extension | t pg_foreign_data_wrapper | t pg_foreign_server | t pg_foreign_table | t |