*** a/doc/src/sgml/catalogs.sgml --- b/doc/src/sgml/catalogs.sgml *************** *** 2750,2755 **** --- 2750,2810 ---- + + <structname>pg_extension</structname> + + + pg_extension + + + + The catalog pg_extension stores information + about the installed extension. See for + details about extensions. + + + + <structname>pg_extension</> Columns + + + + + Name + Type + References + Description + + + + + + extname + name + + Extension name + + + + relocatable + bool + + An extension is relocatable when it's possible to + alter the schema in which all its objects are installed. + + + + extversion + text + + Version string of the extension, as provided by its author. + + + + +
+
+ + <structname>pg_foreign_data_wrapper</structname> *************** *** 6011,6016 **** --- 6066,6076 ---- + pg_available_extensions + available and installed extensions + + + pg_group groups of database users *************** *** 6220,6225 **** --- 6280,6356 ---- + + <structname>pg_available_extensions</structname> + + + pg_available_extensions + + + + The pg_available_extensions view lists the + extensions that are currently available and installed. + + + + <structname>pg_available_extensions</> Columns + + + + + Name + Type + Description + + + + + + name + text + The name of the extensions + + + + version + text + The version string of the extension + + + + relocatable + bool + + An extension is relocatable when it's possible to + alter the schema in which all its objects are installed. + + + + comment + text + The comment field provided in the control + file, or NULL if none were provided. + + + + installed + boolean + + true if the extension is already + installed; false otherwise + + + + + +
+ + + The pg_available_extensions view is read only. + + +
+ <structname>pg_group</structname> *** a/doc/src/sgml/extend.sgml --- b/doc/src/sgml/extend.sgml *************** *** 336,339 **** --- 336,545 ---- + + Putting it all together: create extension + + + extension + + + + The way to put together a coherent set of custom SQL objects + is to create an extension, which is the name of a + packaged script. To wrap an extension you need + the SQL script that defines its objects (functions, + operators, types, etc) and a control file that is used to + register it. The script itself might also require than a shared + library is installed on your system, too. + + + The goal of using extensions is so that pg_dump knows + not to dump all the object definitions into your backups, but rather + issue a single command. + + + + Extension files + + + The CREATE EXTENSION command + relies on a couple + of files to be placed in SHAREDIR/contrib. + + + + + + The extension.control + file should contain the information necessary to register the + extension. The file format must follow the per-line key = + 'value' syntax, with comments supported on any line after + a # character. + + + This control file allows for registering the extension and should + provide values for the keys described below. + + + + + Once registered, the extension is loaded by means of executing the + commands from + the extension.sql + file. + + + + + + control + + + + The extension control file can contain the following + keys: + + + + comment + + + The comment (free form) about the provided extension, that's shown + to users in the extension listings. The comment is added by means of + the command . + + + + + version + + + The version string (free form) of the provided extension. + + + + + script + + + The filename of the SQL script to load when creating the + extension. Defaults to the same name sans extension as the control + filename, with the .sql extension. + + + + + encoding + + + The client_encoding to set while loading the + script file. + + + + + relocatable + + + An extension is relocatable when it installs all its + object into the first schema of the search_path. As an + extension's script author, you have to make sure you're not schema + qualifying any object that you create. When an extension + is relocatable then users will be able to + use . + + + An extension that needs to know where some of its objects are + installed is not relocatable. As the user won't be able + to change the schema where the extensions gets installed, he's given + the possibility to specify the schema at installation time. The + extension installation script is then required to use the + @extschema@ placeholer as the schema to work with. That + will typically mean the script's first line is: + + SET search_path TO @extschema@; + + + + + + + + + Extension functions + + + The admin + function pg_extension_flag_dump + can be used to revert the default pg_dump policy + about objects that belong to an extension and force the flagged objects + to be part of the backups. + + + + + A complete example + + + Let's walk trough a complete example of an SQL only + extension, a two-value composite type that can store any type of value + in its slots, which are named "k" and "v". + + + It all begins with the script pair.sql, here it is: + + CREATE TYPE pair AS ( k text, v text ); + + CREATE OR REPLACE FUNCTION pair(anyelement, text) + RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair'; + + CREATE OR REPLACE FUNCTION pair(text, anyelement) + RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair'; + + CREATE OR REPLACE FUNCTION pair(anyelement, anyelement) + RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair'; + + CREATE OR REPLACE FUNCTION pair(text, text) + RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair;'; + + CREATE OPERATOR ~> (LEFTARG = text, RIGHTARG = anyelement, PROCEDURE = pair); + CREATE OPERATOR ~> (LEFTARG = anyelement, RIGHTARG = text, PROCEDURE = pair); + CREATE OPERATOR ~> (LEFTARG = anyelement, RIGHTARG = anyelement, PROCEDURE = pair); + CREATE OPERATOR ~> (LEFTARG = text, RIGHTARG = text, PROCEDURE = pair); + + + + You then need to provide a pair.control file with the + following content: + + # pair extension + comment = 'A key/value pair data type' + version = '0.1.2' + relocatable = true + + + + Finaly, you need to provide a Makefile as such: + + DATA = pair.sql + EXTENSION = pair + + PG_CONFIG ?= pg_config + PGXS := $(shell $(PG_CONFIG) --pgxs) + include $(PGXS) + + + + Using the command make install will then produce + the pair.control file for you, then install the files in + the right place for the PostgreSQL cluster found + by pg_config. + + + It's then possible for you to use + the command. + + + + *** a/doc/src/sgml/func.sgml --- b/doc/src/sgml/func.sgml *************** *** 14525,14530 **** SELECT (pg_stat_file('filename')).modification; --- 14525,14629 ---- + The functions shown in are + facilities for extension authors. For details about extensions, see + . + + + + Extension Related Functions + + + Name Return Type Description + + + + + + + pg_extension_flag_dump(objid oid) + + bool + Flag an extension's objectid as worthy + of pg_dump. + + + + pg_extension_with_user_data() + + bool + Return true only when the extension's + script should care for user data. + + + + pg_extension_objects(name name, OUT class regclass, OUT classid oid, OUT objid oid, OUT objdesc text) + setof record + List the objects that are part of an extension. + + + + +
+ + + pg_extension_flag_dump + + + pg_extension_flag_dump changes the dependency type between + an extension's and one of its object, given + by oid. The default behavior of an extension is to + skip objects in pg_dump, but in some cases that's not + what you want to achieve, as an extension author. If your extension + provides user data (in a table, for example), then flag this table so + that it's part of the backups, like so: + + SELECT pg_extension_flag_dump('schema.tablename'::regclass); + + + + This function can only be called when loading a SQL script + using the command CREATE + EXTENSION. See + and . + + + + pg_extension_with_user_data + + + pg_extension_with_user_data allows extension's author to + prepare installation scripts that will work well for initial + installation and when restoring from a pg_dump + backup, which issues CREATE EXTENSION foo WITH NO USER + DATA;. See for + details. One way to use it is as following: + + DO $$ + BEGIN + IF pg_extension_with_user_data() THEN + create schema foo; + create table foo.bar(id serial primary key); + perform pg_extension_flag_dump('foo.bar_id_seq'::regclass); + perform pg_extension_flag_dump('foo.bar::regclass); + END IF; + END; + $$; + + + + + pg_extension_objects + + + pg_extension_objects list all objects created by the given + extension, with their human friendly description. You can use this + function to discover what's in an extension, or as an extension + author to discover what oid has been assigned to + some object of your extension, in your upgrade script e.g. + + + The functions shown in manage advisory locks. For details about proper use of these functions, see . *************** *** 14546,14551 **** SELECT (pg_stat_file('filename')).modification; --- 14645,14651 ---- void Obtain exclusive advisory lock + pg_advisory_lock(key1 int, key2 int) *** a/doc/src/sgml/ref/allfiles.sgml --- b/doc/src/sgml/ref/allfiles.sgml *************** *** 11,16 **** Complete list of usable sgml source files in this directory. --- 11,17 ---- + *************** *** 49,54 **** Complete list of usable sgml source files in this directory. --- 50,56 ---- + *************** *** 84,89 **** Complete list of usable sgml source files in this directory. --- 86,92 ---- + *** /dev/null --- b/doc/src/sgml/ref/create_extension.sgml *************** *** 0 **** --- 1,136 ---- + + + + + CREATE EXTENSION + 7 + SQL - Language Statements + + + + CREATE EXTENSION + define and install a new extension + + + + CREATE EXTENSION + + + + + CREATE EXTENSION extension_name + [ [ WITH ] [ [ NO ] USER DATA ] + [ SCHEMA [=] schema ] + + + + + Description + + + CREATE EXTENSION registers a new extension. The + extension name must be distinct from the name of any existing extension + in the database. + + + + An extension allows superusers to load a script in the database. This + script will typically create new SQL objects such as + functions, data types, operators and index support methods. + + + + + + Parameters + + + + extension_name + + + The name of an extension to be + installed. PostgreSQL will register the + extension using details from the + file SHAREDIR/contrib/extension.control + file then load + the SHAREDIR/contrib/extension.sql + script. + + + + + + NO USER DATA + + + The goal of bundling an SQL script as an extension is + for to issue a single command per + extension rather than a command per object installed by means of + running the script. That allows to maintain the script separately + from the database. + + + This option is systematically used + by . As it's possible to flag extension's + objects so that they get dumped + (see ), this option allow + extension authors to prepare installation scripts that will avoid + creating user data when restoring. It does so by having the + function pg_extension_with_user_data + returns False when called from the extension's script, + which is then supposed to avoid creating the objects + where users are to edit the data. + + + + + schema + + + Name of the schema where to install the extension + objects, given that the extension's script supports the option. To + support the option, the script must use the + placeholder @extschema@ rather than hardcode some + schema. + + + + + + + + Examples + + + Install the hstore extension: + + CREATE EXTENSION hstore; + + + + + + Compatibility + + + CREATE EXTENSION is a PostgreSQL + extension. + + + + + See Also + + + + + Extension Related Functions + Additional Supplied Modules + + + + *** /dev/null --- b/doc/src/sgml/ref/drop_extension.sgml *************** *** 0 **** --- 1,117 ---- + + + + + DROP EXTENSION + 7 + SQL - Language Statements + + + + DROP EXTENSION + remove an extension + + + + DROP EXTENSION + + + + + DROP EXTENSION [ IF EXISTS ] extension_name [ CASCADE | RESTRICT ] + + + + + Description + + + DROP EXTENSION removes an extension from the + system. This will also DROP all and + any SQL objects installed by the extension. + + + + An extension can only be dropped by a superuser. + + + + + Parameters + + + + + IF EXISTS + + + Do not throw an error if the extension does not exist. A notice is issued + in this case. + + + + + + extension_name + + + The name of an installed extension. + + + + + + CASCADE + + + Automatically drop objects that depend on the extension. + + + + + + RESTRICT + + + Refuse to drop the extension if any objects depend on it. This is the + default. + + + + + + + + Examples + + + To remove the extension hstore from the system: + + DROP EXTENSION hstore; + + + + + + Compatibility + + + DROP EXTENSION is a PostgreSQL + extension. + + + + + See Also + + + + + Additional Supplied Modules + + + + *** a/doc/src/sgml/ref/psql-ref.sgml --- b/doc/src/sgml/ref/psql-ref.sgml *************** *** 1339,1344 **** testdb=> --- 1339,1358 ---- + \dx[+] [ pattern ] + + + Lists installed + extensions. If pattern + is specified, only those extensions whose names match the pattern + are listed. If the form \dx+ is used, available + extensions are added to the list of installed extension. + + + + + + \edit (or \e) filename line_number *** a/doc/src/sgml/reference.sgml --- b/doc/src/sgml/reference.sgml *************** *** 39,44 **** --- 39,45 ---- &alterDatabase; &alterDefaultPrivileges; &alterDomain; + &alterExtension; &alterForeignDataWrapper; &alterFunction; &alterGroup; *************** *** 77,82 **** --- 78,84 ---- &createConversion; &createDatabase; &createDomain; + &createExtension; &createForeignDataWrapper; &createFunction; &createGroup; *************** *** 112,117 **** --- 114,120 ---- &dropConversion; &dropDatabase; &dropDomain; + &dropExtension; &dropForeignDataWrapper; &dropFunction; &dropGroup; *** a/doc/src/sgml/xfunc.sgml --- b/doc/src/sgml/xfunc.sgml *************** *** 2425,2430 **** concat_text(PG_FUNCTION_ARGS) --- 2425,2431 ---- MODULES = isbn_issn DATA_built = isbn_issn.sql DOCS = README.isbn_issn + EXTENSION = isbn_issn PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) *************** *** 2504,2509 **** include $(PGXS) --- 2505,2525 ---- + EXTENSION + + + extension control files to install + into prefix/share/$MODULEDIR, + derived from + the extension.control + file to add in your sources. EXTENSION can also + be a list, you then have to provide a .control + file per extension in this list. + + + + + DATA_TSEARCH *** a/src/backend/catalog/Makefile --- b/src/backend/catalog/Makefile *************** *** 39,44 **** POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\ --- 39,45 ---- pg_ts_parser.h pg_ts_template.h \ pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \ pg_default_acl.h pg_seclabel.h \ + pg_extension.h \ toasting.h indexing.h \ ) *** a/src/backend/catalog/dependency.c --- b/src/backend/catalog/dependency.c *************** *** 34,39 **** --- 34,40 ---- #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_language.h" *************** *** 55,60 **** --- 56,62 ---- #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" *************** *** 76,110 **** #include "utils/tqual.h" - /* - * Deletion processing requires additional state for each ObjectAddress that - * it's planning to delete. For simplicity and code-sharing we make the - * ObjectAddresses code support arrays with or without this extra state. - */ - typedef struct - { - int flags; /* bitmask, see bit definitions below */ - ObjectAddress dependee; /* object whose deletion forced this one */ - } ObjectAddressExtra; - - /* ObjectAddressExtra flag bits */ - #define DEPFLAG_ORIGINAL 0x0001 /* an original deletion target */ - #define DEPFLAG_NORMAL 0x0002 /* reached via normal dependency */ - #define DEPFLAG_AUTO 0x0004 /* reached via auto dependency */ - #define DEPFLAG_INTERNAL 0x0008 /* reached via internal dependency */ - - - /* expansible list of ObjectAddresses */ - struct ObjectAddresses - { - ObjectAddress *refs; /* => palloc'd array */ - ObjectAddressExtra *extras; /* => palloc'd array, or NULL if not used */ - int numrefs; /* current number of references */ - int maxrefs; /* current size of palloc'd array(s) */ - }; - - /* typedef ObjectAddresses appears in dependency.h */ - /* threaded list of ObjectAddresses, for recursion detection */ typedef struct ObjectAddressStack { --- 78,83 ---- *************** *** 152,158 **** static const Oid object_classes[MAX_OCLASS] = { ForeignDataWrapperRelationId, /* OCLASS_FDW */ ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */ UserMappingRelationId, /* OCLASS_USER_MAPPING */ ! DefaultAclRelationId /* OCLASS_DEFACL */ }; --- 125,132 ---- ForeignDataWrapperRelationId, /* OCLASS_FDW */ ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */ UserMappingRelationId, /* OCLASS_USER_MAPPING */ ! DefaultAclRelationId, /* OCLASS_DEFACL */ ! ExtensionRelationId /* OCLASS_EXTENSION */ }; *************** *** 549,558 **** findDependentObjects(const ObjectAddress *object, /* no problem */ break; case DEPENDENCY_INTERNAL: - /* * This object is part of the internal implementation of ! * another object. We have three cases: * * 1. At the outermost recursion level, disallow the DROP. (We * just ereport here, rather than proceeding, since no other --- 523,531 ---- /* no problem */ break; case DEPENDENCY_INTERNAL: /* * This object is part of the internal implementation of ! * another object. We have four cases: * * 1. At the outermost recursion level, disallow the DROP. (We * just ereport here, rather than proceeding, since no other *************** *** 596,602 **** findDependentObjects(const ObjectAddress *object, break; /* ! * 3. When recursing from anyplace else, transform this * deletion request into a delete of the other object. * * First, release caller's lock on this object and get --- 569,582 ---- break; /* ! * 3. When droping an extension, it's ok to drop its ! * dependencies first, even if they're internal. ! */ ! if (otherObject.classId == ExtensionRelationId) ! break; ! ! /* ! * 4. When recursing from anyplace else, transform this * deletion request into a delete of the other object. * * First, release caller's lock on this object and get *************** *** 1148,1153 **** doDeletion(const ObjectAddress *object) --- 1128,1137 ---- RemoveUserMappingById(object->objectId); break; + case OCLASS_EXTENSION: + RemoveExtensionById(object->objectId); + break; + case OCLASS_DEFACL: RemoveDefaultACLById(object->objectId); break; *************** *** 2074,2079 **** getObjectClass(const ObjectAddress *object) --- 2058,2067 ---- case DefaultAclRelationId: return OCLASS_DEFACL; + + case ExtensionRelationId: + Assert(object->objectSubId == 0); + return OCLASS_EXTENSION; } /* shouldn't get here */ *************** *** 2555,2560 **** getObjectDescription(const ObjectAddress *object) --- 2543,2560 ---- break; } + case OCLASS_EXTENSION: + { + char *extension; + + extension = get_extension_name(object->objectId); + if (!extension) + elog(ERROR, "cache lookup failed for extension %u", + object->objectId); + appendStringInfo(&buffer, _("extension %s"), extension); + break; + } + case OCLASS_TBLSPACE: { char *tblspace; *************** *** 2825,2827 **** pg_describe_object(PG_FUNCTION_ARGS) --- 2825,2873 ---- description = getObjectDescription(&address); PG_RETURN_TEXT_P(cstring_to_text(description)); } + + /* + * Base function to list an extension's objects, used in the + * pg_extension_objects SRF. + * + * Be careful to free the locks as soon as possible, by calling the helper + * function releaseDependentObjects + */ + ObjectAddresses * + listDependentObjects(ObjectAddress *object) + { + Relation depRel; + ObjectAddresses *targetObjects; + + /* + * We save some cycles by opening pg_depend just once and passing the + * Relation pointer down to all the recursive deletion steps. + */ + depRel = heap_open(DependRelationId, RowExclusiveLock); + + /* + * Construct a list of objects to delete (ie, the given object plus + * everything directly or indirectly dependent on it). + */ + targetObjects = new_object_addresses(); + + findDependentObjects(object, + DEPFLAG_ORIGINAL, + NULL, /* empty stack */ + targetObjects, + NULL, /* no pendingObjects */ + depRel); + + heap_close(depRel, RowExclusiveLock); + return targetObjects; + } + + /* + * Release the locks + */ + void + freeDependentObjects(ObjectAddresses *targetObjects) + { + free_object_addresses(targetObjects); + return; + } *** a/src/backend/catalog/heap.c --- b/src/backend/catalog/heap.c *************** *** 50,55 **** --- 50,56 ---- #include "catalog/pg_type.h" #include "catalog/pg_type_fn.h" #include "catalog/storage.h" + #include "commands/extension.h" #include "commands/tablecmds.h" #include "commands/typecmds.h" #include "miscadmin.h" *************** *** 918,923 **** heap_create_with_catalog(const char *relname, --- 919,925 ---- Oid old_type_oid; Oid new_type_oid; Oid new_array_oid = InvalidOid; + ObjectAddress myself; pg_class_desc = heap_open(RelationRelationId, RowExclusiveLock); *************** *** 1154,1169 **** heap_create_with_catalog(const char *relname, * Also, skip this in bootstrap mode, since we don't make dependencies * while bootstrapping. */ if (relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_TOASTVALUE && !IsBootstrapProcessingMode()) { ! ObjectAddress myself, ! referenced; - myself.classId = RelationRelationId; - myself.objectId = relid; - myself.objectSubId = 0; referenced.classId = NamespaceRelationId; referenced.objectId = relnamespace; referenced.objectSubId = 0; --- 1156,1171 ---- * Also, skip this in bootstrap mode, since we don't make dependencies * while bootstrapping. */ + myself.classId = RelationRelationId; + myself.objectId = relid; + myself.objectSubId = 0; + if (relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_TOASTVALUE && !IsBootstrapProcessingMode()) { ! ObjectAddress referenced; referenced.classId = NamespaceRelationId; referenced.objectId = relnamespace; referenced.objectSubId = 0; *************** *** 1196,1201 **** heap_create_with_catalog(const char *relname, --- 1198,1214 ---- InvokeObjectAccessHook(OAT_POST_CREATE, RelationRelationId, relid, 0); /* + * Record a dependency to the extension we're part of if any. We could + * bypass that step but it simplifies pg_dump queries a lot to have the + * direct dependency recorded here, as embedded recursive SQL ain't that + * easy to maintain: WHERE NOT EXISTS(WITH RECURSIVE ...). + */ + if (create_extension) + { + recordDependencyOn(&myself, &CreateExtensionAddress, DEPENDENCY_INTERNAL); + } + + /* * Store any supplied constraints and defaults. * * NB: this may do a CommandCounterIncrement and rebuild the relcache *** a/src/backend/catalog/objectaddress.c --- b/src/backend/catalog/objectaddress.c *************** *** 28,33 **** --- 28,34 ---- #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,51 **** --- 47,53 ---- #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" *************** *** 128,133 **** get_object_address(ObjectType objtype, List *objname, List *objargs, --- 130,136 ---- address = get_object_address_relobject(objtype, objname, &relation); break; case OBJECT_DATABASE: + case OBJECT_EXTENSION: case OBJECT_TABLESPACE: case OBJECT_ROLE: case OBJECT_SCHEMA: *************** *** 266,271 **** get_object_address_unqualified(ObjectType objtype, List *qualname) --- 269,277 ---- 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; *************** *** 298,303 **** get_object_address_unqualified(ObjectType objtype, List *qualname) --- 304,314 ---- 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); *************** *** 635,640 **** object_exists(ObjectAddress address) --- 646,654 ---- case TSConfigRelationId: cache = TSCONFIGOID; break; + case ExtensionRelationId: + indexoid = ExtensionOidIndexId; + break; default: elog(ERROR, "unrecognized classid: %u", address.classId); } *** a/src/backend/catalog/pg_aggregate.c --- b/src/backend/catalog/pg_aggregate.c *************** *** 23,28 **** --- 23,29 ---- #include "catalog/pg_proc.h" #include "catalog/pg_proc_fn.h" #include "catalog/pg_type.h" + #include "commands/extension.h" #include "miscadmin.h" #include "parser/parse_coerce.h" #include "parser/parse_func.h" *************** *** 293,298 **** AggregateCreate(const char *aggName, --- 294,305 ---- referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } + + /* Depends on currently installed extension, if any */ + if (create_extension) + { + recordDependencyOn(&myself, &CreateExtensionAddress, DEPENDENCY_INTERNAL); + } } /* *** a/src/backend/catalog/pg_conversion.c --- b/src/backend/catalog/pg_conversion.c *************** *** 23,28 **** --- 23,29 ---- #include "catalog/pg_conversion_fn.h" #include "catalog/pg_namespace.h" #include "catalog/pg_proc.h" + #include "commands/extension.h" #include "mb/pg_wchar.h" #include "miscadmin.h" #include "utils/acl.h" *************** *** 132,137 **** ConversionCreate(const char *conname, Oid connamespace, --- 133,144 ---- recordDependencyOnOwner(ConversionRelationId, HeapTupleGetOid(tup), conowner); + /* Depends on currently installed extension, if any */ + if (create_extension) + { + recordDependencyOn(&myself, &CreateExtensionAddress, DEPENDENCY_INTERNAL); + } + /* Post creation hook for new conversion */ InvokeObjectAccessHook(OAT_POST_CREATE, ConversionRelationId, HeapTupleGetOid(tup), 0); *** a/src/backend/catalog/pg_depend.c --- b/src/backend/catalog/pg_depend.c *************** *** 20,25 **** --- 20,27 ---- #include "catalog/indexing.h" #include "catalog/pg_constraint.h" #include "catalog/pg_depend.h" + #include "catalog/pg_extension.h" + #include "catalog/pg_namespace.h" #include "miscadmin.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" *************** *** 265,270 **** changeDependencyFor(Oid classId, Oid objectId, --- 267,327 ---- } /* + * Change the deptype of the given dependency. + * + * Returns the number of lines that we changed. + */ + long + changeDependencyTypeFor(Oid objid, + Oid refclassId, Oid refobjectId, int refobjsubid, + DependencyType expected, DependencyType newtype) + { + Relation depRel; + ScanKeyData key[3]; + SysScanDesc scan; + HeapTuple tup; + long count = 0; + + depRel = heap_open(DependRelationId, RowExclusiveLock); + + /* Now search for dependency records */ + ScanKeyInit(&key[0], + Anum_pg_depend_refclassid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(refclassId)); + ScanKeyInit(&key[1], + Anum_pg_depend_refobjid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(refobjectId)); + ScanKeyInit(&key[2], + Anum_pg_depend_refobjsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(refobjsubid)); + scan = systable_beginscan(depRel, DependReferenceIndexId, true, + SnapshotNow, 3, key); + + while (HeapTupleIsValid((tup = systable_getnext(scan)))) + { + Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup); + + if (depform->objid == objid && depform->deptype == expected) + { + count++; + + /* make a modifiable copy */ + tup = heap_copytuple(tup); + depform = (Form_pg_depend) GETSTRUCT(tup); + depform->deptype = newtype; + simple_heap_update(depRel, &tup->t_self, tup); + } + } + systable_endscan(scan); + heap_close(depRel, RowExclusiveLock); + return count; + } + + + /* * isObjectPinned() * * Test if an object is required for basic database functionality. *************** *** 588,590 **** get_index_constraint(Oid indexId) --- 645,699 ---- return constraintId; } + + /* + * get_extension_namespace + * Given the OID of an extension, return the OID of the schema it + * depends on, or InvalidOid when not found + */ + Oid + get_extension_namespace(Oid extensionId) + { + Oid nspId = InvalidOid; + Relation depRel; + ScanKeyData key[3]; + SysScanDesc scan; + HeapTuple tup; + + /* Search the dependency table for the index */ + depRel = heap_open(DependRelationId, AccessShareLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_classid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(ExtensionRelationId)); + ScanKeyInit(&key[1], + Anum_pg_depend_objid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(extensionId)); + ScanKeyInit(&key[2], + Anum_pg_depend_objsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(0)); + + scan = systable_beginscan(depRel, DependDependerIndexId, true, + SnapshotNow, 3, key); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + Form_pg_depend deprec = (Form_pg_depend) GETSTRUCT(tup); + + if (deprec->refclassid == NamespaceRelationId && + deprec->refobjsubid == 0 && + deprec->deptype == DEPENDENCY_NORMAL) + { + nspId = deprec->refobjid; + break; + } + } + + systable_endscan(scan); + heap_close(depRel, AccessShareLock); + + return nspId; + } *** a/src/backend/catalog/pg_namespace.c --- b/src/backend/catalog/pg_namespace.c *************** *** 19,24 **** --- 19,25 ---- #include "catalog/indexing.h" #include "catalog/objectaccess.h" #include "catalog/pg_namespace.h" + #include "commands/extension.h" #include "utils/builtins.h" #include "utils/rel.h" #include "utils/syscache.h" *************** *** 76,81 **** NamespaceCreate(const char *nspName, Oid ownerId) --- 77,94 ---- /* Record dependency on owner */ recordDependencyOnOwner(NamespaceRelationId, nspoid, ownerId); + /* Record dependency on extension, if we're in a CREATE EXTENSION */ + if (create_extension) + { + ObjectAddress myself; + + myself.classId = NamespaceRelationId; + myself.objectId = nspoid; + myself.objectSubId = 0; + + recordDependencyOn(&myself, &CreateExtensionAddress, DEPENDENCY_INTERNAL); + } + /* Post creation hook for new schema */ InvokeObjectAccessHook(OAT_POST_CREATE, NamespaceRelationId, nspoid, 0); *** a/src/backend/catalog/pg_operator.c --- b/src/backend/catalog/pg_operator.c *************** *** 27,32 **** --- 27,33 ---- #include "catalog/pg_operator.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" + #include "commands/extension.h" #include "miscadmin.h" #include "parser/parse_oper.h" #include "utils/acl.h" *************** *** 855,858 **** makeOperatorDependencies(HeapTuple tuple) --- 856,865 ---- /* Dependency on owner */ recordDependencyOnOwner(OperatorRelationId, HeapTupleGetOid(tuple), oper->oprowner); + + /* Dependency on extension */ + if (create_extension) + { + recordDependencyOn(&myself, &CreateExtensionAddress, DEPENDENCY_INTERNAL); + } } *** a/src/backend/catalog/pg_proc.c --- b/src/backend/catalog/pg_proc.c *************** *** 24,29 **** --- 24,30 ---- #include "catalog/pg_proc.h" #include "catalog/pg_proc_fn.h" #include "catalog/pg_type.h" + #include "commands/extension.h" #include "executor/functions.h" #include "funcapi.h" #include "mb/pg_wchar.h" *************** *** 615,620 **** ProcedureCreate(const char *procedureName, --- 616,627 ---- nnewmembers, newmembers); } + /* dependency on extension */ + if (create_extension) + { + recordDependencyOn(&myself, &CreateExtensionAddress, DEPENDENCY_INTERNAL); + } + heap_freetuple(tup); /* Post creation hook for new function */ *** a/src/backend/catalog/pg_type.c --- b/src/backend/catalog/pg_type.c *************** *** 23,28 **** --- 23,29 ---- #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "catalog/pg_type_fn.h" + #include "commands/extension.h" #include "commands/typecmds.h" #include "miscadmin.h" #include "parser/scansup.h" *************** *** 33,38 **** --- 34,40 ---- #include "utils/rel.h" #include "utils/syscache.h" + Oid binary_upgrade_next_pg_type_oid = InvalidOid; /* ---------------------------------------------------------------- *************** *** 632,637 **** GenerateTypeDependencies(Oid typeNamespace, --- 634,645 ---- /* Normal dependency on the default expression. */ if (defaultExpr) recordDependencyOnExpr(&myself, defaultExpr, NIL, DEPENDENCY_NORMAL); + + /* dependency on extension */ + if (create_extension) + { + recordDependencyOn(&myself, &CreateExtensionAddress, DEPENDENCY_INTERNAL); + } } /* *** a/src/backend/catalog/system_views.sql --- b/src/backend/catalog/system_views.sql *************** *** 150,155 **** CREATE VIEW pg_locks AS --- 150,158 ---- CREATE VIEW pg_cursors AS SELECT * FROM pg_cursor() AS C; + CREATE VIEW pg_available_extensions AS + SELECT * FROM pg_extensions() AS E; + CREATE VIEW pg_prepared_xacts AS SELECT P.transaction, P.gid, P.prepared, U.rolname AS owner, D.datname AS database *** a/src/backend/commands/Makefile --- b/src/backend/commands/Makefile *************** *** 14,20 **** 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 \ 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 \ --- 14,21 ---- 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 \ ! extension.o explain.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 \ *** a/src/backend/commands/alter.c --- b/src/backend/commands/alter.c *************** *** 23,28 **** --- 23,29 ---- #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" *************** *** 186,191 **** ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt) --- 187,196 ---- 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); *************** *** 249,256 **** ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt) * * Relation must already by open, it's the responsibility of the caller to * close it. */ ! void AlterObjectNamespace(Relation rel, int cacheId, Oid classId, Oid objid, Oid nspOid, int Anum_name, int Anum_namespace, int Anum_owner, --- 254,265 ---- * * Relation must already by open, it's the responsibility of the caller to * close it. + * + * This function returns the old namespace OID so that at ALTER EXTENSION + * SET SCHEMA time we are able to ERROR out when we see more than one schema + * being used. */ ! Oid AlterObjectNamespace(Relation rel, int cacheId, Oid classId, Oid objid, Oid nspOid, int Anum_name, int Anum_namespace, int Anum_owner, *************** *** 333,340 **** AlterObjectNamespace(Relation rel, int cacheId, --- 342,454 ---- /* update dependencies to point to the new schema */ changeDependencyFor(classId, objid, NamespaceRelationId, oldNspOid, nspOid); + + return oldNspOid; } + /* + * Do the SET SCHEMA depending on the object class. + * + * We only consider objects that have a namespace and that can exist + * without depending on another object (like a table) which will + * have its dependencies follow the SET SCHEMA operation. + * + * This function returns the old namespace OID so that at ALTER EXTENSION + * SET SCHEMA time we are able to ERROR out when we see more than one schema + * being used. + */ + Oid + AlterObjectNamespace_internal(ObjectAddress *thisobj, Oid nspOid) + { + Oid oldNspOid = InvalidOid; + + switch (getObjectClass(thisobj)) + { + case OCLASS_CLASS: + { + Relation classRel; + Relation rel = relation_open(thisobj->objectId, RowExclusiveLock); + + switch (rel->rd_rel->relkind) + { + case RELKIND_COMPOSITE_TYPE: + /* + * just skip the pg_class entry, we have a pg_type + * entry too + */ + break; + + default: + classRel = heap_open(RelationRelationId, RowExclusiveLock); + oldNspOid = + AlterRelationNamespaceInternal( + classRel, + RelationGetRelid(rel), + RelationGetNamespace(rel), + nspOid, + true); + heap_close(classRel, RowExclusiveLock); + break; + } + relation_close(rel, RowExclusiveLock); + break; + } + + case OCLASS_PROC: + oldNspOid = AlterFunctionNamespace_oid(thisobj->objectId, nspOid); + break; + + case OCLASS_TYPE: + { + /* don't allow direct alteration of array types, skip */ + Oid elemOid = get_element_type(thisobj->objectId); + if (OidIsValid(elemOid) + && get_array_type(elemOid) == thisobj->objectId) + break; + + oldNspOid = AlterTypeNamespace_oid(thisobj->objectId, nspOid); + break; + } + + case OCLASS_CONVERSION: + oldNspOid = AlterConversionNamespace_oid(thisobj->objectId, nspOid); + break; + + case OCLASS_OPERATOR: + oldNspOid = AlterOperatorNamespace_oid(thisobj->objectId, nspOid); + break; + + case OCLASS_OPCLASS: + oldNspOid = AlterOpClassNamespace_oid(thisobj->objectId, nspOid); + break; + + case OCLASS_OPFAMILY: + oldNspOid = AlterOpFamilyNamespace_oid(thisobj->objectId, nspOid); + break; + + case OCLASS_TSPARSER: + oldNspOid = AlterTSParserNamespace_oid(thisobj->objectId, nspOid); + break; + + case OCLASS_TSDICT: + oldNspOid = + AlterTSDictionaryNamespace_oid(thisobj->objectId, nspOid); + break; + + case OCLASS_TSTEMPLATE: + oldNspOid = AlterTSTemplateNamespace_oid(thisobj->objectId, nspOid); + break; + + case OCLASS_TSCONFIG: + oldNspOid = + AlterTSConfigurationNamespace_oid(thisobj->objectId, nspOid); + break; + + default: + break; + } + return oldNspOid; + } /* * Executes an ALTER OBJECT / OWNER TO statement. Based on the object *** a/src/backend/commands/comment.c --- b/src/backend/commands/comment.c *************** *** 142,147 **** CommentObject(CommentStmt *stmt) --- 142,153 ---- (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, *** a/src/backend/commands/conversioncmds.c --- b/src/backend/commands/conversioncmds.c *************** *** 32,38 **** #include "utils/rel.h" #include "utils/syscache.h" ! static void AlterConversionOwner_internal(Relation rel, Oid conversionOid, Oid newOwnerId); /* --- 32,38 ---- #include "utils/rel.h" #include "utils/syscache.h" ! static void AlterConversionOwner_internal(Relation rel, Oid convOid, Oid newOwnerId); /* *************** *** 354,356 **** AlterConversionNamespace(List *name, const char *newschema) --- 354,379 ---- heap_close(rel, NoLock); } + + /* + * 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, + ConversionRelationId, convOid, newNspOid, + Anum_pg_conversion_conname, + Anum_pg_conversion_connamespace, + Anum_pg_conversion_conowner, + ACL_KIND_CONVERSION, + false); + + heap_close(rel, NoLock); + return oldNspOid; + } *** /dev/null --- b/src/backend/commands/extension.c *************** *** 0 **** --- 1,1313 ---- + /*------------------------------------------------------------------------- + * + * extension.c + * Commands to manipulate extensions + * + * Extensions in PostgreSQL allow user defined behavior plugged in at + * runtime. They will typically create SQL objects that you want to avoid + * dumping, issuing a single CREATE EXTENSION foo; command instead. + * + * All we need internally to manage an extension is an OID so that the + * dependent objects can get associated with it. An extension is created by + * populating the pg_extension catalog from a "control" file, containing + * some parameters. + * + * The extension control file format is the most simple name = value, we + * don't need anything more there. The SQL file to execute commands from is + * hardcoded to `pg_config --sharedir`/.install.sql. + * + * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/commands/extension.c + * + *------------------------------------------------------------------------- + */ + #include "postgres.h" + + #include + #include + #include + #include + + #include "access/genam.h" + #include "access/heapam.h" + #include "access/sysattr.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 "executor/spi.h" + #include "funcapi.h" + #include "mb/pg_wchar.h" + #include "miscadmin.h" + #include "storage/fd.h" + #include "utils/array.h" + #include "utils/builtins.h" + #include "utils/fmgroids.h" + #include "utils/genfile.h" + #include "utils/guc.h" + #include "utils/memutils.h" + #include "utils/rel.h" + #include "utils/syscache.h" + #include "utils/tqual.h" + + + typedef struct extension_fctx + { + directory_fctx dir; + ExtensionList *installed; + } extension_fctx; + + /* + * See commands/extension.h for details. + */ + ObjectAddress CreateExtensionAddress; + bool create_extension = false; + bool create_extension_with_user_data = true; + + /* + * Utility functions to handle extension related paths + */ + static bool + filename_extension_control_p(const char *filename) + { + return ! ( strcmp(filename, ".") == 0 + || strcmp(filename, "..") == 0 + || strrchr(filename, '.') == NULL + || strcmp(strrchr(filename, '.'), ".control") != 0 ); + } + + /* + * Given the 'name.control' filename, return the extension's name: + * + * `basename $filename .control` + */ + static char * + get_extension_name_from_control_filename(const char *filename) + { + if (filename_extension_control_p(filename)) + { + char *extname; + char *slash = strrchr(filename, '/'); + char *dot = strrchr(filename, '.'); + + if (slash == NULL) + /* relative filename... */ + slash = (char *)filename; + else + /* the file ends with .control and has a / before, that's safe */ + slash++; + + extname = palloc(MAXPGPATH); + memset(extname, '\0', MAXPGPATH); + strncpy(extname, slash, dot - slash); + return extname; + } + else + return NULL; + } + + static char * + get_extension_control_basepath(void) + { + char sharepath[MAXPGPATH]; + char *result; + + get_share_path(my_exec_path, sharepath); + result = 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 = 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. When 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 (char *)filename; + + get_share_path(my_exec_path, sharepath); + result = palloc(MAXPGPATH); + snprintf(result, MAXPGPATH, "%s/contrib/%s", sharepath, filename); + + return result; + } + + + /* + * 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 even try to minimize + * memory allocation risks here. + * + * Also note that only happens in CREATE EXTENSION and pg_extensions(). + * + * To support listing available extensions, the parameter init_gucs can be + * set to false to inhibit any GUC processing. + */ + static ExtensionControlFile * + parse_extension_control_file(const char *extname, + const char *filename, + bool init_gucs) + { + FILE *file; + bool saw_relocatable = false; + ExtensionControlFile *control = + (ExtensionControlFile *)palloc(sizeof(ExtensionControlFile)); + ConfigVariable *item, + *head = NULL, + *tail = NULL; + + elog(DEBUG1, "parse_extension_control_file(%s, '%s')", + extname, filename); + + /* + * default values, all string fields are NULL til parsed, except for the + * name, which ain't parsed + */ + control->name = pstrdup(extname); + control->script = control->version = control->comment = NULL; + control->encoding = -1; + + /* + * Now parse the file content, by line + */ + if ((file = AllocateFile(filename, PG_BINARY_R)) == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\" for reading: %m", + filename))); + + ParseConfigFp(file, filename, 0, ERROR, &head, &tail); + + for (item = head; item; 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, "relocatable") == 0) + { + saw_relocatable = true; + if (!parse_bool(item->value, &control->relocatable)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter \"%s\" requires a Boolean value", + item->name))); + return false; + } + } + 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 in '%s'", + item->value, filename))); + } + else + ereport(ERROR, + (errmsg("Unsupported parameter '%s' in file: %s", + item->name, filename))); + } + + if (control->name == NULL) + ereport(ERROR, + (errmsg("Missing a name parameter in file: %s", filename))); + + /* + * Handle default values + */ + if (control->script == NULL) + { + /* + * script defaults to ${extension-name}.sql + */ + char *script; + + script = palloc(MAXPGPATH); + memset(script, '\0', MAXPGPATH); + sprintf(script, "%s.sql", control->name); + control->script = script;; + } + + if (!saw_relocatable) + ereport(ERROR, + (errmsg("syntax error in extension control file: %s", filename), + errhint("The parameter 'relocatable' is mandatory."))); + + FreeFile(file); + return control; + } + + /* + * We're given an extension name, parse the {extname}.control file. + */ + static ExtensionControlFile * + read_extension_control_file(const char *extname, bool init_gucs) + { + char *filename = get_extension_control_filename(extname); + + if (access(filename, R_OK) == 0) + { + return parse_extension_control_file(extname, filename, true); + } + + ereport(ERROR, + (errmsg("could not read extension control file '%s' " + "for extension %s", filename, extname))); + + /* make compiler happy */ + return NULL; + } + + /* + * Read the SQL script into a string, and care for its encoding + */ + static char * + read_extension_script_file(const ExtensionControlFile *control, + const char *encoding_name) + { + char *filename = get_extension_absolute_path(control->script); + Datum script = CStringGetTextDatum(filename); + Datum raw = DirectFunctionCall1(pg_read_binary_file_all, script); + int src_encoding; + int dest_encoding = GetDatabaseEncoding(); + bytea *content; + const char *src_str; + char *dest_str; + int len; + + /* use database encoding if not given */ + if (encoding_name == NULL) + src_encoding = dest_encoding; + else + src_encoding = pg_char_to_encoding(encoding_name); + + if (src_encoding < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid source encoding name \"%s\"", + encoding_name))); + + content = (bytea *)DatumGetPointer(raw); + + /* make sure that source string is valid */ + 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 (dest_str != src_str) + return dest_str; + else + return text_to_cstring((text *)content); + } + + /* + * Execute given SQL string, and returns true only when success. + */ + static bool + execute_sql_string(const char *sql) + { + /* + * We abuse some internal knowledge from spi.h here. As we don't know + * which queries are going to get executed, we don't know what to expect + * as an OK return code from SPI_execute(). We assume that + * SPI_OK_CONNECT, SPI_OK_FINISH and SPI_OK_FETCH are quite improbable, + * though, and the errors are negatives. So a valid return code is + * considered to be SPI_OK_UTILITY or anything from there. + */ + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + if (SPI_execute(sql, false, 0) < SPI_OK_UTILITY) + return false; + + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); + + return true; + } + + /* + * CREATE EXTENSION + * + * Only superusers can create an extension. + */ + void + CreateExtension(CreateExtensionStmt *stmt) + { + + ListCell *option; + DefElem *d_user_data = NULL; + DefElem *d_schema = NULL; + bool user_data = true; + char *schema = NULL; + int encoding = -1; + + ExtensionControlFile *control; + + Relation rel; + Datum values[Natts_pg_extension]; + bool nulls[Natts_pg_extension]; + HeapTuple tuple; + Oid extensionoid; + + char *old_cmsgs = NULL, *old_lmsgs = NULL; /* silence compiler */ + bool reset_cmsgs = false, reset_lmsgs = false; + + /* 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."))); + + /* + * This check is done once here, early, before we bother parsing the + * control file, then redone once we have the ExclusiveLock on the + * pg_extension catalog to forbid having two backends concurrently + * creating the same extension. + */ + if( InvalidOid != get_extension_oid(stmt->extname, true) ) + { + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("extension \"%s\" already exists", stmt->extname))); + } + + /* + * The control file does not contain any text given to the backend + * operations, so setting the client_encoding here would have no + * effect. Also, the control file might contain a specific encoding to + * apply to reading the script, so we have to read it before to know + * which encoding to choose. + */ + control = read_extension_control_file(stmt->extname, true); + + /* + * Read the statement option list + * [ [ WITH ] [ [ NO ] USER DATA ] ] + */ + foreach(option, stmt->options) + { + DefElem *defel = (DefElem *) lfirst(option); + + if (strcmp(defel->defname, "user_data") == 0) + { + if (d_user_data) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + d_user_data = defel; + } + else if (strcmp(defel->defname, "schema") == 0) + { + if (d_schema) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + d_schema = defel; + } + } + + if (d_user_data && d_user_data->arg) + user_data = intVal(d_user_data->arg) == TRUE; + + if (d_schema && d_schema->arg) + schema = strVal(d_schema->arg); + else + /* + * Default to using schema public in replacements when no schema has + * been given. + */ + schema = pstrdup("public"); + + if (control->encoding != -1) + { + /* The encoding has already been validated */ + encoding = control->encoding; + } + else + { + /* default to utf8 encoding */ + encoding = PG_UTF8; + } + + elog(DEBUG1, "CreateExtensionStmt: %s user data, schema = '%s', encoding = '%s'", + user_data ? "with" : "without", + schema, + pg_encoding_to_char(encoding)); + + /* + * Insert tuple into pg_extension. + * + * We use ExclusiveLock here so that there's only CREATE EXTENSION + * possible at any given time. Maybe we could relax that, but it does + * not seem like a huge contention point. + */ + rel = heap_open(ExtensionRelationId, ExclusiveLock); + + /* + * This check is done once here, early, before we bother parsing the + * control file, then redone once we have the ExclusiveLock on the + * pg_extension catalog to forbid having two backends concurrently + * creating the same extension. + */ + if( InvalidOid != get_extension_oid(stmt->extname, true) ) + { + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("extension \"%s\" already exists", stmt->extname))); + } + + memset(values, 0, sizeof(values)); + MemSet(nulls, false, sizeof(nulls)); + + values[Anum_pg_extension_extname - 1] = + DirectFunctionCall1(namein, CStringGetDatum(control->name)); + + values[Anum_pg_extension_relocatable - 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); + + tuple = heap_form_tuple(rel->rd_att, values, nulls); + extensionoid = simple_heap_insert(rel, tuple); + CatalogUpdateIndexes(rel, tuple); + heap_freetuple(tuple); + heap_close(rel, ExclusiveLock); + + /* + * Comment on extension + */ + if (control->comment != NULL) + CreateComments(extensionoid, ExtensionRelationId, 0, control->comment); + + /* + * Set the globals create_extension and extension so that any object + * created in the script has a dependency recorded towards the extension + * here. + */ + CreateExtensionAddress.classId = ExtensionRelationId; + CreateExtensionAddress.objectId = extensionoid; + CreateExtensionAddress.objectSubId = 0; + create_extension = true; + + /* + * Also set create_extension_with_user_data so that the function + * pg_extension_with_user_data returns the right value. + */ + create_extension_with_user_data = user_data; + + elog(DEBUG1, + "Installing extension '%s' from '%s', in schema %s, %s user data", + stmt->extname, + get_extension_absolute_path(control->script), + schema ? schema : "public", + create_extension_with_user_data ? "with" : "without"); + + /* + * Force the min message settings here, because SPI will dump the + * query_string into the messages and in this case, that's a full + * extension script. At the time of this writing, contrib/isn/isn.sql.in + * is already 3197 lines and will issue lots of CREATE TYPE shell + * notices. + * + * We want to avoid dumping the full script for each of those. + */ + reset_cmsgs = client_min_messages < WARNING; + reset_lmsgs = log_min_messages < WARNING; + + if (reset_cmsgs) + { + old_cmsgs = pstrdup((char *)GetConfigOption("client_min_messages", false)); + SetConfigOption("client_min_messages", "warning", PGC_SUSET, PGC_S_SESSION); + } + if (reset_lmsgs) + { + old_lmsgs = pstrdup((char *)GetConfigOption("log_min_messages", false)); + SetConfigOption("log_min_messages", "warning", PGC_SUSET, PGC_S_SESSION); + } + + /* + * On failure, ensure that create_extension does not remain set. + */ + PG_TRY(); + { + char *filename = get_extension_absolute_path(control->script); + char *sql = read_extension_script_file(control, + pg_encoding_to_char(encoding)); + + /* + * An extension that is relocatable does not need any replacement, + * we will ALTER EXTENSION SET SCHEMA just after installing it. See + * below for details. + * + * An extension that is not relocatable need to offer a mechanism + * for the users to be able to choose where to intall it, that's + * using the placeholder @extschema@. + */ + if (control->relocatable) + { + List *search_path = fetch_search_path(false); + Oid first_schema, target_schema; + + if (!execute_sql_string(sql)) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("could not execute sql file: '%s'", filename))); + + /* + * CREATE EXTENSION foo WITH SCHEMA bar; + * + * The extension is relocatable, so we run its install script + * and it has to install all objects into the same schema. Then + * we relocate all the objects to the target SCHEMA bar, + * ERRORing out if some objects are not in the same schema than + * others, that's doing: + * + * ALTER EXTENSION foo SET SCHEMA bar; + * + * We skip this step if the target schema happens to be the + * first schema of the current search_path. + */ + first_schema = linitial_oid(search_path); + target_schema = LookupCreationNamespace(schema); + list_free(search_path); + + if (first_schema != target_schema) + AlterExtensionNamespace_oid(extensionoid, target_schema); + } + else + { + /* + * Prepare the replacements we use in execute_sql_file(). Even if + * the schema has not been specified by the user, we still have to + * set @extschema@ to 'public' so that extension scripts can rely on + * this placeholder. + */ + sql = text_to_cstring( + DatumGetTextPP( + DirectFunctionCall3(replace_text, + CStringGetTextDatum(sql), + CStringGetTextDatum("@extschema@"), + CStringGetTextDatum(schema)))); + + if (!execute_sql_string(sql)) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("could not execute sql file: '%s'", filename))); + } + } + PG_CATCH(); + { + create_extension = false; + PG_RE_THROW(); + } + PG_END_TRY(); + + /* + * the extension depends on the schema we used, record that + */ + { + ObjectAddress nsp; + + nsp.classId = NamespaceRelationId; + nsp.objectId = get_namespace_oid(schema, false); + nsp.objectSubId = 0; + + recordDependencyOn(&CreateExtensionAddress, &nsp, DEPENDENCY_NORMAL); + } + + if (reset_cmsgs) + SetConfigOption("client_min_messages", + old_cmsgs, PGC_SUSET, PGC_S_SESSION); + if (reset_lmsgs) + SetConfigOption("log_min_messages", + old_lmsgs, PGC_SUSET, PGC_S_SESSION); + + /* reset the current_extension dependency tracker */ + create_extension = false; + + return; + } + + /* + * Drop an extension + */ + void + DropExtension(DropExtensionStmt *stmt) + { + char *extname = stmt->extname; + Relation rel; + HeapScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + Oid extensionoid; + ObjectAddress object; + + /* Must be super user */ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to drop extension \"%s\"", + stmt->extname), + errhint("Must be superuser to drop an extension."))); + + /* + * Find the target tuple + */ + rel = heap_open(ExtensionRelationId, RowExclusiveLock); + + ScanKeyInit(&entry[0], + Anum_pg_extension_extname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(extname)); + scandesc = heap_beginscan(rel, SnapshotNow, 1, entry); + tuple = heap_getnext(scandesc, ForwardScanDirection); + + if (!HeapTupleIsValid(tuple)) + { + if (!stmt->missing_ok) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("extension \"%s\" does not exist", extname))); + } + else + { + ereport(NOTICE, + (errmsg("extension \"%s\" does not exist, skipping", + extname))); + /* XXX I assume I need one or both of these next two calls */ + heap_endscan(scandesc); + heap_close(rel, RowExclusiveLock); + } + return; + } + extensionoid = HeapTupleGetOid(tuple); + heap_endscan(scandesc); + heap_close(rel, RowExclusiveLock); + + /* + * Do the deletion + */ + object.classId = ExtensionRelationId; + object.objectId = extensionoid; + object.objectSubId = 0; + + performDeletion(&object, stmt->behavior); + + return; + } + + /* + * 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; + HeapScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + rel = heap_open(ExtensionRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + Anum_pg_extension_extname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(extname)); + scandesc = heap_beginscan(rel, SnapshotNow, 1, entry); + tuple = heap_getnext(scandesc, ForwardScanDirection); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + result = HeapTupleGetOid(tuple); + else + result = InvalidOid; + + heap_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; + HeapScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + /* + * Search pg_extension. We use a heapscan here even though there is an + * index on oid, on the theory that pg_extension will usually have just a + * few entries and so an indexed lookup is a waste of effort. + */ + rel = heap_open(ExtensionRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(ext_oid)); + scandesc = heap_beginscan(rel, SnapshotNow, 1, entry); + tuple = heap_getnext(scandesc, ForwardScanDirection); + + /* 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; + + heap_endscan(scandesc); + heap_close(rel, AccessShareLock); + + return result; + } + + /* + * extension_relocatable_p + * + * Returns true when the extension is marked 'relocatable' + */ + bool + extension_relocatable_p(Oid ext_oid) + { + bool relocatable; + Relation rel; + HeapScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + /* + * Search pg_extension. We use a heapscan here even though there is an + * index on oid, on the theory that pg_extension will usually have just a + * few entries and so an indexed lookup is a waste of effort. + */ + rel = heap_open(ExtensionRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(ext_oid)); + scandesc = heap_beginscan(rel, SnapshotNow, 1, entry); + tuple = heap_getnext(scandesc, ForwardScanDirection); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + relocatable = ((Form_pg_extension) GETSTRUCT(tuple))->relocatable; + else + relocatable = false; + + heap_endscan(scandesc); + heap_close(rel, AccessShareLock); + + return relocatable; + } + + /* + * Drop extension by OID. This is called to clean up dependencies. + */ + void + RemoveExtensionById(Oid extId) + { + Relation rel; + HeapScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + /* + * Search pg_extension. We use a heapscan here even though there is an + * index on oid, on the theory that pg_extension will usually have just a + * few entries and so an indexed lookup is a waste of effort. + */ + rel = heap_open(ExtensionRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(extId)); + scandesc = heap_beginscan(rel, SnapshotNow, 1, entry); + tuple = heap_getnext(scandesc, ForwardScanDirection); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + simple_heap_delete(rel, &tuple->t_self); + + heap_endscan(scandesc); + heap_close(rel, AccessShareLock); + } + + /* + * Public (SQL callable) function to let the extension install script know + * if it should care for user data or not. The typical usage is from pg_dump + * that will issue + * CREATE EXTENSION foo WITH NO DATA; + * + * That allows any object flagged as worthy of dump to be restored by the + * script produced by pg_dump rather than installed with default content + * from the extension's script. + */ + Datum + pg_extension_with_user_data(PG_FUNCTION_ARGS) + { + if (!create_extension) + ereport(ERROR, + (errmsg("This function can only be called from SQL " + "script executed by CREATE EXTENSION"))); + + PG_RETURN_BOOL(create_extension_with_user_data); + } + + /* + * Public (SQL callable) function for managing user object that you want to + * dump from an extension. + * + * pg_dump queries are filtering out objects that have an internal ('i') + * dependency with the extension's oid, so we provide a function to change + * the dependency fron 'i' to 'n'. As a result, pg_dump will consider such + * objects. + * + * update pg_depend + * set deptype = 'n' + * where refclassid = 'pg_extension'::regclass + * and refobjid = $ext_oid and oibjid = $1 + * and deptype = 'i' + */ + Datum + pg_extension_flag_dump(PG_FUNCTION_ARGS) + { + Oid objid = PG_GETARG_OID(0); + long count; + + /* + * CREATE EXTENSION is superuser only and we check we're in that code + * path, so we don't add another explicit check for superuser here. + */ + if (!create_extension) + ereport(ERROR, + (errmsg("This function can only be used from CREATE EXTENSION."))); + + count = changeDependencyTypeFor(objid, + ExtensionRelationId, + CreateExtensionAddress.objectId, 0, + DEPENDENCY_INTERNAL, DEPENDENCY_NORMAL); + + if (count == 0) + ereport(ERROR, + (errmsg("could not find a pg_depend entry for oid %u " + "referring to current extension.", objid))); + else if (count > 1) + ereport(ERROR, + (errmsg("current extension has more than one " + "dependency with object id %u.", objid))); + + PG_RETURN_BOOL(true); + } + + /* + * read (seqscan) the pg_extension catalog and fill in the exts array (that + * we allocate --- the caller has to free it). returns the number of items + * added into the array. + */ + static ExtensionList * + extension_list_installed(bool name_only) + { + Relation rel; + HeapScanDesc scandesc; + HeapTuple tuple; + bool visnull; + Datum d_version; + ExtensionList *elist; + + elist = (ExtensionList *)palloc(sizeof(ExtensionList)); + + elist->numrefs = 0; + elist->maxrefs = 8; + elist->exts = (ExtensionControlFile *) + palloc(elist->maxrefs * sizeof(ExtensionControlFile)); + + rel = heap_open(ExtensionRelationId, AccessShareLock); + scandesc = heap_beginscan(rel, SnapshotNow, 0, NULL); + + while ((tuple = heap_getnext(scandesc, ForwardScanDirection)) != NULL) + { + if (HeapTupleIsValid(tuple)) + { + Form_pg_extension e = (Form_pg_extension) GETSTRUCT(tuple); + + if( elist->numrefs >= elist->numrefs ) + { + elist->maxrefs += 12; + elist->exts = (ExtensionControlFile *) + repalloc(elist->exts, + elist->maxrefs * sizeof(ExtensionControlFile)); + } + + elist->exts[elist->numrefs].name = pstrdup(NameStr(e->extname)); + + if (!name_only) + { + elist->exts[elist->numrefs].relocatable = e->relocatable; + + d_version = heap_getattr(tuple, + Anum_pg_extension_extversion, + rel->rd_att, + &visnull); + + if (visnull) + elist->exts[elist->numrefs].version = NULL; + else + elist->exts[elist->numrefs].version = + pstrdup(TextDatumGetCString(d_version)); + + elist->exts[elist->numrefs].comment = + GetComment(HeapTupleGetOid(tuple), tuple->t_tableOid, 0); + } + elist->numrefs++; + } + } + heap_endscan(scandesc); + heap_close(rel, AccessShareLock); + + return elist; + } + + /* + * This function lists all extensions, the one currently already installed + * but also the one available for installation. For those, we parse the + * control file and return the information that would be available in the + * pg_extension catalog. + * + * A system view pg_extensions provides the user interface to this SRF + */ + Datum + pg_extensions(PG_FUNCTION_ARGS) + { + FuncCallContext *funcctx; + struct dirent *de; + extension_fctx *fctx; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to get extensions listings")))); + + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + TupleDesc tupdesc; + ExtensionList *installed; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + tupdesc = CreateTemplateTupleDesc(5, false); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name", + NAMEOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "version", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "relocatable", + BOOLOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "comment", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "installed", + BOOLOID, -1, 0); + + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + /* Get an array of installed extensions, but only their names */ + installed = extension_list_installed(true); + + fctx = palloc(sizeof(extension_fctx)); + fctx->dir.location = get_extension_control_basepath(); + fctx->dir.dirdesc = AllocateDir(fctx->dir.location); + fctx->installed = installed; + + if (!fctx->dir.dirdesc) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open directory \"%s\": %m", + fctx->dir.location))); + + funcctx->user_fctx = fctx; + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + fctx = (extension_fctx *) funcctx->user_fctx; + + while ((de = ReadDir(fctx->dir.dirdesc, fctx->dir.location)) != NULL) + { + ExtensionControlFile *control; + Datum values[5]; + bool nulls[5]; + bool is_installed = false; + int i = 0; + HeapTuple tuple; + + if (!filename_extension_control_p(de->d_name)) + continue; + + /* + * parse the control file but don't init any GUC from here + */ + control = parse_extension_control_file( + get_extension_name_from_control_filename(de->d_name), + get_extension_absolute_path(de->d_name), false); + + memset(values, 0, sizeof(values)); + MemSet(nulls, false, sizeof(nulls)); + + values[0] = DirectFunctionCall1(namein, CStringGetDatum(control->name)); + values[2] = BoolGetDatum(control->relocatable); + + if( control->version == NULL ) + nulls[1] = true; + else + values[1] = CStringGetTextDatum(control->version); + + if( control->comment == NULL ) + nulls[3] = true; + else + values[3] = CStringGetTextDatum(control->comment); + + /* + * We add a boolean "installed" to the pg_extension struct. As we + * expect installations to typically have very few extensions + * installed, we simple rescan the same array each time. + */ + while( !is_installed && i < fctx->installed->numrefs ) + is_installed = 0 == strcmp(fctx->installed->exts[i++].name, + control->name); + + values[4] = BoolGetDatum(is_installed); + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } + FreeDir(fctx->dir.dirdesc); + SRF_RETURN_DONE(funcctx); + } + + /* + * List all objects that pertain to a given extension. + * + * SELECT class, classid, objid, objdesc + * FROM pg_extension_objects('hstore'); + */ + Datum + pg_extension_objects(PG_FUNCTION_ARGS) + { + char *extname = NameStr(*PG_GETARG_NAME(0)); + FuncCallContext *funcctx; + extension_objects_fctx *fctx; + + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + TupleDesc tupdesc; + ObjectAddress *object; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + tupdesc = CreateTemplateTupleDesc(4, false); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "class", + REGCLASSOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "classid", + OIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "objid", + OIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "objdesc", + TEXTOID, -1, 0); + + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + fctx = (extension_objects_fctx *)palloc(sizeof(extension_objects_fctx)); + + object = (ObjectAddress *)palloc(sizeof(ObjectAddress)); + object->classId = ExtensionRelationId; + object->objectId = get_extension_oid(extname, false); + object->objectSubId = 0; + + fctx->targetObjects = listDependentObjects(object); + fctx->i = fctx->targetObjects->numrefs - 1; + + funcctx->user_fctx = fctx; + + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + fctx = (extension_objects_fctx *) funcctx->user_fctx; + + while (fctx->i >= 0) + { + ObjectAddress *thisobj = fctx->targetObjects->refs + fctx->i--; + char *regclass = (char *)palloc(NAMEDATALEN*sizeof(char)); + Datum values[4]; + bool nulls[4]; + HeapTuple tuple; + + sprintf(regclass, "%u", thisobj->classId); + memset(values, 0, sizeof(values)); + MemSet(nulls, false, sizeof(nulls)); + + values[0] = DirectFunctionCall1(regclassin, CStringGetDatum(regclass)); + values[1] = ObjectIdGetDatum(thisobj->classId); + values[2] = ObjectIdGetDatum(thisobj->objectId); + values[3] = CStringGetTextDatum(getObjectDescription(thisobj)); + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } + freeDependentObjects(fctx->targetObjects); + SRF_RETURN_DONE(funcctx); + } + + /* + * Execute ALTER EXTENSION SET SCHEMA + */ + void + AlterExtensionNamespace(List *name, const char *newschema) + { + Oid extensionOid, nspOid; + + Assert(list_length(name) == 1); + extensionOid = get_extension_oid(strVal(linitial(name)), false); + nspOid = LookupCreationNamespace(newschema); + + AlterExtensionNamespace_oid(extensionOid, nspOid); + } + + void + AlterExtensionNamespace_oid(Oid extensionOid, Oid nspOid) + { + Oid oldNspOid; + ObjectAddress *object; + ObjectAddresses *targetObjects; + int i; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to ALTER EXTENSION")))); + + if (!extension_relocatable_p(extensionOid)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + (errmsg("this extension does not support SET SCHEMA")))); + + object = (ObjectAddress *)palloc(sizeof(ObjectAddress)); + object->classId = ExtensionRelationId; + object->objectId = extensionOid; + object->objectSubId = 0; + + /* + * At CREATE EXTENSION time the dependency towards the schema where live + * the extension's objects is not registered yet, so we get InvalidOid + * here. + */ + oldNspOid = get_extension_namespace(extensionOid); + + targetObjects = listDependentObjects(object); + + for (i = 0; i < targetObjects->numrefs; i++) + { + Oid thisobj_oldNspOid; + ObjectAddress *thisobj = targetObjects->refs + i; + + elog(DEBUG1, "SET SCHEMA on %u: %s", + thisobj->objectId, getObjectDescription(thisobj)); + + thisobj_oldNspOid = AlterObjectNamespace_internal(thisobj, nspOid); + + /* + * When called from CREATE EXTENSION, we have to track the first's + * object Oid here + */ + if (thisobj_oldNspOid != InvalidOid && oldNspOid == InvalidOid) + oldNspOid = thisobj_oldNspOid; + + /* we get InvalidOid for no-namespace objects */ + if (thisobj_oldNspOid != InvalidOid && thisobj_oldNspOid != oldNspOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + (errmsg("this extension does not support SET SCHEMA"), + errdetail("%s is not in the extension's schema [%i %i]", + getObjectDescription(thisobj), + oldNspOid, + thisobj_oldNspOid)))); + } + + /* update dependencies to point to the new schema */ + changeDependencyFor(ExtensionRelationId, extensionOid, + NamespaceRelationId, oldNspOid, nspOid); + } *** a/src/backend/commands/foreigncmds.c --- b/src/backend/commands/foreigncmds.c *************** *** 33,38 **** --- 33,39 ---- #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/syscache.h" + #include "commands/extension.h" /* *************** *** 412,417 **** CreateForeignDataWrapper(CreateFdwStmt *stmt) --- 413,424 ---- referenced.objectId = fdwvalidator; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + /* dependency on extension */ + if (create_extension) + { + recordDependencyOn(&myself, &CreateExtensionAddress, DEPENDENCY_INTERNAL); + } } recordDependencyOnOwner(ForeignDataWrapperRelationId, fdwId, ownerId); *************** *** 701,706 **** CreateForeignServer(CreateForeignServerStmt *stmt) --- 708,719 ---- recordDependencyOnOwner(ForeignServerRelationId, srvId, ownerId); + /* dependency on extension */ + if (create_extension) + { + recordDependencyOn(&myself, &CreateExtensionAddress, DEPENDENCY_INTERNAL); + } + /* Post creation hook for new foreign server */ InvokeObjectAccessHook(OAT_POST_CREATE, ForeignServerRelationId, srvId, 0); *************** *** 975,980 **** CreateUserMapping(CreateUserMappingStmt *stmt) --- 988,999 ---- /* Record the mapped user dependency */ recordDependencyOnOwner(UserMappingRelationId, umId, useId); + /* dependency on extension */ + if (create_extension) + { + recordDependencyOn(&myself, &CreateExtensionAddress, DEPENDENCY_INTERNAL); + } + /* Post creation hook for new user mapping */ InvokeObjectAccessHook(OAT_POST_CREATE, UserMappingRelationId, umId, 0); *** a/src/backend/commands/functioncmds.c --- b/src/backend/commands/functioncmds.c *************** *** 62,68 **** #include "utils/rel.h" #include "utils/syscache.h" #include "utils/tqual.h" ! static void AlterFunctionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId); --- 62,68 ---- #include "utils/rel.h" #include "utils/syscache.h" #include "utils/tqual.h" ! #include "commands/extension.h" static void AlterFunctionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId); *************** *** 1762,1767 **** CreateCast(CreateCastStmt *stmt) --- 1762,1772 ---- recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } + if (create_extension) + { + recordDependencyOn(&myself, &CreateExtensionAddress, DEPENDENCY_INTERNAL); + } + /* Post creation hook for new cast */ InvokeObjectAccessHook(OAT_POST_CREATE, CastRelationId, myself.objectId, 0); *************** *** 1875,1887 **** 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) --- 1880,1886 ---- *************** *** 1894,1899 **** AlterFunctionNamespace(List *name, List *argtypes, bool isagg, --- 1893,1913 ---- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(name)); + 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); *************** *** 1901,1909 **** AlterFunctionNamespace(List *name, List *argtypes, bool isagg, oldNspOid = proc->pronamespace; - /* get schema OID and check its permissions */ - nspOid = LookupCreationNamespace(newschema); - /* common checks on switching namespaces */ CheckSetNamespace(oldNspOid, nspOid, ProcedureRelationId, procOid); --- 1915,1920 ---- *************** *** 1916,1922 **** AlterFunctionNamespace(List *name, List *argtypes, bool isagg, (errcode(ERRCODE_DUPLICATE_FUNCTION), errmsg("function \"%s\" already exists in schema \"%s\"", NameStr(proc->proname), ! newschema))); /* OK, modify the pg_proc row */ --- 1927,1933 ---- (errcode(ERRCODE_DUPLICATE_FUNCTION), errmsg("function \"%s\" already exists in schema \"%s\"", NameStr(proc->proname), ! get_namespace_name(nspOid)))); /* OK, modify the pg_proc row */ *************** *** 1930,1940 **** 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)); heap_freetuple(tup); heap_close(procRel, RowExclusiveLock); } --- 1941,1953 ---- if (changeDependencyFor(ProcedureRelationId, procOid, NamespaceRelationId, oldNspOid, nspOid) != 1) elog(ERROR, "failed to change schema dependency for function \"%s\"", ! NameStr(proc->proname)); heap_freetuple(tup); heap_close(procRel, RowExclusiveLock); + + return oldNspOid; } *** a/src/backend/commands/opclasscmds.c --- b/src/backend/commands/opclasscmds.c *************** *** 44,49 **** --- 44,50 ---- #include "utils/rel.h" #include "utils/syscache.h" #include "utils/tqual.h" + #include "commands/extension.h" /* *************** *** 309,314 **** CreateOpFamily(char *amname, char *opfname, Oid namespaceoid, Oid amoid) --- 310,321 ---- /* dependency on owner */ recordDependencyOnOwner(OperatorFamilyRelationId, opfamilyoid, GetUserId()); + /* dependency on extension */ + if (create_extension) + { + recordDependencyOn(&myself, &CreateExtensionAddress, DEPENDENCY_INTERNAL); + } + /* Post creation hook for new operator family */ InvokeObjectAccessHook(OAT_POST_CREATE, OperatorFamilyRelationId, opfamilyoid, 0); *************** *** 709,714 **** DefineOpClass(CreateOpClassStmt *stmt) --- 716,727 ---- /* dependency on owner */ recordDependencyOnOwner(OperatorClassRelationId, opclassoid, GetUserId()); + /* dependency on extension */ + if (create_extension) + { + recordDependencyOn(&myself, &CreateExtensionAddress, DEPENDENCY_INTERNAL); + } + /* Post creation hook for new operator class */ InvokeObjectAccessHook(OAT_POST_CREATE, OperatorClassRelationId, opclassoid, 0); *************** *** 2024,2029 **** AlterOpClassNamespace(List *name, List *argam, const char *newschema) --- 2037,2069 ---- heap_close(rel, NoLock); } + Oid + AlterOpClassNamespace_oid(Oid opclassOid, Oid newNspOid) + { + Oid oldNspOid; + HeapTuple tup; + Relation rel; + + rel = heap_open(OperatorClassRelationId, RowExclusiveLock); + + tup = SearchSysCacheCopy1(CLAOID, ObjectIdGetDatum(opclassOid)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for opclass %u", opclassOid); + + oldNspOid = + AlterObjectNamespace(rel, CLAOID, OperatorClassRelationId, + HeapTupleGetOid(tup), newNspOid, + Anum_pg_opfamily_opfname, + Anum_pg_opfamily_opfnamespace, + Anum_pg_opfamily_opfowner, + ACL_KIND_OPCLASS, + false); + + heap_freetuple(tup); + heap_close(rel, NoLock); + return oldNspOid; + } + /* * Change opfamily owner by name */ *************** *** 2214,2216 **** AlterOpFamilyNamespace(List *name, List *argam, const char *newschema) --- 2254,2283 ---- heap_close(rel, NoLock); } + + Oid + AlterOpFamilyNamespace_oid(Oid opfamilyOid, Oid newNspOid) + { + Oid oldNspOid; + HeapTuple tup; + Relation rel; + + rel = heap_open(OperatorFamilyRelationId, RowExclusiveLock); + + tup = SearchSysCacheCopy1(OPFAMILYOID, ObjectIdGetDatum(opfamilyOid)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for opfamily %u", opfamilyOid); + + oldNspOid = + AlterObjectNamespace(rel, OPFAMILYOID, OperatorFamilyRelationId, + opfamilyOid, newNspOid, + Anum_pg_opfamily_opfname, + Anum_pg_opfamily_opfnamespace, + Anum_pg_opfamily_opfowner, + ACL_KIND_OPFAMILY, + false); + + heap_freetuple(tup); + heap_close(rel, NoLock); + return oldNspOid; + } *** a/src/backend/commands/operatorcmds.c --- b/src/backend/commands/operatorcmds.c *************** *** 486,488 **** AlterOperatorNamespace(List *names, List *argtypes, const char *newschema) --- 486,509 ---- heap_close(rel, NoLock); } + + Oid + AlterOperatorNamespace_oid(Oid operOid, Oid newNspOid) + { + Oid oldNspOid; + Relation rel; + + rel = heap_open(OperatorRelationId, RowExclusiveLock); + + oldNspOid = AlterObjectNamespace(rel, OPEROID, OperatorRelationId, operOid, + newNspOid, + Anum_pg_operator_oprname, + Anum_pg_operator_oprnamespace, + Anum_pg_operator_oprowner, + ACL_KIND_OPER, + false); + + heap_close(rel, NoLock); + + return oldNspOid; + } *** a/src/backend/commands/proclang.c --- b/src/backend/commands/proclang.c *************** *** 37,42 **** --- 37,43 ---- #include "utils/rel.h" #include "utils/syscache.h" #include "utils/tqual.h" + #include "commands/extension.h" typedef struct *************** *** 426,431 **** create_proc_lang(const char *languageName, bool replace, --- 427,438 ---- recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } + /* dependency on extension */ + if (create_extension) + { + recordDependencyOn(&myself, &CreateExtensionAddress, DEPENDENCY_INTERNAL); + } + /* Post creation hook for new procedural language */ InvokeObjectAccessHook(OAT_POST_CREATE, LanguageRelationId, myself.objectId, 0); *** a/src/backend/commands/tablecmds.c --- b/src/backend/commands/tablecmds.c *************** *** 8225,8231 **** AlterTableNamespace(RangeVar *relation, const char *newschema, * entry, and the pg_depend entry if any. Caller must already have * opened and write-locked pg_class. */ ! void AlterRelationNamespaceInternal(Relation classRel, Oid relOid, Oid oldNspOid, Oid newNspOid, bool hasDependEntry) --- 8225,8231 ---- * entry, and the pg_depend entry if any. Caller must already have * opened and write-locked pg_class. */ ! Oid AlterRelationNamespaceInternal(Relation classRel, Oid relOid, Oid oldNspOid, Oid newNspOid, bool hasDependEntry) *************** *** 8263,8268 **** AlterRelationNamespaceInternal(Relation classRel, Oid relOid, --- 8263,8270 ---- NameStr(classForm->relname)); heap_freetuple(classTup); + + return oldNspOid; } /* *** a/src/backend/commands/tsearchcmds.c --- b/src/backend/commands/tsearchcmds.c *************** *** 33,38 **** --- 33,39 ---- #include "catalog/pg_ts_template.h" #include "catalog/pg_type.h" #include "commands/alter.h" + #include "commands/extension.h" #include "commands/defrem.h" #include "miscadmin.h" #include "nodes/makefuncs.h" *************** *** 156,161 **** makeParserDependencies(HeapTuple tuple) --- 157,168 ---- referenced.objectId = prs->prsheadline; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } + + /* dependency on extension */ + if (create_extension) + { + recordDependencyOn(&myself, &CreateExtensionAddress, DEPENDENCY_INTERNAL); + } } /* *************** *** 422,427 **** AlterTSParserNamespace(List *name, const char *newschema) --- 429,453 ---- heap_close(rel, NoLock); } + Oid + AlterTSParserNamespace_oid(Oid prsId, Oid newNspOid) + { + Oid oldNspOid; + Relation rel; + + rel = heap_open(TSParserRelationId, RowExclusiveLock); + + oldNspOid = + AlterObjectNamespace(rel, TSPARSEROID, + TSParserRelationId, prsId, newNspOid, + Anum_pg_ts_parser_prsname, + Anum_pg_ts_parser_prsnamespace, + -1, -1, true); + + heap_close(rel, NoLock); + return oldNspOid; + } + /* ---------------------- TS Dictionary commands -----------------------*/ /* *************** *** 452,457 **** makeDictionaryDependencies(HeapTuple tuple) --- 478,489 ---- referenced.objectId = dict->dicttemplate; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + /* dependency on extension, if in create extension */ + if (create_extension) + { + recordDependencyOn(&myself, &CreateExtensionAddress, DEPENDENCY_INTERNAL); + } } /* *************** *** 678,683 **** AlterTSDictionaryNamespace(List *name, const char *newschema) --- 710,736 ---- heap_close(rel, NoLock); } + Oid + AlterTSDictionaryNamespace_oid(Oid dictId, Oid newNspOid) + { + Oid oldNspOid; + Relation rel; + + rel = heap_open(TSDictionaryRelationId, RowExclusiveLock); + + oldNspOid = + AlterObjectNamespace(rel, TSDICTOID, + TSDictionaryRelationId, dictId, newNspOid, + Anum_pg_ts_dict_dictname, + Anum_pg_ts_dict_dictnamespace, + Anum_pg_ts_dict_dictowner, + ACL_KIND_TSDICTIONARY, + false); + + heap_close(rel, NoLock); + return oldNspOid; + } + /* * DROP TEXT SEARCH DICTIONARY */ *************** *** 1024,1029 **** makeTSTemplateDependencies(HeapTuple tuple) --- 1077,1088 ---- referenced.objectId = tmpl->tmplinit; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } + + /* dependency on extension, if in create extension */ + if (create_extension) + { + recordDependencyOn(&myself, &CreateExtensionAddress, DEPENDENCY_INTERNAL); + } } /* *************** *** 1186,1191 **** AlterTSTemplateNamespace(List *name, const char *newschema) --- 1245,1269 ---- heap_close(rel, NoLock); } + Oid + AlterTSTemplateNamespace_oid(Oid tmplId, Oid newNspOid) + { + Oid oldNspOid; + Relation rel; + + rel = heap_open(TSTemplateRelationId, RowExclusiveLock); + + oldNspOid = + AlterObjectNamespace(rel, TSTEMPLATEOID, TSTemplateRelationId, + tmplId, newNspOid, + Anum_pg_ts_template_tmplname, + Anum_pg_ts_template_tmplnamespace, + -1, -1, true); + + heap_close(rel, NoLock); + return oldNspOid; + } + /* * DROP TEXT SEARCH TEMPLATE */ *************** *** 1376,1381 **** makeConfigurationDependencies(HeapTuple tuple, bool removeOld, --- 1454,1465 ---- /* Record 'em (this includes duplicate elimination) */ record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); + /* dependencies on extension being created, if any */ + if (create_extension) + { + recordDependencyOn(&myself, &CreateExtensionAddress, DEPENDENCY_INTERNAL); + } + free_object_addresses(addrs); } *************** *** 1613,1618 **** AlterTSConfigurationNamespace(List *name, const char *newschema) --- 1697,1723 ---- heap_close(rel, NoLock); } + Oid + AlterTSConfigurationNamespace_oid(Oid cfgId, Oid newNspOid) + { + Oid oldNspOid; + Relation rel; + + rel = heap_open(TSConfigRelationId, RowExclusiveLock); + + oldNspOid = + AlterObjectNamespace(rel, TSCONFIGOID, + TSConfigRelationId, cfgId, newNspOid, + Anum_pg_ts_config_cfgname, + Anum_pg_ts_config_cfgnamespace, + Anum_pg_ts_config_cfgowner, + ACL_KIND_TSCONFIGURATION, + false); + + heap_close(rel, NoLock); + return oldNspOid; + } + /* * DROP TEXT SEARCH CONFIGURATION */ *** a/src/backend/commands/typecmds.c --- b/src/backend/commands/typecmds.c *************** *** 2765,2784 **** 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); /* 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) --- 2765,2791 ---- TypeName *typename; Oid typeOid; Oid nspOid; /* 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)); /* don't allow direct alteration of array types */ elemOid = get_element_type(typeOid); if (OidIsValid(elemOid) && get_array_type(elemOid) == typeOid) *************** *** 2790,2796 **** AlterTypeNamespace(List *names, const char *newschema) format_type_be(elemOid)))); /* and do the work */ ! AlterTypeNamespaceInternal(typeOid, nspOid, false, true); } /* --- 2797,2803 ---- format_type_be(elemOid)))); /* and do the work */ ! return AlterTypeNamespaceInternal(typeOid, nspOid, false, false); } /* *************** *** 2806,2812 **** AlterTypeNamespace(List *names, const char *newschema) * a table type. ALTER TABLE has to be used to move a table to a new * namespace. */ ! void AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, bool isImplicitArray, bool errorOnTableType) --- 2813,2819 ---- * a table type. ALTER TABLE has to be used to move a table to a new * namespace. */ ! Oid AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, bool isImplicitArray, bool errorOnTableType) *************** *** 2913,2916 **** AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, --- 2920,2925 ---- /* Recursively alter the associated array type, if any */ if (OidIsValid(arrayOid)) AlterTypeNamespaceInternal(arrayOid, nspOid, true, true); + + return oldNspOid; } *** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** *** 3150,3155 **** _copyDiscardStmt(DiscardStmt *from) --- 3150,3178 ---- return newnode; } + static CreateExtensionStmt * + _copyCreateExtensionStmt(CreateExtensionStmt *from) + { + CreateExtensionStmt *newnode = makeNode(CreateExtensionStmt); + + COPY_STRING_FIELD(extname); + COPY_NODE_FIELD(options); + + return newnode; + } + + static DropExtensionStmt * + _copyDropExtensionStmt(DropExtensionStmt *from) + { + DropExtensionStmt *newnode = makeNode(DropExtensionStmt); + + COPY_STRING_FIELD(extname); + COPY_SCALAR_FIELD(missing_ok); + COPY_SCALAR_FIELD(behavior); + + return newnode; + } + static CreateTableSpaceStmt * _copyCreateTableSpaceStmt(CreateTableSpaceStmt *from) { *************** *** 4168,4173 **** copyObject(void *from) --- 4191,4202 ---- case T_DropTableSpaceStmt: retval = _copyDropTableSpaceStmt(from); break; + case T_CreateExtensionStmt: + retval = _copyCreateExtensionStmt(from); + break; + case T_DropExtensionStmt: + retval = _copyDropExtensionStmt(from); + break; case T_AlterTableSpaceOptionsStmt: retval = _copyAlterTableSpaceOptionsStmt(from); break; *** a/src/backend/nodes/equalfuncs.c --- b/src/backend/nodes/equalfuncs.c *************** *** 1614,1619 **** _equalDropTableSpaceStmt(DropTableSpaceStmt *a, DropTableSpaceStmt *b) --- 1614,1638 ---- } static bool + _equalCreateExtensionStmt(CreateExtensionStmt *a, CreateExtensionStmt *b) + { + COMPARE_STRING_FIELD(extname); + COMPARE_NODE_FIELD(options); + + return true; + } + + static bool + _equalDropExtensionStmt(DropExtensionStmt *a, DropExtensionStmt *b) + { + COMPARE_STRING_FIELD(extname); + COMPARE_SCALAR_FIELD(missing_ok); + COMPARE_SCALAR_FIELD(behavior); + + return true; + } + + static bool _equalAlterTableSpaceOptionsStmt(AlterTableSpaceOptionsStmt *a, AlterTableSpaceOptionsStmt *b) { *************** *** 2785,2790 **** equal(void *a, void *b) --- 2804,2815 ---- case T_DiscardStmt: retval = _equalDiscardStmt(a, b); break; + case T_CreateExtensionStmt: + retval = _equalCreateExtensionStmt(a, b); + break; + case T_DropExtensionStmt: + retval = _equalDropExtensionStmt(a, b); + break; case T_CreateTableSpaceStmt: retval = _equalCreateTableSpaceStmt(a, b); break; *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** *** 190,196 **** static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_ AlterDefaultPrivilegesStmt DefACLAction AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt ! CreateDomainStmt CreateGroupStmt CreateOpClassStmt CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateAssertStmt CreateTrigStmt --- 190,196 ---- AlterDefaultPrivilegesStmt DefACLAction AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt ! CreateDomainStmt CreateExtensionStmt CreateGroupStmt CreateOpClassStmt CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateAssertStmt CreateTrigStmt *************** *** 198,204 **** static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_ CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt ! DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt --- 198,204 ---- CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt ! DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt DropExtensionStmt DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt *************** *** 225,233 **** static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_ %type opt_drop_behavior %type createdb_opt_list alterdb_opt_list copy_opt_list ! transaction_mode_list %type createdb_opt_item alterdb_opt_item copy_opt_item ! transaction_mode_item %type opt_lock lock_type cast_context %type vacuum_option_list vacuum_option_elem --- 225,233 ---- %type opt_drop_behavior %type createdb_opt_list alterdb_opt_list copy_opt_list ! transaction_mode_list create_extension_opt_list %type createdb_opt_item alterdb_opt_item copy_opt_item ! transaction_mode_item create_extension_opt_item %type opt_lock lock_type cast_context %type vacuum_option_list vacuum_option_elem *************** *** 485,491 **** 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 FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS --- 485,491 ---- 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 EXTENSION EXTERNAL EXTRACT FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS *************** *** 682,687 **** stmt : --- 682,688 ---- | CreateCastStmt | CreateConversionStmt | CreateDomainStmt + | CreateExtensionStmt | CreateFdwStmt | CreateForeignServerStmt | CreateFunctionStmt *************** *** 707,712 **** stmt : --- 708,714 ---- | DoStmt | DropAssertStmt | DropCastStmt + | DropExtensionStmt | DropFdwStmt | DropForeignServerStmt | DropGroupStmt *************** *** 3086,3091 **** opt_procedural: --- 3088,3159 ---- /***************************************************************************** * * QUERY: + * CREATE EXTENSION extension + * [ [ WITH ] [ [ NO ] USER DATA ] + * [ 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: + USER DATA_P + { + $$ = makeDefElem("user_data", (Node *)makeInteger(TRUE)); + } + | NO USER DATA_P + { + $$ = makeDefElem("user_data", (Node *)makeInteger(FALSE)); + } + | SCHEMA opt_equal name + { + $$ = makeDefElem("schema", (Node *)makeString($3)); + } + ; + + /***************************************************************************** + * + * QUERY : + * DROP EXTENSION [ RESTRICT | CASCADE ] + * + ****************************************************************************/ + + DropExtensionStmt: DROP EXTENSION name opt_drop_behavior + { + DropExtensionStmt *n = makeNode(DropExtensionStmt); + n->extname = $3; + n->missing_ok = false; + n->behavior = $4; + $$ = (Node *) n; + } + | DROP EXTENSION IF_P EXISTS name opt_drop_behavior + { + DropExtensionStmt *n = makeNode(DropExtensionStmt); + n->extname = $5; + n->missing_ok = true; + n->behavior = $6; + $$ = (Node *) n; + } + ; + + /***************************************************************************** + * + * QUERY: * CREATE TABLESPACE tablespace LOCATION '/path/to/tablespace/' * *****************************************************************************/ *************** *** 4234,4240 **** opt_restart_seqs: * * COMMENT ON [ [ DATABASE | DOMAIN | INDEX | SEQUENCE | TABLE | TYPE | VIEW | * CONVERSION | LANGUAGE | OPERATOR CLASS | LARGE OBJECT | ! * CAST | COLUMN | SCHEMA | TABLESPACE | ROLE | * TEXT SEARCH PARSER | TEXT SEARCH DICTIONARY | * TEXT SEARCH TEMPLATE | * TEXT SEARCH CONFIGURATION ] | --- 4302,4308 ---- * * COMMENT ON [ [ DATABASE | DOMAIN | INDEX | SEQUENCE | TABLE | TYPE | VIEW | * CONVERSION | LANGUAGE | OPERATOR CLASS | LARGE OBJECT | ! * CAST | COLUMN | SCHEMA | TABLESPACE | EXTENSION | ROLE | * TEXT SEARCH PARSER | TEXT SEARCH DICTIONARY | * TEXT SEARCH TEMPLATE | * TEXT SEARCH CONFIGURATION ] | *************** *** 4413,4418 **** comment_type: --- 4481,4487 ---- | VIEW { $$ = OBJECT_VIEW; } | CONVERSION_P { $$ = OBJECT_CONVERSION; } | TABLESPACE { $$ = OBJECT_TABLESPACE; } + | EXTENSION { $$ = OBJECT_EXTENSION; } | ROLE { $$ = OBJECT_ROLE; } ; *************** *** 6068,6073 **** AlterObjectSchemaStmt: --- 6137,6150 ---- 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); *** a/src/backend/rewrite/rewriteDefine.c --- b/src/backend/rewrite/rewriteDefine.c *************** *** 22,27 **** --- 22,28 ---- #include "catalog/objectaccess.h" #include "catalog/pg_rewrite.h" #include "catalog/storage.h" + #include "commands/extension.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "parser/parse_utilcmd.h" *************** *** 168,173 **** InsertRule(char *rulname, --- 169,180 ---- recordDependencyOnExpr(&myself, (Node *) action, NIL, DEPENDENCY_NORMAL); + /* Depends on currently installed extension, if any */ + if (create_extension) + { + recordDependencyOn(&myself, &CreateExtensionAddress, DEPENDENCY_INTERNAL); + } + if (event_qual != NULL) { /* Find query containing OLD/NEW rtable entries */ *** a/src/backend/tcop/utility.c --- b/src/backend/tcop/utility.c *************** *** 32,37 **** --- 32,38 ---- #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" *************** *** 175,180 **** check_xact_readonly(Node *parsetree) --- 176,182 ---- case T_CreateConversionStmt: case T_CreatedbStmt: case T_CreateDomainStmt: + case T_CreateExtensionStmt: case T_CreateFunctionStmt: case T_CreateRoleStmt: case T_IndexStmt: *************** *** 195,200 **** check_xact_readonly(Node *parsetree) --- 197,203 ---- case T_DropCastStmt: case T_DropStmt: case T_DropdbStmt: + case T_DropExtensionStmt: case T_DropTableSpaceStmt: case T_RemoveFuncStmt: case T_DropRoleStmt: *************** *** 558,563 **** standard_ProcessUtility(Node *parsetree, --- 561,574 ---- } break; + case T_CreateExtensionStmt: + CreateExtension((CreateExtensionStmt *) parsetree); + break; + + case T_DropExtensionStmt: + DropExtension((DropExtensionStmt *) parsetree); + break; + case T_CreateTableSpaceStmt: PreventTransactionChain(isTopLevel, "CREATE TABLESPACE"); CreateTableSpace((CreateTableSpaceStmt *) parsetree); *************** *** 1509,1514 **** CreateCommandTag(Node *parsetree) --- 1520,1533 ---- tag = "CREATE TABLE"; break; + case T_CreateExtensionStmt: + tag = "CREATE EXTENSION"; + break; + + case T_DropExtensionStmt: + tag = "DROP EXTENSION"; + break; + case T_CreateTableSpaceStmt: tag = "CREATE TABLESPACE"; break; *************** *** 1700,1705 **** CreateCommandTag(Node *parsetree) --- 1719,1727 ---- case OBJECT_DOMAIN: tag = "ALTER DOMAIN"; break; + case OBJECT_EXTENSION: + tag = "ALTER EXTENSION"; + break; case OBJECT_OPERATOR: tag = "ALTER OPERATOR"; break; *************** *** 2319,2324 **** GetCommandLogLevel(Node *parsetree) --- 2341,2354 ---- lev = LOGSTMT_DDL; break; + case T_CreateExtensionStmt: + lev = LOGSTMT_DDL; + break; + + case T_DropExtensionStmt: + lev = LOGSTMT_DDL; + break; + case T_CreateTableSpaceStmt: lev = LOGSTMT_DDL; break; *** a/src/backend/utils/adt/genfile.c --- b/src/backend/utils/adt/genfile.c *************** *** 21,42 **** #include #include "catalog/pg_type.h" #include "funcapi.h" #include "mb/pg_wchar.h" #include "miscadmin.h" #include "postmaster/syslogger.h" #include "storage/fd.h" #include "utils/builtins.h" #include "utils/memutils.h" #include "utils/timestamp.h" - typedef struct - { - char *location; - DIR *dirdesc; - } directory_fctx; - - /* * Convert a "text" filename argument to C string, and check it's allowable. * --- 21,37 ---- #include #include "catalog/pg_type.h" + #include "commands/extension.h" #include "funcapi.h" #include "mb/pg_wchar.h" #include "miscadmin.h" #include "postmaster/syslogger.h" #include "storage/fd.h" #include "utils/builtins.h" + #include "utils/genfile.h" #include "utils/memutils.h" #include "utils/timestamp.h" /* * Convert a "text" filename argument to C string, and check it's allowable. * *************** *** 51,56 **** convert_and_check_filename(text *arg) --- 46,73 ---- filename = text_to_cstring(arg); canonicalize_path(filename); /* filename can change length here */ + /* + * When reading the SQL script for executing CREATE EXTENSION, we accept + * path in sharepath rather than in $PGDATA. + */ + if (create_extension) + { + char sharepath[MAXPGPATH]; + get_share_path(my_exec_path, sharepath); + + if (path_is_prefix_of_path(sharepath, filename)) + return filename; + else + { + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("script path not allowed"), + errdetail("Extension's script are expected in \"%s\".", + sharepath)))); + return NULL; + } + } + /* Disallow ".." in the path */ if (path_contains_parent_reference(filename)) ereport(ERROR, *** a/src/backend/utils/adt/varlena.c --- b/src/backend/utils/adt/varlena.c *************** *** 24,29 **** --- 24,30 ---- #include "miscadmin.h" #include "parser/scansup.h" #include "regex/regex.h" + #include "utils/array.h" #include "utils/builtins.h" #include "utils/bytea.h" #include "utils/lsyscache.h" *** a/src/backend/utils/init/postinit.c --- b/src/backend/utils/init/postinit.c *************** *** 29,34 **** --- 29,35 ---- #include "catalog/pg_database.h" #include "catalog/pg_db_role_setting.h" #include "catalog/pg_tablespace.h" + #include "commands/extension.h" #include "libpq/auth.h" #include "libpq/libpq-be.h" #include "mb/pg_wchar.h" *** a/src/backend/utils/misc/guc.c --- b/src/backend/utils/misc/guc.c *************** *** 128,134 **** extern char *temp_tablespaces; extern bool synchronize_seqscans; extern bool fullPageWrites; extern int ssl_renegotiation_limit; - extern char *SSLCipherSuites; #ifdef TRACE_SORT extern bool trace_sort; --- 128,133 ---- *************** *** 140,145 **** extern bool trace_syncscan; --- 139,148 ---- extern bool optimize_bounded_sort; #endif + #ifdef USE_SSL + extern char *SSLCipherSuites; + #endif + static void set_config_sourcefile(const char *name, char *sourcefile, int sourceline); *************** *** 148,161 **** static const char *assign_log_destination(const char *value, #ifdef HAVE_SYSLOG static int syslog_facility = LOG_LOCAL0; - #else - static int syslog_facility = 0; - #endif static bool assign_syslog_facility(int newval, bool doit, GucSource source); static const char *assign_syslog_ident(const char *ident, bool doit, GucSource source); static bool assign_session_replication_role(int newval, bool doit, GucSource source); --- 151,162 ---- #ifdef HAVE_SYSLOG static int syslog_facility = LOG_LOCAL0; static bool assign_syslog_facility(int newval, bool doit, GucSource source); static const char *assign_syslog_ident(const char *ident, bool doit, GucSource source); + #endif static bool assign_session_replication_role(int newval, bool doit, GucSource source); *************** *** 279,286 **** static const struct config_enum_entry session_replication_role_options[] = { {NULL, 0, false} }; - static const struct config_enum_entry syslog_facility_options[] = { #ifdef HAVE_SYSLOG {"local0", LOG_LOCAL0, false}, {"local1", LOG_LOCAL1, false}, {"local2", LOG_LOCAL2, false}, --- 280,287 ---- {NULL, 0, false} }; #ifdef HAVE_SYSLOG + static const struct config_enum_entry syslog_facility_options[] = { {"local0", LOG_LOCAL0, false}, {"local1", LOG_LOCAL1, false}, {"local2", LOG_LOCAL2, false}, *************** *** 289,299 **** static const struct config_enum_entry syslog_facility_options[] = { {"local5", LOG_LOCAL5, false}, {"local6", LOG_LOCAL6, false}, {"local7", LOG_LOCAL7, false}, - #else - {"none", 0, false}, - #endif {NULL, 0} }; static const struct config_enum_entry track_function_options[] = { {"none", TRACK_FUNC_OFF, false}, --- 290,298 ---- {"local5", LOG_LOCAL5, false}, {"local6", LOG_LOCAL6, false}, {"local7", LOG_LOCAL7, false}, {NULL, 0} }; + #endif static const struct config_enum_entry track_function_options[] = { {"none", TRACK_FUNC_OFF, false}, *************** *** 411,417 **** int tcp_keepalives_count; --- 410,418 ---- */ static char *log_destination_string; + #ifdef HAVE_SYSLOG static char *syslog_ident_str; + #endif static bool phony_autocommit; static bool session_auth_is_superuser; static double phony_random_seed; *************** *** 2530,2535 **** static struct config_string ConfigureNamesString[] = --- 2531,2537 ---- "postgresql-%Y-%m-%d_%H%M%S.log", NULL, NULL }, + #ifdef HAVE_SYSLOG { {"syslog_ident", PGC_SIGHUP, LOGGING_WHERE, gettext_noop("Sets the program name used to identify PostgreSQL " *************** *** 2539,2544 **** static struct config_string ConfigureNamesString[] = --- 2541,2547 ---- &syslog_ident_str, "postgres", assign_syslog_ident, NULL }, + #endif { {"TimeZone", PGC_USERSET, CLIENT_CONN_LOCALE, *************** *** 2677,2682 **** static struct config_string ConfigureNamesString[] = --- 2680,2686 ---- "pg_catalog.simple", assignTSCurrentConfig, NULL }, + #ifdef USE_SSL { {"ssl_ciphers", PGC_POSTMASTER, CONN_AUTH_SECURITY, gettext_noop("Sets the list of allowed SSL ciphers."), *************** *** 2684,2696 **** static struct config_string ConfigureNamesString[] = GUC_SUPERUSER_ONLY }, &SSLCipherSuites, ! #ifdef USE_SSL ! "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH", ! #else ! "none", ! #endif ! NULL, NULL }, { {"application_name", PGC_USERSET, LOGGING_WHAT, --- 2688,2696 ---- GUC_SUPERUSER_ONLY }, &SSLCipherSuites, ! "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH", NULL, NULL }, + #endif /* USE_SSL */ { {"application_name", PGC_USERSET, LOGGING_WHAT, *************** *** 2807,2825 **** static struct config_enum ConfigureNamesEnum[] = LOGSTMT_NONE, log_statement_options, NULL, NULL }, { {"syslog_facility", PGC_SIGHUP, LOGGING_WHERE, gettext_noop("Sets the syslog \"facility\" to be used when syslog enabled."), NULL }, &syslog_facility, ! #ifdef HAVE_SYSLOG ! LOG_LOCAL0, ! #else ! 0, ! #endif ! syslog_facility_options, assign_syslog_facility, NULL }, { {"session_replication_role", PGC_SUSET, CLIENT_CONN_STATEMENT, --- 2807,2822 ---- LOGSTMT_NONE, log_statement_options, NULL, NULL }, + #ifdef HAVE_SYSLOG { {"syslog_facility", PGC_SIGHUP, LOGGING_WHERE, gettext_noop("Sets the syslog \"facility\" to be used when syslog enabled."), NULL }, &syslog_facility, ! LOG_LOCAL0, syslog_facility_options, assign_syslog_facility, NULL }, + #endif { {"session_replication_role", PGC_SUSET, CLIENT_CONN_STATEMENT, *************** *** 7640,7654 **** assign_log_destination(const char *value, bool doit, GucSource source) return value; } static bool assign_syslog_facility(int newval, bool doit, GucSource source) { - #ifdef HAVE_SYSLOG if (doit) set_syslog_parameters(syslog_ident_str ? syslog_ident_str : "postgres", newval); - #endif - /* Without syslog support, just ignore it */ return true; } --- 7637,7650 ---- return value; } + #ifdef HAVE_SYSLOG + static bool assign_syslog_facility(int newval, bool doit, GucSource source) { if (doit) set_syslog_parameters(syslog_ident_str ? syslog_ident_str : "postgres", newval); return true; } *************** *** 7656,7669 **** assign_syslog_facility(int newval, bool doit, GucSource source) static const char * assign_syslog_ident(const char *ident, bool doit, GucSource source) { - #ifdef HAVE_SYSLOG if (doit) set_syslog_parameters(ident, syslog_facility); - #endif - /* Without syslog support, it will always be set to "none", so ignore */ return ident; } static bool --- 7652,7663 ---- static const char * assign_syslog_ident(const char *ident, bool doit, GucSource source) { if (doit) set_syslog_parameters(ident, syslog_facility); return ident; } + #endif /* HAVE_SYSLOG */ static bool *** a/src/bin/pg_dump/common.c --- b/src/bin/pg_dump/common.c *************** *** 78,83 **** static int strInArray(const char *pattern, char **arr, int arr_size); --- 78,84 ---- TableInfo * getSchemaData(int *numTablesPtr) { + ExtensionInfo *extinfo; NamespaceInfo *nsinfo; AggInfo *agginfo; InhInfo *inhinfo; *************** *** 94,99 **** getSchemaData(int *numTablesPtr) --- 95,101 ---- FdwInfo *fdwinfo; ForeignServerInfo *srvinfo; DefaultACLInfo *daclinfo; + int numExtensions; int numNamespaces; int numAggregates; int numInherits; *************** *** 112,117 **** getSchemaData(int *numTablesPtr) --- 114,123 ---- int numDefaultACLs; if (g_verbose) + write_msg(NULL, "reading extensions\n"); + extinfo = getExtensions(&numExtensions); + + if (g_verbose) write_msg(NULL, "reading schemas\n"); nsinfo = getNamespaces(&numNamespaces); *** a/src/bin/pg_dump/pg_dump.c --- b/src/bin/pg_dump/pg_dump.c *************** *** 157,162 **** static int findSecLabels(Archive *fout, Oid classoid, Oid objoid, --- 157,163 ---- SecLabelItem **items); static int collectSecLabels(Archive *fout, SecLabelItem **items); static void dumpDumpableObject(Archive *fout, DumpableObject *dobj); + static void dumpExtension(Archive *fout, ExtensionInfo *extinfo); static void dumpNamespace(Archive *fout, NamespaceInfo *nspinfo); static void dumpType(Archive *fout, TypeInfo *tyinfo); static void dumpBaseType(Archive *fout, TypeInfo *tyinfo); *************** *** 2376,2381 **** binary_upgrade_set_relfilenodes(PQExpBuffer upgrade_buffer, Oid pg_class_oid, --- 2377,2463 ---- } /* + * getExtensions: + * read all extensions in the system catalogs and return them in the + * ExtensionInfo* structure + * + * numExtensions is set to the number of extensions read in + */ + ExtensionInfo * + getExtensions(int *numExtensions) + { + PGresult *res; + int ntups; + int i; + PQExpBuffer query; + ExtensionInfo *extinfo = NULL; + int i_tableoid; + int i_oid; + int i_extname; + int i_extversion; + int i_extcclass; + int i_nspname; + + /* + * Before 9.1, there are no extensions. + */ + if (g_fout->remoteVersion < 90100) + { + *numExtensions = 0; + return extinfo; + } + + query = createPQExpBuffer(); + + /* Make sure we are in proper schema */ + selectSourceSchema("pg_catalog"); + + /* + * we fetch all namespaces including system ones, so that every object we + * read in can be linked to a containing namespace. + */ + appendPQExpBuffer(query, + "SELECT x.tableoid, x.oid, " + "x.extname, x.extversion, x.custom_class, n.nspname " + "FROM pg_extension x " + "JOIN pg_depend d ON d.objid = x.oid " + "and d.refclassid = 'pg_catalog.pg_namespace'::regclass " + "JOIN pg_namespace n ON d.refobjid = n.oid"); + + res = PQexec(g_conn, query->data); + check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + + extinfo = (ExtensionInfo *) malloc(ntups * sizeof(ExtensionInfo)); + + i_tableoid = PQfnumber(res, "tableoid"); + i_oid = PQfnumber(res, "oid"); + i_extname = PQfnumber(res, "extname"); + i_extversion = PQfnumber(res, "extversion"); + i_extcclass = PQfnumber(res, "custom_class"); + i_nspname = PQfnumber(res, "nspname"); + + for (i = 0; i < ntups; i++) + { + extinfo[i].dobj.objType = DO_EXTENSION; + extinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid)); + extinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid)); + AssignDumpId(&extinfo[i].dobj); + extinfo[i].dobj.name = strdup(PQgetvalue(res, i, i_extname)); + extinfo[i].extversion = strdup(PQgetvalue(res, i, i_extversion)); + extinfo[i].custom_class = strdup(PQgetvalue(res, i, i_extcclass)); + extinfo[i].namespace = strdup(PQgetvalue(res, i, i_nspname)); + } + + PQclear(res); + destroyPQExpBuffer(query); + + *numExtensions = ntups; + return extinfo; + } + + /* * getNamespaces: * read all namespaces in the system catalogs and return them in the * NamespaceInfo* structure *************** *** 2439,2448 **** getNamespaces(int *numNamespaces) * we fetch all namespaces including system ones, so that every object we * read in can be linked to a containing namespace. */ ! appendPQExpBuffer(query, "SELECT tableoid, oid, nspname, " ! "(%s nspowner) AS rolname, " ! "nspacl FROM pg_namespace", ! username_subquery); res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); --- 2521,2577 ---- * we fetch all namespaces including system ones, so that every object we * read in can be linked to a containing namespace. */ ! if (g_fout->remoteVersion >= 90100) ! { ! /* ! * So we want the namespaces, but we want to filter out any ! * namespace created by an extension's script. That's unless the ! * user went over his head and created objects into the extension's ! * schema: we now want the schema not to be filtered out to avoid: ! * ! * pg_dump: schema with OID 77869 does not exist ! */ ! appendPQExpBuffer(query, "SELECT n.tableoid, n.oid, n.nspname, " ! "(%s nspowner) AS rolname, " ! "n.nspacl " ! " FROM pg_namespace n " ! " WHERE n.nspname != 'information_schema' " ! " AND CASE WHEN (SELECT count(*) " ! " FROM pg_depend " ! " WHERE refobjid = n.oid and deptype != 'p') > 0 " ! " THEN EXISTS( " ! "WITH RECURSIVE depends AS ( " ! " select n.oid as nsp, objid, refobjid, array[refobjid] as deps " ! " from pg_depend " ! " where refobjid = n.oid and deptype != 'p' " ! " UNION ALL " ! " select p.nsp, p.objid, d.refobjid, deps || d.refobjid " ! " from pg_depend d JOIN depends p ON d.objid = p.objid " ! " where d.deptype != 'p' and not d.refobjid = any(deps) " ! " and not (d.refclassid = 'pg_extension'::regclass and d.deptype = 'n') " ! ") " ! " SELECT nsp, objid, array_agg(distinct refobjid) " ! " FROM depends " ! "GROUP BY nsp, objid " ! " HAVING NOT array_agg(distinct refobjid) && array(select oid from pg_extension) " ! ") " ! " ELSE true " ! " END " ! "UNION ALL " ! "SELECT n.tableoid, n.oid, n.nspname, " ! "(%s nspowner) AS rolname, " ! "nspacl FROM pg_namespace n " ! "WHERE n.nspname = 'information_schema'", ! username_subquery, ! username_subquery); ! } ! else ! { ! appendPQExpBuffer(query, "SELECT tableoid, oid, nspname, " ! "(%s nspowner) AS rolname, " ! "nspacl FROM pg_namespace", ! username_subquery); ! } res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); *************** *** 2506,2512 **** findNamespace(Oid nsoid, Oid objoid) if (nsoid == nsinfo->dobj.catId.oid) return nsinfo; } ! write_msg(NULL, "schema with OID %u does not exist\n", nsoid); exit_nicely(); } else --- 2635,2642 ---- if (nsoid == nsinfo->dobj.catId.oid) return nsinfo; } ! write_msg(NULL, "schema with OID %u does not exist, " ! "but is needed for object %u\n", nsoid, objoid); exit_nicely(); } else *************** *** 2559,2565 **** getTypes(int *numTypes) * we include even the built-in types because those may be used as array * elements by user-defined types * ! * we filter out the built-in types when we dump out the types * * same approach for undefined (shell) types and array types * --- 2689,2696 ---- * we include even the built-in types because those may be used as array * elements by user-defined types * ! * we filter out the built-in types when we dump out the types, and from ! * 9.1 we also filter out types that depend on an extension * * same approach for undefined (shell) types and array types * *************** *** 2574,2580 **** getTypes(int *numTypes) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 80300) { appendPQExpBuffer(query, "SELECT tableoid, oid, typname, " "typnamespace, " --- 2705,2730 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT t.tableoid, t.oid, t.typname, " ! "t.typnamespace, " ! "(%s typowner) AS rolname, " ! "t.typinput::oid AS typinput, " ! "t.typoutput::oid AS typoutput, t.typelem, t.typrelid, " ! "CASE WHEN t.typrelid = 0 THEN ' '::\"char\" " ! "ELSE (SELECT relkind FROM pg_class WHERE oid = t.typrelid) END AS typrelkind, " ! "t.typtype, t.typisdefined, " ! "t.typname[0] = '_' AND t.typelem != 0 AND " ! "(SELECT typarray FROM pg_type te WHERE oid = t.typelem) = t.oid AS isarray " ! "FROM pg_type t " ! "LEFT JOIN pg_depend d ON d.objid = t.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL", ! username_subquery); ! } ! else if (g_fout->remoteVersion >= 80300) { appendPQExpBuffer(query, "SELECT tableoid, oid, typname, " "typnamespace, " *************** *** 2806,2812 **** getOperators(int *numOprs) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 70300) { appendPQExpBuffer(query, "SELECT tableoid, oid, oprname, " "oprnamespace, " --- 2956,2975 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT o.tableoid, o.oid, o.oprname, " ! "o.oprnamespace, " ! "(%s oprowner) AS rolname, " ! "o.oprcode::oid AS oprcode " ! "FROM pg_operator o " ! "LEFT JOIN pg_depend d ON d.objid = o.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL", ! username_subquery); ! } ! else if (g_fout->remoteVersion >= 70300) { appendPQExpBuffer(query, "SELECT tableoid, oid, oprname, " "oprnamespace, " *************** *** 2985,2991 **** getOpclasses(int *numOpclasses) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 70300) { appendPQExpBuffer(query, "SELECT tableoid, oid, opcname, " "opcnamespace, " --- 3148,3166 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT o.tableoid, o.oid, o.opcname, " ! "o.opcnamespace, " ! "(%s opcowner) AS rolname " ! "FROM pg_opclass o " ! "LEFT JOIN pg_depend d ON d.objid = o.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL", ! username_subquery); ! } ! else if (g_fout->remoteVersion >= 70300) { appendPQExpBuffer(query, "SELECT tableoid, oid, opcname, " "opcnamespace, " *************** *** 3091,3101 **** getOpfamilies(int *numOpfamilies) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! appendPQExpBuffer(query, "SELECT tableoid, oid, opfname, " ! "opfnamespace, " ! "(%s opfowner) AS rolname " ! "FROM pg_opfamily", ! username_subquery); res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); --- 3266,3291 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT o.tableoid, o.oid, o.opfname, " ! "o.opfnamespace, " ! "(%s opfowner) AS rolname " ! "FROM pg_opfamily o " ! "LEFT JOIN pg_depend d ON d.objid = o.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL", ! username_subquery); ! } ! else ! { ! appendPQExpBuffer(query, "SELECT tableoid, oid, opfname, " ! "opfnamespace, " ! "(%s opfowner) AS rolname " ! "FROM pg_opfamily", ! username_subquery); ! } res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); *************** *** 3169,3175 **** getAggregates(int *numAggs) /* find all user-defined aggregates */ ! if (g_fout->remoteVersion >= 80200) { appendPQExpBuffer(query, "SELECT tableoid, oid, proname AS aggname, " "pronamespace AS aggnamespace, " --- 3359,3381 ---- /* find all user-defined aggregates */ ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT a.tableoid, a.oid, a.proname AS aggname, " ! "a.pronamespace AS aggnamespace, " ! "a.pronargs, a.proargtypes, " ! "(%s proowner) AS rolname, " ! "a.proacl AS aggacl " ! "FROM pg_proc a " ! "LEFT JOIN pg_depend d ON d.objid = a.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL AND proisagg " ! "AND pronamespace != " ! "(SELECT oid FROM pg_namespace WHERE nspname = 'pg_catalog')", ! username_subquery); ! } ! else if (g_fout->remoteVersion >= 80200) { appendPQExpBuffer(query, "SELECT tableoid, oid, proname AS aggname, " "pronamespace AS aggnamespace, " *************** *** 3315,3321 **** getFuncs(int *numFuncs) /* find all user-defined funcs */ ! if (g_fout->remoteVersion >= 70300) { appendPQExpBuffer(query, "SELECT tableoid, oid, proname, prolang, " --- 3521,3544 ---- /* find all user-defined funcs */ ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, ! "SELECT f.tableoid, f.oid, f.proname, f.prolang, " ! "f.pronargs, f.proargtypes, f.prorettype, f.proacl, " ! "f.pronamespace, " ! "(%s proowner) AS rolname " ! "FROM pg_proc f " ! "LEFT JOIN pg_depend d ON d.objid = f.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL AND NOT proisagg " ! "AND pronamespace != " ! "(SELECT oid FROM pg_namespace " ! "WHERE nspname = 'pg_catalog')", ! username_subquery); ! } ! else if (g_fout->remoteVersion >= 70300) { appendPQExpBuffer(query, "SELECT tableoid, oid, proname, prolang, " *************** *** 3477,3483 **** getTables(int *numTables) * we cannot correctly identify inherited columns, owned sequences, etc. */ ! if (g_fout->remoteVersion >= 90000) { /* * Left join to pick up dependency info linking sequences to their --- 3700,3743 ---- * we cannot correctly identify inherited columns, owned sequences, etc. */ ! if (g_fout->remoteVersion >= 90100) ! { ! /* ! * Left join to pick up dependency info linking sequences to their ! * owning column, if any (note this dependency is AUTO as of 8.2) ! */ ! appendPQExpBuffer(query, ! "SELECT c.tableoid, c.oid, c.relname, " ! "c.relacl, c.relkind, c.relnamespace, " ! "(%s c.relowner) AS rolname, " ! "c.relchecks, c.relhastriggers, " ! "c.relhasindex, c.relhasrules, c.relhasoids, " ! "c.relfrozenxid, " ! "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, " ! "d.refobjid AS owning_tab, " ! "d.refobjsubid AS owning_col, " ! "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, " ! "array_to_string(c.reloptions, ', ') AS reloptions, " ! "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions " ! "FROM pg_class c " ! "LEFT JOIN pg_depend d ON " ! "(c.relkind = '%c' AND " ! "d.classid = c.tableoid AND d.objid = c.oid AND " ! "d.objsubid = 0 AND " ! "d.refclassid = c.tableoid AND d.deptype = 'a') " ! "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) " ! "LEFT JOIN pg_depend dx ON dx.objid = c.oid and dx.deptype = 'i' " ! "and dx.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON dx.refobjid = x.oid " ! "WHERE x.oid IS NULL " ! "AND c.relkind in ('%c', '%c', '%c', '%c') " ! "ORDER BY c.oid", ! username_subquery, ! RELKIND_SEQUENCE, ! RELKIND_RELATION, RELKIND_SEQUENCE, ! RELKIND_VIEW, RELKIND_COMPOSITE_TYPE); ! } ! else if (g_fout->remoteVersion >= 90000) { /* * Left join to pick up dependency info linking sequences to their *************** *** 4477,4483 **** getRules(int *numRules) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 80300) { appendPQExpBuffer(query, "SELECT " "tableoid, oid, rulename, " --- 4737,4756 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT " ! "r.tableoid, r.oid, r.rulename, " ! "r.ev_class AS ruletable, r.ev_type, r.is_instead, " ! "r.ev_enabled " ! "FROM pg_rewrite r " ! "LEFT JOIN pg_depend d ON d.objid = r.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL " ! "ORDER BY oid"); ! } ! else if (g_fout->remoteVersion >= 80300) { appendPQExpBuffer(query, "SELECT " "tableoid, oid, rulename, " *************** *** 4838,4844 **** getProcLangs(int *numProcLangs) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90000) { /* pg_language has a laninline column */ appendPQExpBuffer(query, "SELECT tableoid, oid, " --- 5111,5132 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! /* pg_language has a laninline column */ ! appendPQExpBuffer(query, "SELECT l.tableoid, l.oid, " ! "l.lanname, l.lanpltrusted, l.lanplcallfoid, " ! "l.laninline, l.lanvalidator, l.lanacl, " ! "(%s lanowner) AS lanowner " ! "FROM pg_language l " ! "LEFT JOIN pg_depend d ON d.objid = l.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL and lanispl " ! "ORDER BY oid", ! username_subquery); ! } ! else if (g_fout->remoteVersion >= 90000) { /* pg_language has a laninline column */ appendPQExpBuffer(query, "SELECT tableoid, oid, " *************** *** 4992,4998 **** getCasts(int *numCasts) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 80400) { appendPQExpBuffer(query, "SELECT tableoid, oid, " "castsource, casttarget, castfunc, castcontext, " --- 5280,5298 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT c.tableoid, c.oid, " ! "c.castsource, c.casttarget, c.castfunc, c.castcontext, " ! "c.castmethod " ! "FROM pg_cast c " ! "LEFT JOIN pg_depend d ON d.objid = c.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL " ! "ORDER BY 3,4"); ! } ! else if (g_fout->remoteVersion >= 80400) { appendPQExpBuffer(query, "SELECT tableoid, oid, " "castsource, casttarget, castfunc, castcontext, " *************** *** 5586,5595 **** getTSParsers(int *numTSParsers) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! appendPQExpBuffer(query, "SELECT tableoid, oid, prsname, prsnamespace, " ! "prsstart::oid, prstoken::oid, " ! "prsend::oid, prsheadline::oid, prslextype::oid " ! "FROM pg_ts_parser"); res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); --- 5886,5909 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT p.tableoid, p.oid, p.prsname, p.prsnamespace, " ! "p.prsstart::oid, p.prstoken::oid, " ! "p.prsend::oid, p.prsheadline::oid, p.prslextype::oid " ! "FROM pg_ts_parser p " ! "LEFT JOIN pg_depend d ON d.objid = p.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL"); ! } ! else ! { ! appendPQExpBuffer(query, "SELECT tableoid, oid, prsname, prsnamespace, " ! "prsstart::oid, prstoken::oid, " ! "prsend::oid, prsheadline::oid, prslextype::oid " ! "FROM pg_ts_parser"); ! } res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); *************** *** 5668,5678 **** getTSDictionaries(int *numTSDicts) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! appendPQExpBuffer(query, "SELECT tableoid, oid, dictname, " ! "dictnamespace, (%s dictowner) AS rolname, " ! "dicttemplate, dictinitoption " ! "FROM pg_ts_dict", ! username_subquery); res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); --- 5982,6007 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT tsd.tableoid, tsd.oid, tsd.dictname, " ! "tsd.dictnamespace, (%s dictowner) AS rolname, " ! "tsd.dicttemplate, tsd.dictinitoption " ! "FROM pg_ts_dict tsd " ! "LEFT JOIN pg_depend d ON d.objid = tsd.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL", ! username_subquery); ! } ! else ! { ! appendPQExpBuffer(query, "SELECT tableoid, oid, dictname, " ! "dictnamespace, (%s dictowner) AS rolname, " ! "dicttemplate, dictinitoption " ! "FROM pg_ts_dict", ! username_subquery); ! } res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); *************** *** 5749,5757 **** getTSTemplates(int *numTSTemplates) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! appendPQExpBuffer(query, "SELECT tableoid, oid, tmplname, " ! "tmplnamespace, tmplinit::oid, tmpllexize::oid " ! "FROM pg_ts_template"); res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); --- 6078,6099 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT t.tableoid, t.oid, t.tmplname, " ! "t.tmplnamespace, t.tmplinit::oid, t.tmpllexize::oid " ! "FROM pg_ts_template t " ! "LEFT JOIN pg_depend d ON d.objid = t.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL"); ! } ! else ! { ! appendPQExpBuffer(query, "SELECT tableoid, oid, tmplname, " ! "tmplnamespace, tmplinit::oid, tmpllexize::oid " ! "FROM pg_ts_template"); ! } res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); *************** *** 5823,5832 **** getTSConfigurations(int *numTSConfigs) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! appendPQExpBuffer(query, "SELECT tableoid, oid, cfgname, " ! "cfgnamespace, (%s cfgowner) AS rolname, cfgparser " ! "FROM pg_ts_config", ! username_subquery); res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); --- 6165,6188 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT c.tableoid, c.oid, c.cfgname, " ! "c.cfgnamespace, (%s cfgowner) AS rolname, c.cfgparser " ! "FROM pg_ts_config c " ! "LEFT JOIN pg_depend d ON d.objid = c.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL", ! username_subquery); ! } ! else ! { ! appendPQExpBuffer(query, "SELECT tableoid, oid, cfgname, " ! "cfgnamespace, (%s cfgowner) AS rolname, cfgparser " ! "FROM pg_ts_config", ! username_subquery); ! } res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); *************** *** 5899,5911 **** getForeignDataWrappers(int *numForeignDataWrappers) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! appendPQExpBuffer(query, "SELECT tableoid, oid, fdwname, " ! "(%s fdwowner) AS rolname, fdwvalidator::pg_catalog.regproc, fdwacl," ! "array_to_string(ARRAY(" ! " SELECT option_name || ' ' || quote_literal(option_value) " ! " FROM pg_options_to_table(fdwoptions)), ', ') AS fdwoptions " ! "FROM pg_foreign_data_wrapper", ! username_subquery); res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); --- 6255,6284 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT fdw.tableoid, fdw.oid, fdw.fdwname, " ! "(%s fdwowner) AS rolname, fdw.fdwvalidator::pg_catalog.regproc, fdwacl," ! "array_to_string(ARRAY(" ! " SELECT option_name || ' ' || quote_literal(option_value) " ! " FROM pg_options_to_table(fdw.fdwoptions)), ', ') AS fdwoptions " ! "FROM pg_foreign_data_wrapper fdw " ! "LEFT JOIN pg_depend d ON d.objid = fdw.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL", ! username_subquery); ! } ! else ! { ! appendPQExpBuffer(query, "SELECT tableoid, oid, fdwname, " ! "(%s fdwowner) AS rolname, fdwvalidator::pg_catalog.regproc, fdwacl," ! "array_to_string(ARRAY(" ! " SELECT option_name || ' ' || quote_literal(option_value) " ! " FROM pg_options_to_table(fdwoptions)), ', ') AS fdwoptions " ! "FROM pg_foreign_data_wrapper", ! username_subquery); ! } res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); *************** *** 5983,5996 **** getForeignServers(int *numForeignServers) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! appendPQExpBuffer(query, "SELECT tableoid, oid, srvname, " ! "(%s srvowner) AS rolname, " ! "srvfdw, srvtype, srvversion, srvacl," ! "array_to_string(ARRAY(" ! " SELECT option_name || ' ' || quote_literal(option_value) " ! " FROM pg_options_to_table(srvoptions)), ', ') AS srvoptions " ! "FROM pg_foreign_server", ! username_subquery); res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); --- 6356,6387 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT fs.tableoid, fs.oid, fs.srvname, " ! "(%s srvowner) AS rolname, " ! "fs.srvfdw, fs.srvtype, fs.srvversion, fs.srvacl," ! "array_to_string(ARRAY(" ! " SELECT option_name || ' ' || quote_literal(option_value) " ! " FROM pg_options_to_table(fs.srvoptions)), ', ') AS srvoptions " ! "FROM pg_foreign_server fs " ! "LEFT JOIN pg_depend d ON d.objid = fs.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL", ! username_subquery); ! } ! else ! { ! appendPQExpBuffer(query, "SELECT tableoid, oid, srvname, " ! "(%s srvowner) AS rolname, " ! "srvfdw, srvtype, srvversion, srvacl," ! "array_to_string(ARRAY(" ! " SELECT option_name || ' ' || quote_literal(option_value) " ! " FROM pg_options_to_table(srvoptions)), ', ') AS srvoptions " ! "FROM pg_foreign_server", ! username_subquery); ! } res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); *************** *** 6467,6472 **** dumpDumpableObject(Archive *fout, DumpableObject *dobj) --- 6858,6866 ---- { switch (dobj->objType) { + case DO_EXTENSION: + dumpExtension(fout, (ExtensionInfo *) dobj); + break; case DO_NAMESPACE: dumpNamespace(fout, (NamespaceInfo *) dobj); break; *************** *** 6563,6568 **** dumpDumpableObject(Archive *fout, DumpableObject *dobj) --- 6957,7009 ---- } /* + * dumpExtension + * writes out to fout the queries to recreate an extension + */ + static void + dumpExtension(Archive *fout, ExtensionInfo *extinfo) + { + PQExpBuffer q; + PQExpBuffer delq; + char *qextname; + + /* Skip if not to be dumped */ + if (!extinfo->dobj.dump || dataOnly) + return; + + q = createPQExpBuffer(); + delq = createPQExpBuffer(); + + qextname = strdup(fmtId(extinfo->dobj.name)); + + appendPQExpBuffer(delq, "DROP EXTENSION %s;\n", qextname); + + appendPQExpBuffer(q, "CREATE EXTENSION %s WITH SCHEMA %s NO USER DATA ;\n", + qextname, strdup(extinfo->namespace)); + + ArchiveEntry(fout, extinfo->dobj.catId, extinfo->dobj.dumpId, + extinfo->dobj.name, + NULL, NULL, + "", + false, "EXTENSION", SECTION_PRE_DATA, + q->data, delq->data, NULL, + extinfo->dobj.dependencies, extinfo->dobj.nDeps, + NULL, NULL); + + /* Dump Extension Comments */ + resetPQExpBuffer(q); + appendPQExpBuffer(q, "EXTENSION %s", qextname); + dumpComment(fout, q->data, + NULL, "", + extinfo->dobj.catId, 0, extinfo->dobj.dumpId); + + free(qextname); + + destroyPQExpBuffer(q); + destroyPQExpBuffer(delq); + } + + /* * dumpNamespace * writes out to fout the queries to recreate a user-defined namespace */ *** a/src/bin/pg_dump/pg_dump.h --- b/src/bin/pg_dump/pg_dump.h *************** *** 88,93 **** typedef struct SimpleStringList --- 88,94 ---- typedef enum { /* When modifying this enum, update priority tables in pg_dump_sort.c! */ + DO_EXTENSION, DO_NAMESPACE, DO_TYPE, DO_SHELL_TYPE, *************** *** 132,137 **** typedef struct _dumpableObject --- 133,147 ---- int allocDeps; /* allocated size of dependencies[] */ } DumpableObject; + typedef struct _extensionInfo + { + DumpableObject dobj; + char *extname; + char *extversion; + char *custom_class; + char *namespace; + } ExtensionInfo; + typedef struct _namespaceInfo { DumpableObject dobj; *************** *** 511,516 **** extern void sortDumpableObjectsByTypeOid(DumpableObject **objs, int numObjs); --- 521,527 ---- /* * version specific routines */ + extern ExtensionInfo *getExtensions(int *numExtensions); extern NamespaceInfo *getNamespaces(int *numNamespaces); extern TypeInfo *getTypes(int *numTypes); extern FuncInfo *getFuncs(int *numFuncs); *** a/src/bin/pg_dump/pg_dump_sort.c --- b/src/bin/pg_dump/pg_dump_sort.c *************** *** 28,33 **** static const char *modulename = gettext_noop("sorter"); --- 28,34 ---- */ static const int oldObjectTypePriority[] = { + 1, /* DO_EXTENSION */ 1, /* DO_NAMESPACE */ 2, /* DO_TYPE */ 2, /* DO_SHELL_TYPE */ *************** *** 65,70 **** static const int oldObjectTypePriority[] = --- 66,72 ---- */ static const int newObjectTypePriority[] = { + 1, /* DO_EXTENSION */ 1, /* DO_NAMESPACE */ 3, /* DO_TYPE */ 3, /* DO_SHELL_TYPE */ *************** *** 1018,1023 **** describeDumpableObject(DumpableObject *obj, char *buf, int bufsize) --- 1020,1030 ---- { switch (obj->objType) { + case DO_EXTENSION: + snprintf(buf, bufsize, + "EXTENSION %s (ID %d OID %u)", + obj->name, obj->dumpId, obj->catId.oid); + return; case DO_NAMESPACE: snprintf(buf, bufsize, "SCHEMA %s (ID %d OID %u)", *** a/src/bin/psql/command.c --- b/src/bin/psql/command.c *************** *** 488,493 **** exec_command(const char *cmd, --- 488,503 ---- break; } break; + case 'x': /* Installed Extensions */ + if (pattern) + success = describeExtension(pattern, show_verbose); + else + success = listExtensions(show_verbose); + break; + break; + case 'X': /* Available Extensions */ + success = listAvailableExtensions(pattern); + break; default: status = PSQL_CMD_UNKNOWN; } *** a/src/bin/psql/describe.c --- b/src/bin/psql/describe.c *************** *** 38,43 **** static bool describeOneTSConfig(const char *oid, const char *nspname, --- 38,45 ---- const char *cfgname, const char *pnspname, const char *prsname); static void printACLColumn(PQExpBuffer buf, const char *colname); + static bool describeOneExtension(const char *extname, + const char *oid, bool verbose); /*---------------- *************** *** 2748,2753 **** listSchemas(const char *pattern, bool verbose, bool showSystem) --- 2750,2954 ---- return true; } + /* + * \dx + * + * Describes installed extensions + */ + bool + listExtensions(bool verbose) + { + PQExpBufferData buf; + PGresult *res; + printQueryOpt myopt = pset.popt; + + initPQExpBuffer(&buf); + printfPQExpBuffer(&buf, + "SELECT n.nspname as \"%s\", e.extname AS \"%s\", " + "e.extversion AS \"%s\", c.description as \"%s\" " + "FROM pg_catalog.pg_extension e " + "LEFT JOIN pg_catalog.pg_depend d ""ON d.objid = e.oid " + "AND d.refclassid = 'pg_catalog.pg_namespace'::regclass " + "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.refobjid " + "JOIN pg_catalog.pg_description c ON c.objoid = e.oid " + "AND c.classoid = 'pg_catalog.pg_extension'::regclass ", + gettext_noop("Schema"), + gettext_noop("Name"), + gettext_noop("Version"), + gettext_noop("Description")); + + appendPQExpBuffer(&buf, "ORDER BY 1, 2;"); + + res = PSQLexec(buf.data, false); + termPQExpBuffer(&buf); + if (!res) + return false; + + myopt.nullPrint = NULL; + myopt.title = _("List of extensions"); + myopt.translate_header = true; + + printQuery(res, &myopt, pset.queryFout, pset.logfile); + + PQclear(res); + return true; + } + + /* + * \dx pattern + */ + bool + describeExtension(const char *pattern, bool verbose) + { + PQExpBufferData buf; + PGresult *res; + int i; + + initPQExpBuffer(&buf); + + printfPQExpBuffer(&buf, + "SELECT e.oid,\n" + " e.extname\n" + "FROM pg_catalog.pg_extension e\n"); + + processSQLNamePattern(pset.db, &buf, pattern, false, false, + NULL, "e.extname", NULL, + NULL); + + appendPQExpBuffer(&buf, "ORDER BY 2;"); + + res = PSQLexec(buf.data, false); + termPQExpBuffer(&buf); + if (!res) + return false; + + if (PQntuples(res) == 0) + { + if (!pset.quiet) + fprintf(stderr, _("Did not find any extension named \"%s\".\n"), + pattern); + PQclear(res); + return false; + } + + for (i = 0; i < PQntuples(res); i++) + { + const char *oid; + const char *extname; + + oid = PQgetvalue(res, i, 0); + extname = PQgetvalue(res, i, 1); + + if (!describeOneExtension(extname, oid, verbose)) + { + PQclear(res); + return false; + } + if (cancel_pressed) + { + PQclear(res); + return false; + } + } + + PQclear(res); + return true; + } + + bool + describeOneExtension(const char *extname, const char *oid, bool verbose) + { + PQExpBufferData buf; + PGresult *res; + char title[1024]; + printQueryOpt myopt = pset.popt; + + initPQExpBuffer(&buf); + if (verbose) + { + printfPQExpBuffer(&buf, + "SELECT x.objid as \"%s\", x.classid as \"%s\", " + "x.class as \"%s\", x.objdesc as \"%s\" " + " FROM pg_extension_objects('%s') x", + gettext_noop("Object OID"), + gettext_noop("Object ClassOID"), + gettext_noop("Object Class"), + gettext_noop("Object Description"), + extname); + } + else + { + printfPQExpBuffer(&buf, + "SELECT x.class as \"%s\", x.objdesc as \"%s\" " + " FROM pg_extension_objects('%s') x", + gettext_noop("Object Class"), + gettext_noop("Object Description"), + extname); + } + + res = PSQLexec(buf.data, false); + termPQExpBuffer(&buf); + if (!res) + return false; + + myopt.nullPrint = NULL; + sprintf(title, _("Objects in extension \"%s\""), extname); + myopt.title = title; + myopt.translate_header = true; + + printQuery(res, &myopt, pset.queryFout, pset.logfile); + + PQclear(res); + return true; + } + + /* + * \dX + * + * Describes available and installed extensions + */ + bool + listAvailableExtensions(const char *pattern) + { + PQExpBufferData buf; + PGresult *res; + printQueryOpt myopt = pset.popt; + + initPQExpBuffer(&buf); + printfPQExpBuffer(&buf, + "SELECT n.nspname as \"%s\", x.name AS \"%s\", " + "x.version AS \"%s\", x.comment as \"%s\" " + "FROM pg_catalog.pg_available_extensions x " + "LEFT JOIN pg_catalog.pg_extension e ON e.extname = x.name " + "LEFT JOIN pg_catalog.pg_depend d ON d.objid = e.oid " + "AND d.refclassid = 'pg_catalog.pg_namespace'::regclass " + "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.refobjid ", + gettext_noop("Schema"), + gettext_noop("Name"), + gettext_noop("Version"), + gettext_noop("Description")); + + processSQLNamePattern(pset.db, &buf, pattern, false, false, + NULL, "x.extname", NULL, + NULL); + + appendPQExpBuffer(&buf, "ORDER BY 2;"); + + res = PSQLexec(buf.data, false); + termPQExpBuffer(&buf); + if (!res) + return false; + + myopt.nullPrint = NULL; + myopt.title = _("List of extensions"); + myopt.translate_header = true; + + printQuery(res, &myopt, pset.queryFout, pset.logfile); + + PQclear(res); + return true; + } + /* * \dFp *** a/src/bin/psql/describe.h --- b/src/bin/psql/describe.h *************** *** 54,59 **** extern bool listTSDictionaries(const char *pattern, bool verbose); --- 54,68 ---- /* \dFt */ extern bool listTSTemplates(const char *pattern, bool verbose); + /* \dx */ + bool listExtensions(bool verbose); + + /* \dx PATTERN */ + bool describeExtension(const char *pattern, bool verbose); + + /* \dX */ + bool listAvailableExtensions(const char *pattern); + /* \l */ extern bool listAllDbs(bool verbose); *** a/src/bin/psql/help.c --- b/src/bin/psql/help.c *************** *** 219,224 **** slashUsage(unsigned short int pager) --- 219,227 ---- fprintf(output, _(" \\dT[S+] [PATTERN] list data types\n")); fprintf(output, _(" \\du[+] [PATTERN] list roles\n")); fprintf(output, _(" \\dv[S+] [PATTERN] list views\n")); + fprintf(output, _(" \\dx list installed extensions\n")); + fprintf(output, _(" \\dx[+] [PATTERN] list extension's objects\n")); + fprintf(output, _(" \\dX [PATTERN] list installed and available extensions\n")); fprintf(output, _(" \\l[+] list all databases\n")); fprintf(output, _(" \\sf[+] FUNCNAME show a function's definition\n")); fprintf(output, _(" \\z [PATTERN] same as \\dp\n")); *** a/src/include/catalog/dependency.h --- b/src/include/catalog/dependency.h *************** *** 101,108 **** typedef enum SharedDependencyType SHARED_DEPENDENCY_INVALID = 0 } SharedDependencyType; ! /* expansible list of ObjectAddresses (private in dependency.c) */ ! typedef struct ObjectAddresses ObjectAddresses; /* * This enum covers all system catalogs whose OIDs can appear in --- 101,131 ---- SHARED_DEPENDENCY_INVALID = 0 } SharedDependencyType; ! /* ! * Deletion processing requires additional state for each ObjectAddress that ! * it's planning to delete. For simplicity and code-sharing we make the ! * ObjectAddresses code support arrays with or without this extra state. ! */ ! typedef struct ! { ! int flags; /* bitmask, see bit definitions below */ ! ObjectAddress dependee; /* object whose deletion forced this one */ ! } ObjectAddressExtra; ! ! /* ObjectAddressExtra flag bits */ ! #define DEPFLAG_ORIGINAL 0x0001 /* an original deletion target */ ! #define DEPFLAG_NORMAL 0x0002 /* reached via normal dependency */ ! #define DEPFLAG_AUTO 0x0004 /* reached via auto dependency */ ! #define DEPFLAG_INTERNAL 0x0008 /* reached via internal dependency */ ! ! /* expansible list of ObjectAddresses (used in dependency.c and extension.c) */ ! typedef struct ! { ! ObjectAddress *refs; /* => palloc'd array */ ! ObjectAddressExtra *extras; /* => palloc'd array, or NULL if not used */ ! int numrefs; /* current number of references */ ! int maxrefs; /* current size of palloc'd array(s) */ ! } ObjectAddresses; /* * This enum covers all system catalogs whose OIDs can appear in *************** *** 138,143 **** typedef enum ObjectClass --- 161,167 ---- OCLASS_FOREIGN_SERVER, /* pg_foreign_server */ OCLASS_USER_MAPPING, /* pg_user_mapping */ OCLASS_DEFACL, /* pg_default_acl */ + OCLASS_EXTENSION, /* pg_extension */ MAX_OCLASS /* MUST BE LAST */ } ObjectClass; *************** *** 167,172 **** extern ObjectClass getObjectClass(const ObjectAddress *object); --- 191,198 ---- extern char *getObjectDescription(const ObjectAddress *object); extern char *getObjectDescriptionOids(Oid classid, Oid objid); + extern char *getObjectDescriptionOids(Oid classid, Oid objid); + extern ObjectAddresses *new_object_addresses(void); extern void add_exact_object_address(const ObjectAddress *object, *************** *** 181,186 **** extern void record_object_address_dependencies(const ObjectAddress *depender, --- 207,215 ---- extern void free_object_addresses(ObjectAddresses *addrs); + extern ObjectAddresses *listDependentObjects(ObjectAddress *object); + extern void freeDependentObjects(ObjectAddresses *targetObjects); + /* in pg_depend.c */ extern void recordDependencyOn(const ObjectAddress *depender, *************** *** 198,203 **** extern long changeDependencyFor(Oid classId, Oid objectId, --- 227,236 ---- Oid refClassId, Oid oldRefObjectId, Oid newRefObjectId); + extern long changeDependencyTypeFor(Oid objid, + Oid refclassId, Oid refobjectId, int refobjsubid, + DependencyType expected, DependencyType newtype); + extern bool sequenceIsOwned(Oid seqId, Oid *tableId, int32 *colId); extern void markSequenceUnowned(Oid seqId); *************** *** 208,213 **** extern Oid get_constraint_index(Oid constraintId); --- 241,249 ---- extern Oid get_index_constraint(Oid indexId); + extern Oid get_extension_namespace(Oid extensionId); + + /* in pg_shdepend.c */ extern void recordSharedDependencyOn(ObjectAddress *depender, *** a/src/include/catalog/indexing.h --- b/src/include/catalog/indexing.h *************** *** 286,291 **** DECLARE_UNIQUE_INDEX(pg_db_role_setting_databaseid_rol_index, 2965, on pg_db_rol --- 286,297 ---- DECLARE_UNIQUE_INDEX(pg_seclabel_object_index, 3597, on pg_seclabel using btree(objoid oid_ops, classoid oid_ops, objsubid int4_ops, provider text_ops)); #define SecLabelObjectIndexId 3597 + DECLARE_UNIQUE_INDEX(pg_extension_oid_index, 3995, on pg_extension using btree(oid oid_ops)); + #define ExtensionOidIndexId 3995 + + DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3997, on pg_extension using btree(extname name_ops)); + #define ExtensionNameIndexId 3997 + /* last step of initialization script: build the indexes declared above */ BUILD_INDICES *** /dev/null --- b/src/include/catalog/pg_extension.h *************** *** 0 **** --- 1,61 ---- + /*------------------------------------------------------------------------- + * + * pg_extension.h + * definition of the system "extension" relation (pg_extension) + * + * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_extension.h + * + * NOTES + * the genbki.pl script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ + #ifndef PG_EXTENSION_H + #define PG_EXTENSION_H + + #include "catalog/genbki.h" + + /* ---------------- + * pg_extension definition. cpp turns this into + * typedef struct FormData_pg_extension + * ---------------- + */ + #define ExtensionRelationId 3996 + + CATALOG(pg_extension,3996) + { + NameData extname; /* name of the extension */ + bool relocatable; /* when true we can ALTER EXTENSION SET SCHEMA */ + text extversion; /* version "name" of the extension */ + } FormData_pg_extension; + + /* ---------------- + * Form_pg_extension corresponds to a pointer to a tuple with + * the format of pg_extension relation. + * ---------------- + */ + typedef FormData_pg_extension *Form_pg_extension; + + /* ---------------- + * compiler constants for pg_extension + * ---------------- + */ + + #define Natts_pg_extension 3 + #define Anum_pg_extension_extname 1 + #define Anum_pg_extension_relocatable 2 + #define Anum_pg_extension_extversion 3 + + /* ---------------- + * initial contents of pg_extension + * ---------------- + */ + + /* btree + DATA(insert ("PostgreSQL" PG_VERSION _null_)); + */ + #endif /* PG_EXTENSION_H */ *** a/src/include/catalog/pg_proc.h --- b/src/include/catalog/pg_proc.h *************** *** 3399,3407 **** DESCR("reload configuration files"); DATA(insert OID = 2622 ( pg_rotate_logfile PGNSP PGUID 12 1 0 0 f f f t f v 0 0 16 "" _null_ _null_ _null_ _null_ pg_rotate_logfile _null_ _null_ _null_ )); DESCR("rotate log file"); ! DATA(insert OID = 2623 ( pg_stat_file PGNSP PGUID 12 1 0 0 f f f t f v 1 0 2249 "25" "{25,20,1184,1184,1184,1184,16}" "{i,o,o,o,o,o,o}" "{filename,size,access,modification,change,creation,isdir}" _null_ pg_stat_file _null_ _null_ _null_ )); DESCR("return file information"); ! DATA(insert OID = 2624 ( pg_read_file PGNSP PGUID 12 1 0 0 f f f t f v 3 0 25 "25 20 20" _null_ _null_ _null_ _null_ pg_read_file _null_ _null_ _null_ )); DESCR("read text from a file"); DATA(insert OID = 3826 ( pg_read_file PGNSP PGUID 12 1 0 0 f f f t f v 1 0 25 "25" _null_ _null_ _null_ _null_ pg_read_file_all _null_ _null_ _null_ )); DESCR("read text from a file"); --- 3399,3407 ---- DATA(insert OID = 2622 ( pg_rotate_logfile PGNSP PGUID 12 1 0 0 f f f t f v 0 0 16 "" _null_ _null_ _null_ _null_ pg_rotate_logfile _null_ _null_ _null_ )); DESCR("rotate log file"); ! DATA(insert OID = 2623 ( pg_stat_file PGNSP PGUID 12 1 0 0 f f f t f v 1 0 2249 "25" "{25,20,1184,1184,1184,1184,16}" "{i,o,o,o,o,o,o}" "{filename,size,access,modification,change,creation,isdir}" _null_ pg_stat_file _null_ _null_ _null_ )); DESCR("return file information"); ! DATA(insert OID = 2624 ( pg_read_file PGNSP PGUID 12 1 0 0 f f f t f v 3 0 25 "25 20 20" _null_ _null_ _null_ _null_ pg_read_file _null_ _null_ _null_ )); DESCR("read text from a file"); DATA(insert OID = 3826 ( pg_read_file PGNSP PGUID 12 1 0 0 f f f t f v 1 0 25 "25" _null_ _null_ _null_ _null_ pg_read_file_all _null_ _null_ _null_ )); DESCR("read text from a file"); *************** *** 3411,3417 **** DATA(insert OID = 3828 ( pg_read_binary_file PGNSP PGUID 12 1 0 0 f f f t f v 1 DESCR("read bytea from a file"); DATA(insert OID = 2625 ( pg_ls_dir PGNSP PGUID 12 1 1000 0 f f f t t v 1 0 25 "25" _null_ _null_ _null_ _null_ pg_ls_dir _null_ _null_ _null_ )); DESCR("list all files in a directory"); ! DATA(insert OID = 2626 ( pg_sleep PGNSP PGUID 12 1 0 0 f f f t f v 1 0 2278 "701" _null_ _null_ _null_ _null_ pg_sleep _null_ _null_ _null_ )); DESCR("sleep for the specified time in seconds"); DATA(insert OID = 2971 ( text PGNSP PGUID 12 1 0 0 f f f t f i 1 0 25 "16" _null_ _null_ _null_ _null_ booltext _null_ _null_ _null_ )); --- 3411,3417 ---- DESCR("read bytea from a file"); DATA(insert OID = 2625 ( pg_ls_dir PGNSP PGUID 12 1 1000 0 f f f t t v 1 0 25 "25" _null_ _null_ _null_ _null_ pg_ls_dir _null_ _null_ _null_ )); DESCR("list all files in a directory"); ! DATA(insert OID = 2626 ( pg_sleep PGNSP PGUID 12 1 0 0 f f f t f v 1 0 2278 "701" _null_ _null_ _null_ _null_ pg_sleep _null_ _null_ _null_ )); DESCR("sleep for the specified time in seconds"); DATA(insert OID = 2971 ( text PGNSP PGUID 12 1 0 0 f f f t f i 1 0 25 "16" _null_ _null_ _null_ _null_ booltext _null_ _null_ _null_ )); *************** *** 4871,4876 **** DESCR("fetch the last row value"); --- 4871,4885 ---- DATA(insert OID = 3114 ( nth_value PGNSP PGUID 12 1 0 0 f t f t f i 2 0 2283 "2283 23" _null_ _null_ _null_ _null_ window_nth_value _null_ _null_ _null_ )); DESCR("fetch the Nth row value"); + /* Extensions */ + DATA(insert OID = 3991 ( pg_extensions PGNSP PGUID 12 1 100 0 f f f t t s 0 0 2249 "" "{19,25,16,25,16}" "{o,o,o,o,o}" "{name,version,relocatable,comment,installed}" _null_ pg_extensions _null_ _null_ _null_ )); + DESCR("list available and installed extensions"); + DATA(insert OID = 3992 ( pg_extension_objects PGNSP PGUID 12 1 100 0 f f f t t s 1 0 2249 "19" "{19,2205,26,26,25}" "{i,o,o,o,o}" "{name,class,classid,objid,objdesc}" _null_ pg_extension_objects _null_ _null_ _null_ )); + DESCR("list a given extension objects"); + DATA(insert OID = 3993 ( pg_extension_flag_dump PGNSP PGUID 12 1 0 0 f f f t f v 1 0 16 "26" _null_ _null_ _null_ _null_ pg_extension_flag_dump _null_ _null_ _null_ )); + DESCR("flag an extension's object to be part of pg_dump"); + DATA(insert OID = 3994 ( pg_extension_with_user_data PGNSP PGUID 12 1 0 0 f f f t f s 0 0 16 "" _null_ _null_ _null_ _null_ pg_extension_with_user_data _null_ _null_ _null_ )); + DESCR("return true if the extension install script should create user data"); /* * Symbolic values for provolatile column: these indicate whether the result *** a/src/include/catalog/toasting.h --- b/src/include/catalog/toasting.h *************** *** 43,48 **** extern void BootstrapToastTable(char *relName, --- 43,49 ---- DECLARE_TOAST(pg_attrdef, 2830, 2831); DECLARE_TOAST(pg_constraint, 2832, 2833); DECLARE_TOAST(pg_description, 2834, 2835); + DECLARE_TOAST(pg_extension, 3998, 3999); DECLARE_TOAST(pg_proc, 2836, 2837); DECLARE_TOAST(pg_rewrite, 2838, 2839); DECLARE_TOAST(pg_seclabel, 3598, 3599); *** a/src/include/commands/alter.h --- b/src/include/commands/alter.h *************** *** 14,30 **** #ifndef ALTER_H #define ALTER_H #include "nodes/parsenodes.h" #include "utils/acl.h" #include "utils/relcache.h" extern void ExecRenameStmt(RenameStmt *stmt); extern void ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt); ! extern void AlterObjectNamespace(Relation rel, int cacheId, Oid classId, Oid objid, Oid nspId, int Anum_name, int Anum_namespace, int Anum_owner, AclObjectKind acl_kind, bool superuser_only); extern void ExecAlterOwnerStmt(AlterOwnerStmt *stmt); #endif /* ALTER_H */ --- 14,32 ---- #ifndef ALTER_H #define ALTER_H + #include "catalog/objectaddress.h" #include "nodes/parsenodes.h" #include "utils/acl.h" #include "utils/relcache.h" extern void ExecRenameStmt(RenameStmt *stmt); extern void ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt); ! extern Oid AlterObjectNamespace(Relation rel, int cacheId, Oid classId, Oid objid, Oid nspId, int Anum_name, int Anum_namespace, int Anum_owner, AclObjectKind acl_kind, bool superuser_only); + extern Oid AlterObjectNamespace_internal(ObjectAddress *thisobj, Oid nspOid); extern void ExecAlterOwnerStmt(AlterOwnerStmt *stmt); #endif /* ALTER_H */ *** a/src/include/commands/conversioncmds.h --- b/src/include/commands/conversioncmds.h *************** *** 23,27 **** extern void RenameConversion(List *name, const char *newname); --- 23,28 ---- extern void AlterConversionOwner(List *name, Oid newOwnerId); extern void AlterConversionOwner_oid(Oid conversionOid, Oid newOwnerId); extern void AlterConversionNamespace(List *name, const char *newschema); + extern Oid AlterConversionNamespace_oid(Oid convOid, Oid newNspOid); #endif /* CONVERSIONCMDS_H */ *** a/src/include/commands/defrem.h --- b/src/include/commands/defrem.h *************** *** 66,71 **** extern void DropCast(DropCastStmt *stmt); --- 66,72 ---- extern void DropCastById(Oid castOid); extern void AlterFunctionNamespace(List *name, List *argtypes, bool isagg, const char *newschema); + extern Oid AlterFunctionNamespace_oid(Oid procOid, Oid nspOid); extern void ExecuteDoStmt(DoStmt *stmt); extern Oid get_cast_oid(Oid sourcetypeid, Oid targettypeid, bool missing_ok); *************** *** 79,84 **** extern void AlterOperatorOwner_oid(Oid operOid, Oid newOwnerId); --- 80,87 ---- extern void AlterOperatorNamespace(List *names, List *argtypes, const char *newschema); extern Oid get_opclass_oid(Oid amID, List *opclassname, bool missing_ok); extern Oid get_opfamily_oid(Oid amID, List *opfamilyname, bool missing_ok); + extern Oid AlterOperatorNamespace_oid(Oid operOid, Oid newNspOid); + extern void AlterOperatorNamespace(List *names, List *argtypes, const char *newschema); /* commands/aggregatecmds.c */ extern void DefineAggregate(List *name, List *args, bool oldstyle, *************** *** 102,111 **** extern void RenameOpFamily(List *name, const char *access_method, const char *ne --- 105,116 ---- extern void AlterOpClassOwner(List *name, const char *access_method, Oid newOwnerId); extern void AlterOpClassOwner_oid(Oid opclassOid, Oid newOwnerId); extern void AlterOpClassNamespace(List *name, List *argam, const char *newschema); + extern Oid AlterOpClassNamespace_oid(Oid opclassOid, Oid newNspOid); extern void AlterOpFamilyOwner(List *name, const char *access_method, Oid newOwnerId); extern void AlterOpFamilyOwner_oid(Oid opfamilyOid, Oid newOwnerId); extern Oid get_am_oid(const char *amname, bool missing_ok); extern void AlterOpFamilyNamespace(List *name, List *argam, const char *newschema); + extern Oid AlterOpFamilyNamespace_oid(Oid opfamilyOid, Oid newNspOid); /* commands/tsearchcmds.c */ extern void DefineTSParser(List *names, List *parameters); *************** *** 113,118 **** extern void RenameTSParser(List *oldname, const char *newname); --- 118,125 ---- extern void AlterTSParserNamespace(List *name, const char *newschema); extern void RemoveTSParsers(DropStmt *drop); extern void RemoveTSParserById(Oid prsId); + extern void AlterTSParserNamespace(List *name, const char *newschema); + extern Oid AlterTSParserNamespace_oid(Oid prsId, Oid newNspOid); extern void DefineTSDictionary(List *names, List *parameters); extern void RenameTSDictionary(List *oldname, const char *newname); *************** *** 121,135 **** extern void RemoveTSDictionaryById(Oid dictId); --- 128,146 ---- extern void AlterTSDictionary(AlterTSDictionaryStmt *stmt); extern void AlterTSDictionaryOwner(List *name, Oid newOwnerId); extern void AlterTSDictionaryNamespace(List *name, const char *newschema); + extern Oid AlterTSDictionaryNamespace_oid(Oid dictId, Oid newNspOid); extern void DefineTSTemplate(List *names, List *parameters); extern void RenameTSTemplate(List *oldname, const char *newname); extern void AlterTSTemplateNamespace(List *name, const char *newschema); + extern Oid AlterTSTemplateNamespace_oid(Oid tmplId, Oid newNspOid); extern void RemoveTSTemplates(DropStmt *stmt); extern void RemoveTSTemplateById(Oid tmplId); extern void DefineTSConfiguration(List *names, List *parameters); extern void RenameTSConfiguration(List *oldname, const char *newname); + extern void AlterTSConfigurationNamespace(List *name, const char *newschema); + extern Oid AlterTSConfigurationNamespace_oid(Oid cfgId, Oid newNspOid); extern void RemoveTSConfigurations(DropStmt *stmt); extern void RemoveTSConfigurationById(Oid cfgId); extern void AlterTSConfiguration(AlterTSConfigurationStmt *stmt); *** /dev/null --- b/src/include/commands/extension.h *************** *** 0 **** --- 1,71 ---- + /*------------------------------------------------------------------------- + * + * extension.h + * Extension management commands (create/drop extension). + * + * + * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/commands/extension.h + * + *------------------------------------------------------------------------- + */ + #ifndef EXTENSION_H + #define EXTENSION_H + + #include "catalog/dependency.h" + #include "catalog/objectaddress.h" + #include "utils/genfile.h" + + + /* + * create_extension is only set when running a CREATE EXTENSION command, it + * allows to register the (INTERNAL) dependencies between the pg_extension + * row and the SQL objects created by its installation script. + * + * For that to work out, all CREATE commands have been modified so that they + * will inquire about create_extension and call recordDependencyOn() when + * it's set. + */ + extern ObjectAddress CreateExtensionAddress; + bool create_extension; + bool create_extension_with_user_data; + + typedef struct ExtensionControlFile + { + char *name; /* name of the extension */ + char *script; /* filename of the install script to execute */ + char *version; /* version "name" of the extension */ + char *comment; /* the control file can also contain a comment */ + int encoding; /* encoding of the given script */ + bool relocatable; /* when true, ALTER EXTENSION SET SCHEMA is supported */ + } ExtensionControlFile; + + /* expansible list of extensions */ + typedef struct ExtensionList + { + ExtensionControlFile *exts; /* => palloc'd array */ + int numrefs; /* current number of extensions */ + int maxrefs; /* current size of palloc'd array */ + } ExtensionList; + + + typedef struct + { + ObjectAddresses *targetObjects; + int i; + } extension_objects_fctx; + + extern void CreateExtension(CreateExtensionStmt *stmt); + extern void DropExtension(DropExtensionStmt *stmt); + + extern Oid get_extension_oid(const char *extname, bool missing_ok); + extern char *get_extension_name(Oid ext_oid); + extern bool extension_relocatable_p(Oid ext_oid); + extern void RemoveExtensionById(Oid extId); + extern void AlterExtensionNamespace(List *name, const char *newschema); + extern void AlterExtensionNamespace_oid(Oid extensionOid, Oid nspOid); + + + #endif /* EXTENSION_H */ *** a/src/include/commands/tablecmds.h --- b/src/include/commands/tablecmds.h *************** *** 34,40 **** extern void AlterTableInternal(Oid relid, List *cmds, bool recurse); extern void AlterTableNamespace(RangeVar *relation, const char *newschema, ObjectType stmttype, LOCKMODE lockmode); ! extern void AlterRelationNamespaceInternal(Relation classRel, Oid relOid, Oid oldNspOid, Oid newNspOid, bool hasDependEntry); --- 34,40 ---- extern void AlterTableNamespace(RangeVar *relation, const char *newschema, ObjectType stmttype, LOCKMODE lockmode); ! extern Oid AlterRelationNamespaceInternal(Relation classRel, Oid relOid, Oid oldNspOid, Oid newNspOid, bool hasDependEntry); *** a/src/include/commands/typecmds.h --- b/src/include/commands/typecmds.h *************** *** 41,47 **** extern void AlterTypeOwner(List *names, Oid newOwnerId); extern void AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId, bool hasDependEntry); extern void AlterTypeNamespace(List *names, const char *newschema); ! extern void AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, bool isImplicitArray, bool errorOnTableType); --- 41,48 ---- extern void AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId, bool hasDependEntry); extern void AlterTypeNamespace(List *names, const char *newschema); ! extern Oid AlterTypeNamespace_oid(Oid typeOid, Oid nspOid); ! extern Oid AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, bool isImplicitArray, bool errorOnTableType); *** a/src/include/nodes/nodes.h --- b/src/include/nodes/nodes.h *************** *** 353,358 **** typedef enum NodeTag --- 353,360 ---- T_DropUserMappingStmt, T_AlterTableSpaceOptionsStmt, T_SecLabelStmt, + T_CreateExtensionStmt, + T_DropExtensionStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** *** 1064,1069 **** typedef enum ObjectType --- 1064,1070 ---- OBJECT_CONVERSION, OBJECT_DATABASE, OBJECT_DOMAIN, + OBJECT_EXTENSION, OBJECT_FDW, OBJECT_FOREIGN_SERVER, OBJECT_FUNCTION, *************** *** 1490,1495 **** typedef struct Constraint --- 1491,1516 ---- } Constraint; /* ---------------------- + * Create/Drop Extension Statements + * ---------------------- + */ + + typedef struct CreateExtensionStmt + { + NodeTag type; + char *extname; + List *options; /* List of DefElem nodes */ + } CreateExtensionStmt; + + typedef struct DropExtensionStmt + { + NodeTag type; + char *extname; + bool missing_ok; /* skip error if missing? */ + DropBehavior behavior; /* drop behavior - cascade/restrict */ + } DropExtensionStmt; + + /* ---------------------- * Create/Drop Table Space Statements * ---------------------- */ *** a/src/include/parser/kwlist.h --- b/src/include/parser/kwlist.h *************** *** 150,155 **** PG_KEYWORD("exclusive", EXCLUSIVE, UNRESERVED_KEYWORD) --- 150,156 ---- PG_KEYWORD("execute", EXECUTE, UNRESERVED_KEYWORD) PG_KEYWORD("exists", EXISTS, COL_NAME_KEYWORD) PG_KEYWORD("explain", EXPLAIN, UNRESERVED_KEYWORD) + PG_KEYWORD("extension", EXTENSION, UNRESERVED_KEYWORD) PG_KEYWORD("external", EXTERNAL, UNRESERVED_KEYWORD) PG_KEYWORD("extract", EXTRACT, COL_NAME_KEYWORD) PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD) *** a/src/include/utils/builtins.h --- b/src/include/utils/builtins.h *************** *** 1062,1065 **** extern Datum pg_prepared_statement(PG_FUNCTION_ARGS); --- 1062,1071 ---- /* utils/mmgr/portalmem.c */ extern Datum pg_cursor(PG_FUNCTION_ARGS); + /* commands/extension.c */ + extern Datum pg_extensions(PG_FUNCTION_ARGS); + extern Datum pg_extension_with_user_data(PG_FUNCTION_ARGS); + extern Datum pg_extension_flag_dump(PG_FUNCTION_ARGS); + extern Datum pg_extension_objects(PG_FUNCTION_ARGS); + #endif /* BUILTINS_H */ *** /dev/null --- b/src/include/utils/genfile.h *************** *** 0 **** --- 1,26 ---- + /*-------------------------------------------------------------------- + * genfile.h + * + * External declarations pertaining to backend/utils/misc/genfile.c + * + * Copyright (c) 2000-2010, PostgreSQL Global Development Group + * + * src/include/utils/genfile.h + *-------------------------------------------------------------------- + */ + #ifndef GENFILE_H + #define GENFILE_H + + #include + #include "utils/array.h" + + /* + * We need that in extension.c in the SRF that lists available extensions. + */ + typedef struct + { + char *location; + DIR *dirdesc; + } directory_fctx; + + #endif /* GENFILE_H */ *** a/src/makefiles/pgxs.mk --- b/src/makefiles/pgxs.mk *************** *** 82,88 **** ifdef PG_CPPFLAGS override CPPFLAGS := $(PG_CPPFLAGS) $(CPPFLAGS) endif ! all: $(PROGRAM) $(DATA_built) $(SCRIPTS_built) $(addsuffix $(DLSUFFIX), $(MODULES)) ifdef MODULE_big # shared library parameters --- 82,88 ---- override CPPFLAGS := $(PG_CPPFLAGS) $(CPPFLAGS) endif ! all: $(PROGRAM) $(DATA_built) $(SCRIPTS_built) $(addsuffix $(DLSUFFIX), $(MODULES)) $(addsuffix .control, $(EXTENSION)) ifdef MODULE_big # shared library parameters *************** *** 93,100 **** include $(top_srcdir)/src/Makefile.shlib all: all-lib endif # MODULE_big - install: all installdirs ifneq (,$(DATA)$(DATA_built)) @for file in $(addprefix $(srcdir)/, $(DATA)) $(DATA_built); do \ echo "$(INSTALL_DATA) $$file '$(DESTDIR)$(datadir)/$(datamoduledir)'"; \ --- 93,105 ---- all: all-lib endif # MODULE_big install: all installdirs + ifneq (,$(EXTENSION)) + @for file in $(addsuffix .control, $(EXTENSION)); do \ + echo "$(INSTALL_DATA) $$file '$(DESTDIR)$(datadir)/$(datamoduledir)'"; \ + $(INSTALL_DATA) $$file '$(DESTDIR)$(datadir)/$(datamoduledir)'; \ + done + endif # EXTENSION ifneq (,$(DATA)$(DATA_built)) @for file in $(addprefix $(srcdir)/, $(DATA)) $(DATA_built); do \ echo "$(INSTALL_DATA) $$file '$(DESTDIR)$(datadir)/$(datamoduledir)'"; \ *************** *** 167,174 **** endif # MODULE_big uninstall: ! ifneq (,$(DATA)$(DATA_built)) ! rm -f $(addprefix '$(DESTDIR)$(datadir)/$(datamoduledir)'/, $(notdir $(DATA) $(DATA_built))) endif ifneq (,$(DATA_TSEARCH)) rm -f $(addprefix '$(DESTDIR)$(datadir)/tsearch_data'/, $(notdir $(DATA_TSEARCH))) --- 172,179 ---- uninstall: ! ifneq (,$(DATA)$(DATA_built)$(EXTENSION)) ! rm -f $(addprefix '$(DESTDIR)$(datadir)/$(datamoduledir)'/, $(notdir $(DATA) $(DATA_built) $(addsuffix .control, $(EXTENSION)))) endif ifneq (,$(DATA_TSEARCH)) rm -f $(addprefix '$(DESTDIR)$(datadir)/tsearch_data'/, $(notdir $(DATA_TSEARCH))) *** a/src/test/regress/expected/rules.out --- b/src/test/regress/expected/rules.out *************** *** 1279,1284 **** SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem --- 1279,1285 ---- viewname | definition -----------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- iexit | SELECT ih.name, ih.thepath, interpt_pp(ih.thepath, r.thepath) AS exit FROM ihighway ih, ramp r WHERE (ih.thepath ## r.thepath); + pg_available_extensions | SELECT e.name, e.version, e.relocatable, e.comment, e.installed FROM pg_extensions() e(name, version, relocatable, comment, installed); pg_cursors | SELECT c.name, c.statement, c.is_holdable, c.is_binary, c.is_scrollable, c.creation_time FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time); pg_group | SELECT pg_authid.rolname AS groname, pg_authid.oid AS grosysid, ARRAY(SELECT pg_auth_members.member FROM pg_auth_members WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist FROM pg_authid WHERE (NOT pg_authid.rolcanlogin); pg_indexes | SELECT n.nspname AS schemaname, c.relname AS tablename, i.relname AS indexname, t.spcname AS tablespace, pg_get_indexdef(i.oid) AS indexdef FROM ((((pg_index x JOIN pg_class c ON ((c.oid = x.indrelid))) JOIN pg_class i ON ((i.oid = x.indexrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace))) WHERE ((c.relkind = 'r'::"char") AND (i.relkind = 'i'::"char")); *************** *** 1334,1340 **** SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem shoelace_obsolete | SELECT shoelace.sl_name, shoelace.sl_avail, shoelace.sl_color, shoelace.sl_len, shoelace.sl_unit, shoelace.sl_len_cm FROM shoelace WHERE (NOT (EXISTS (SELECT shoe.shoename FROM shoe WHERE (shoe.slcolor = shoelace.sl_color)))); street | SELECT r.name, r.thepath, c.cname FROM ONLY road r, real_city c WHERE (c.outline ## r.thepath); toyemp | SELECT emp.name, emp.age, emp.location, (12 * emp.salary) AS annualsal FROM emp; ! (56 rows) SELECT tablename, rulename, definition FROM pg_rules ORDER BY tablename, rulename; --- 1335,1341 ---- shoelace_obsolete | SELECT shoelace.sl_name, shoelace.sl_avail, shoelace.sl_color, shoelace.sl_len, shoelace.sl_unit, shoelace.sl_len_cm FROM shoelace WHERE (NOT (EXISTS (SELECT shoe.shoename FROM shoe WHERE (shoe.slcolor = shoelace.sl_color)))); street | SELECT r.name, r.thepath, c.cname FROM ONLY road r, real_city c WHERE (c.outline ## r.thepath); toyemp | SELECT emp.name, emp.age, emp.location, (12 * emp.salary) AS annualsal FROM emp; ! (57 rows) SELECT tablename, rulename, definition FROM pg_rules ORDER BY tablename, rulename; *** a/src/test/regress/expected/sanity_check.out --- b/src/test/regress/expected/sanity_check.out *************** *** 100,105 **** SELECT relname, relhasindex --- 100,106 ---- pg_depend | t pg_description | t pg_enum | t + pg_extension | t pg_foreign_data_wrapper | t pg_foreign_server | t pg_index | t *************** *** 154,160 **** SELECT relname, relhasindex timetz_tbl | f tinterval_tbl | f varchar_tbl | f ! (143 rows) -- -- another sanity check: every system catalog that has OIDs should have --- 155,161 ---- timetz_tbl | f tinterval_tbl | f varchar_tbl | f ! (144 rows) -- -- another sanity check: every system catalog that has OIDs should have