From d9572c4e3b474031060189050e14ef384b94e001 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 8 Feb 2011 16:08:41 -0500 Subject: [PATCH] Core support for "extensions", which are packages of SQL objects. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds the server infrastructure to support extensions. There is still one significant loose end, namely how to make it play nice with pg_upgrade, so I am not yet committing the changes that would make all the contrib modules depend on this feature. In passing, fix a disturbingly large amount of breakage in AlterObjectNamespace() and callers. Dimitri Fontaine, reviewed by Anssi Kääriäinen, Itagaki Takahiro, Tom Lane, and numerous others --- doc/src/sgml/acronyms.sgml | 2 +- doc/src/sgml/catalogs.sgml | 195 +++ doc/src/sgml/extend.sgml | 715 +++++++++- doc/src/sgml/ref/allfiles.sgml | 3 + doc/src/sgml/ref/alter_extension.sgml | 98 ++ doc/src/sgml/ref/comment.sgml | 1 + doc/src/sgml/ref/create_extension.sgml | 118 ++ doc/src/sgml/ref/drop_extension.sgml | 121 ++ doc/src/sgml/ref/psql-ref.sgml | 14 +- doc/src/sgml/reference.sgml | 3 + doc/src/sgml/release-9.0.sgml | 5 +- doc/src/sgml/xfunc.sgml | 331 +---- src/backend/catalog/Makefile | 2 +- src/backend/catalog/dependency.c | 45 +- src/backend/catalog/heap.c | 5 +- src/backend/catalog/objectaddress.c | 14 + src/backend/catalog/pg_conversion.c | 3 + src/backend/catalog/pg_depend.c | 88 +- src/backend/catalog/pg_namespace.c | 11 +- src/backend/catalog/pg_operator.c | 5 +- src/backend/catalog/pg_proc.c | 9 +- src/backend/catalog/pg_type.c | 10 +- src/backend/catalog/system_views.sql | 7 + src/backend/commands/Makefile | 3 +- src/backend/commands/alter.c | 196 ++- src/backend/commands/cluster.c | 6 +- src/backend/commands/comment.c | 6 + src/backend/commands/conversioncmds.c | 31 +- src/backend/commands/extension.c | 1401 ++++++++++++++++++++ src/backend/commands/foreigncmds.c | 27 +- src/backend/commands/functioncmds.c | 42 +- src/backend/commands/opclasscmds.c | 82 +- src/backend/commands/operatorcmds.c | 28 +- src/backend/commands/proclang.c | 9 +- src/backend/commands/tablecmds.c | 1 + src/backend/commands/tsearchcmds.c | 128 +- src/backend/commands/typecmds.c | 23 +- src/backend/nodes/copyfuncs.c | 14 + src/backend/nodes/equalfuncs.c | 12 + src/backend/parser/gram.y | 56 +- src/backend/rewrite/rewriteDefine.c | 2 +- src/backend/tcop/utility.c | 24 + src/backend/utils/adt/genfile.c | 63 +- src/bin/pg_dump/common.c | 7 + src/bin/pg_dump/pg_dump.c | 287 +++- src/bin/pg_dump/pg_dump.h | 10 + src/bin/pg_dump/pg_dump_sort.c | 65 +- src/bin/psql/command.c | 6 + src/bin/psql/describe.c | 164 ++- src/bin/psql/describe.h | 6 + src/bin/psql/help.c | 1 + src/bin/psql/tab-complete.c | 36 +- src/include/catalog/catversion.h | 2 +- src/include/catalog/dependency.h | 16 +- src/include/catalog/indexing.h | 6 + src/include/catalog/pg_extension.h | 72 + src/include/catalog/pg_proc.h | 6 + src/include/commands/alter.h | 10 +- src/include/commands/conversioncmds.h | 1 + src/include/commands/defrem.h | 8 + src/include/commands/extension.h | 40 + src/include/commands/typecmds.h | 3 +- src/include/nodes/nodes.h | 1 + src/include/nodes/parsenodes.h | 13 + src/include/parser/kwlist.h | 1 + src/include/utils/builtins.h | 6 + src/makefiles/pgxs.mk | 23 +- src/test/regress/expected/rules.out | 3 +- src/test/regress/expected/sanity_check.out | 1 + 69 files changed, 4206 insertions(+), 547 deletions(-) create mode 100644 doc/src/sgml/ref/alter_extension.sgml create mode 100644 doc/src/sgml/ref/create_extension.sgml create mode 100644 doc/src/sgml/ref/drop_extension.sgml create mode 100644 src/backend/commands/extension.c create mode 100644 src/include/catalog/pg_extension.h create mode 100644 src/include/commands/extension.h diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml index d1ef489e36..8f6752f05d 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 f31662c272..24aa22cbce 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_wrapper foreign-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 @@ + + <structname>pg_extension</structname> + + + pg_extension + + + + The catalog pg_extension stores information + about the installed extensions. See + for details about extensions. + + + + <structname>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. + +
+ + <structname>pg_foreign_data_wrapper</structname> @@ -6191,6 +6306,11 @@ + + pg_available_extensions + available extensions + + pg_cursors open cursors @@ -6286,6 +6406,81 @@ + + <structname>pg_available_extensions</structname> + + + 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. + + + + <structname>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. + + +
+ <structname>pg_cursors</structname> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 2033ae21ba..206eb6b901 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 c44d11ef91..ba85cae083 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 0000000000..1b29d274cd --- /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 f1a1605df3..e1fe0c16f9 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 0000000000..961cab3839 --- /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 0000000000..1e09ec4c7a --- /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 eacae71cdc..cdf1abfa95 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 6ee8e5bcff..47cd01f58e 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 2288f1b0e6..4902c058a9 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 4ad50ec0cb..4f2c23fab7 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 7cffde1769..45aca8dd7f 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 ba0ea178ac..5c5f750a06 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 14c69f3faa..d9b272a712 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 c4608f7f17..82989acc08 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 b2f66d75ac..1ef6a9d24e 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 370051d91e..b2ce148d62 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 c78aa019bf..172f99196c 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 c70483a739..ccd0fe1997 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 2ab87d2df5..3f3877da28 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 8ceaab1fb1..9b574179ff 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 4fa1453b14..987026c835 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 9d2a732245..0aadbc56ad 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 1c6ae0243e..2c9340accf 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 59a439413e..4c4f356e79 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 2e8c4df927..bbb3f34409 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 da024df5f0..b5e4420ca8 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 0000000000..5003203197 --- /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 3a0ea9a632..a2b5358e16 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 dad65ee8ff..3f25b3bf02 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 662b942038..68072dd421 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 35bb76162d..b4374a62f4 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 3860105b26..b36f31ee6d 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 c0a4e6f954..f67e9b9b16 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 7afd2f896e..81f129dff6 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 25d0f3596e..fb9d67a30a 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 9b2c874d6d..851186146d 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 837eafaacc..00d23ccfa5 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 a1bcf02f5b..4c4536b9be 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 99c397b4f2..fecc4e27fa 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 af2eba01d6..10a4438995 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 93bc401c2d..c3ec98aa5e 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 55cc3126e1..cc0db66320 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 49c570016a..dec96bc025 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 43fd1ade27..f0e9ae1e06 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 fe5cf56e69..f1c1c65e6c 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 301dc11bcc..a80678c2c3 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 d6c6cf1f15..0342eb55bd 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 4e80bcf41f..4b690b3b70 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 2647b1081f..c44079e034 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 84c68a7bff..1c9623de1e 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 3defe0641f..019cd8fab5 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 c6ab313edf..4d7ff8853d 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 866942cf9a..4118e64542 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 0000000000..0ad47b01a4 --- /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 836574355c..9e6ec3dda5 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 74d5132636..21731685f5 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 6156c4a94e..f77023ffe3 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 01f271bff4..157ee39461 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 0000000000..10d08935a0 --- /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 b13363aaf7..1b20296934 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 63b9dd80be..1ca5f1ef9a 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 b68fe10990..5de4dbd5ec 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 7dad00197e..4939b493bc 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 540d16b844..d17eb67b1b 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 912578a84d..655c5f9e38 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 72e5630b1f..8ddc116d66 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 d378e2641f..59e1bdb807 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