aboutsummaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/catalog/Makefile2
-rw-r--r--src/backend/catalog/dependency.c45
-rw-r--r--src/backend/catalog/heap.c5
-rw-r--r--src/backend/catalog/objectaddress.c14
-rw-r--r--src/backend/catalog/pg_conversion.c3
-rw-r--r--src/backend/catalog/pg_depend.c88
-rw-r--r--src/backend/catalog/pg_namespace.c11
-rw-r--r--src/backend/catalog/pg_operator.c5
-rw-r--r--src/backend/catalog/pg_proc.c9
-rw-r--r--src/backend/catalog/pg_type.c10
-rw-r--r--src/backend/catalog/system_views.sql7
-rw-r--r--src/backend/commands/Makefile3
-rw-r--r--src/backend/commands/alter.c196
-rw-r--r--src/backend/commands/cluster.c6
-rw-r--r--src/backend/commands/comment.c6
-rw-r--r--src/backend/commands/conversioncmds.c31
-rw-r--r--src/backend/commands/extension.c1401
-rw-r--r--src/backend/commands/foreigncmds.c27
-rw-r--r--src/backend/commands/functioncmds.c42
-rw-r--r--src/backend/commands/opclasscmds.c82
-rw-r--r--src/backend/commands/operatorcmds.c28
-rw-r--r--src/backend/commands/proclang.c9
-rw-r--r--src/backend/commands/tablecmds.c1
-rw-r--r--src/backend/commands/tsearchcmds.c128
-rw-r--r--src/backend/commands/typecmds.c23
-rw-r--r--src/backend/nodes/copyfuncs.c14
-rw-r--r--src/backend/nodes/equalfuncs.c12
-rw-r--r--src/backend/parser/gram.y56
-rw-r--r--src/backend/rewrite/rewriteDefine.c2
-rw-r--r--src/backend/tcop/utility.c24
-rw-r--r--src/backend/utils/adt/genfile.c63
31 files changed, 2196 insertions, 157 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));
}
/*