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