Core support for "extensions", which are packages of SQL objects.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 8 Feb 2011 21:08:41 +0000 (16:08 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 8 Feb 2011 21:13:22 +0000 (16:13 -0500)
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

69 files changed:
doc/src/sgml/acronyms.sgml
doc/src/sgml/catalogs.sgml
doc/src/sgml/extend.sgml
doc/src/sgml/ref/allfiles.sgml
doc/src/sgml/ref/alter_extension.sgml [new file with mode: 0644]
doc/src/sgml/ref/comment.sgml
doc/src/sgml/ref/create_extension.sgml [new file with mode: 0644]
doc/src/sgml/ref/drop_extension.sgml [new file with mode: 0644]
doc/src/sgml/ref/psql-ref.sgml
doc/src/sgml/reference.sgml
doc/src/sgml/release-9.0.sgml
doc/src/sgml/xfunc.sgml
src/backend/catalog/Makefile
src/backend/catalog/dependency.c
src/backend/catalog/heap.c
src/backend/catalog/objectaddress.c
src/backend/catalog/pg_conversion.c
src/backend/catalog/pg_depend.c
src/backend/catalog/pg_namespace.c
src/backend/catalog/pg_operator.c
src/backend/catalog/pg_proc.c
src/backend/catalog/pg_type.c
src/backend/catalog/system_views.sql
src/backend/commands/Makefile
src/backend/commands/alter.c
src/backend/commands/cluster.c
src/backend/commands/comment.c
src/backend/commands/conversioncmds.c
src/backend/commands/extension.c [new file with mode: 0644]
src/backend/commands/foreigncmds.c
src/backend/commands/functioncmds.c
src/backend/commands/opclasscmds.c
src/backend/commands/operatorcmds.c
src/backend/commands/proclang.c
src/backend/commands/tablecmds.c
src/backend/commands/tsearchcmds.c
src/backend/commands/typecmds.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/parser/gram.y
src/backend/rewrite/rewriteDefine.c
src/backend/tcop/utility.c
src/backend/utils/adt/genfile.c
src/bin/pg_dump/common.c
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/pg_dump.h
src/bin/pg_dump/pg_dump_sort.c
src/bin/psql/command.c
src/bin/psql/describe.c
src/bin/psql/describe.h
src/bin/psql/help.c
src/bin/psql/tab-complete.c
src/include/catalog/catversion.h
src/include/catalog/dependency.h
src/include/catalog/indexing.h
src/include/catalog/pg_extension.h [new file with mode: 0644]
src/include/catalog/pg_proc.h
src/include/commands/alter.h
src/include/commands/conversioncmds.h
src/include/commands/defrem.h
src/include/commands/extension.h [new file with mode: 0644]
src/include/commands/typecmds.h
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/include/parser/kwlist.h
src/include/utils/builtins.h
src/makefiles/pgxs.mk
src/test/regress/expected/rules.out
src/test/regress/expected/sanity_check.out

index d1ef489e3666f39b03fcd46d8be5d1572b579444..8f6752f05d70f1c74b830600d4fe57e18316d3c8 100644 (file)
     <term><acronym>PGXS</acronym></term>
     <listitem>
      <para>
-      <link linkend="xfunc-c-pgxs"><productname>PostgreSQL</> Extension System</link>
+      <link linkend="extend-pgxs"><productname>PostgreSQL</> Extension System</link>
      </para>
     </listitem>
    </varlistentry>
index f31662c2720f7b6ef10c3b4a46a077d53d7b98e1..24aa22cbced804c05df94a65aada91c6e69f4bf2 100644 (file)
       <entry>enum label and value definitions</entry>
      </row>
 
+     <row>
+      <entry><link linkend="catalog-pg-extension"><structname>pg_extension</structname></link></entry>
+      <entry>installed extensions</entry>
+     </row>
+
      <row>
       <entry><link linkend="catalog-pg-foreign-data-wrapper"><structname>pg_foreign_data_wrapper</structname></link></entry>
       <entry>foreign-data wrapper definitions</entry>
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><symbol>DEPENDENCY_EXTENSION</> (<literal>e</>)</term>
+     <listitem>
+      <para>
+       The dependent object is a member of the <firstterm>extension</> that is
+       the referenced object (see
+       <link linkend="catalog-pg-extension"><structname>pg_extension</structname></link>).
+       The dependent object can be dropped only via
+       <command>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 <application>pg_dump</>.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><symbol>DEPENDENCY_PIN</> (<literal>p</>)</term>
      <listitem>
  </sect1>
 
 
+ <sect1 id="catalog-pg-extension">
+  <title><structname>pg_extension</structname></title>
+
+  <indexterm zone="catalog-pg-extension">
+   <primary>pg_extension</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_extension</structname> stores information
+   about the installed extensions.  See <xref linkend="extend-extensions">
+   for details about extensions.
+  </para>
+
+  <table>
+   <title><structname>pg_extension</> Columns</title>
+
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry><structfield>extname</structfield></entry>
+      <entry><type>name</type></entry>
+      <entry></entry>
+      <entry>Name of the extension</entry>
+     </row>
+
+     <row>
+      <entry><structfield>extowner</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>.oid</literal></entry>
+      <entry>Owner of the extension</entry>
+     </row>
+
+     <row>
+      <entry><structfield>extnamespace</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.oid</literal></entry>
+      <entry>Schema containing the extension's exported objects</entry>
+     </row>
+
+     <row>
+      <entry><structfield>extrelocatable</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if extension can be relocated to another schema</entry>
+     </row>
+
+     <row>
+      <entry><structfield>extversion</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry></entry>
+      <entry>Version string for the extension, or <literal>NULL</> if none</entry>
+     </row>
+
+     <row>
+      <entry><structfield>extconfig</structfield></entry>
+      <entry><type>oid[]</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>Array of <type>regclass</> OIDs for the extension's configuration
+       table(s), or <literal>NULL</> if none</entry>
+     </row>
+
+     <row>
+      <entry><structfield>extcondition</structfield></entry>
+      <entry><type>text[]</type></entry>
+      <entry></entry>
+      <entry>Array of <literal>WHERE</>-clause filter conditions for the
+       extension's configuration table(s), or <literal>NULL</> if none</entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   Note that unlike most catalogs with a <quote>namespace</> column,
+   <structfield>extnamespace</structfield> is not meant to imply
+   that the extension belongs to that schema.  Extension names are never
+   schema-qualified.  Rather, <structfield>extnamespace</structfield>
+   indicates the schema that contains most or all of the extension's
+   objects.  If <structfield>extrelocatable</structfield> is true, then
+   this schema must in fact contain all schema-qualifiable objects
+   belonging to the extension.
+  </para>
+ </sect1>
+
+
  <sect1 id="catalog-pg-foreign-data-wrapper">
   <title><structname>pg_foreign_data_wrapper</structname></title>
 
     </thead>
 
     <tbody>
+     <row>
+      <entry><link linkend="view-pg-available-extensions"><structname>pg_available_extensions</structname></link></entry>
+      <entry>available extensions</entry>
+     </row>
+
      <row>
       <entry><link linkend="view-pg-cursors"><structname>pg_cursors</structname></link></entry>
       <entry>open cursors</entry>
   </table>
  </sect1>
 
+ <sect1 id="view-pg-available-extensions">
+  <title><structname>pg_available_extensions</structname></title>
+
+  <indexterm zone="view-pg-available-extensions">
+   <primary>pg_available_extensions</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_available_extensions</structname> view lists the
+   extensions that are available for installation.  This view can only
+   be read by superusers.  See also the
+   <link linkend="catalog-pg-extension"><structname>pg_extension</structname></link>
+   catalog, which shows the extensions currently installed.
+  </para>
+
+  <table>
+   <title><structname>pg_available_extensions</> Columns</title>
+
+   <tgroup cols="3">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry><structfield>name</structfield></entry>
+      <entry><type>name</type></entry>
+      <entry>Extension name</entry>
+     </row>
+
+     <row>
+      <entry><structfield>version</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry>Version string from the extension's control file</entry>
+     </row>
+
+     <row>
+      <entry><structfield>installed</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry>Currently installed version of the extension,
+       or <literal>NULL</literal> if not installed</entry>
+     </row>
+
+     <row>
+      <entry><structfield>schema</structfield></entry>
+      <entry><type>name</type></entry>
+      <entry>Name of the schema where the extension is installed,
+       or <literal>NULL</literal> if not installed</entry>
+     </row>
+
+     <row>
+      <entry><structfield>relocatable</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry>True if extension can be relocated to another schema</entry>
+     </row>
+
+     <row>
+      <entry><structfield>comment</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry>Comment string from the extension's control file</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   The <structname>pg_available_extensions</structname> view is read only.
+  </para>
+
+ </sect1>
+
  <sect1 id="view-pg-cursors">
   <title><structname>pg_cursors</structname></title>
 
index 2033ae21ba5261f91b48563c0e8ec16fd4db4ff0..206eb6b9017cb7f8205b1ff4c6def320fab94712 100644 (file)
       operator classes for indexes (starting in <xref linkend="xindex">)
      </para>
     </listitem>
+    <listitem>
+     <para>
+      packages of related objects (starting in <xref linkend="extend-extensions">)
+     </para>
+    </listitem>
    </itemizedlist>
   </para>
 
   &xoper;
   &xindex;
 
-  <sect1 id="extend-Cpp">
-   <title>Using C++ for Extensibility</title>
 
-   <indexterm zone="extend-Cpp">
-    <primary>C++</primary>
+  <sect1 id="extend-extensions">
+   <title>Packaging Related Objects into an Extension</title>
+
+   <indexterm zone="extend-extensions">
+    <primary>extension</primary>
    </indexterm>
 
    <para>
-    It is possible to use a compiler in C++ mode to build
-    <productname>PostgreSQL</productname> extensions by following these
-    guidelines:
+    A useful extension to <productname>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.  <productname>PostgreSQL</> calls
+    such a package an <firstterm>extension</>.  To define an extension,
+    you need at least a <firstterm>script file</> that contains the
+    <acronym>SQL</> commands to create the extension's objects, and a
+    <firstterm>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
+    <xref linkend="sql-createextension"> command loads the objects into
+    your database.
+   </para>
+
+   <para>
+    The advantage of using an extension, rather than just running the
+    <acronym>SQL</> script to load a bunch of <quote>loose</> objects
+    into your database, is that <productname>PostgreSQL</> will then
+    understand that the objects of the extension go together.  You can
+    drop all the objects with a single <xref linkend="sql-dropextension">
+    command (no need to maintain a separate <quote>uninstall</> script).
+    Even more useful, <application>pg_dump</> knows that it should not
+    dump the individual member objects of the extension &mdash; it will
+    just include a <command>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.
+   </para>
+
+   <para>
+    <productname>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 <command>CREATE OR REPLACE FUNCTION</command> for a
+    function), bear in mind that the modified definition will not be dumped
+    by <application>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.)
+   </para>
+
+   <sect2>
+    <title>Extension Files</title>
+
+   <indexterm>
+    <primary>control file</primary>
+   </indexterm>
+
+    <para>
+     The <xref linkend="sql-createextension"> command relies on a control
+     file for each extension, which must be named the same as the extension
+     with a suffix of <literal>.control</>, and must be placed in the
+     installation's <literal>SHAREDIR/contrib</literal> directory.  There
+     must also be a <acronym>SQL</> script file, which typically is
+     named after the extension with a suffix of <literal>.sql</>, and is also
+     placed in the <literal>SHAREDIR/contrib</literal> directory; but these
+     defaults can be overridden by the control file.
+    </para>
+
+    <para>
+     The file format for an extension control file is the same as for the
+     <filename>postgresql.conf</> file, namely a list of
+     <replaceable>parameter-name</> <literal>=</> <replaceable>value</>
+     assignments, one per line.  Blank lines and comments introduced by
+     <literal>#</> are allowed.  Be sure to quote any value that is not
+     a single word or number.
+    </para>
+
+    <para>
+     A control file can set the following parameters:
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><varname>script</varname> (<type>string</type>)</term>
+      <listitem>
+       <para>
+        The filename of the extension's <acronym>SQL</> script.
+        Defaults to the same name as the control file, but with the
+        <literal>.sql</literal> extension.  Unless an absolute path is
+        given, the name is relative to the <literal>SHAREDIR/contrib</literal>
+        directory.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>version</varname> (<type>string</type>)</term>
+      <listitem>
+       <para>
+        The version of the extension.  Any string can be given.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>comment</varname> (<type>string</type>)</term>
+      <listitem>
+       <para>
+        A comment (any string) about the extension.  Alternatively,
+        the comment can be set by means of the <xref linkend="sql-comment">
+        command.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>requires</varname> (<type>string</type>)</term>
+      <listitem>
+       <para>
+        A list of names of extensions that this extension depends on,
+        for example <literal>requires = 'foo, bar'</literal>.  Those
+        extensions must be installed before this one can be installed.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>encoding</varname> (<type>string</type>)</term>
+      <listitem>
+       <para>
+        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.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>relocatable</varname> (<type>boolean</type>)</term>
+      <listitem>
+       <para>
+        An extension is <firstterm>relocatable</> if it is possible to move
+        its contained objects into a different schema after initial creation
+        of the extension.  The default is <literal>false</>, i.e. the
+        extension is not relocatable.
+        See below for more information.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>schema</varname> (<type>string</type>)</term>
+      <listitem>
+       <para>
+        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.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+
+    <para>
+     An extension's <acronym>SQL</> script file can contain any SQL commands,
+     except for transaction control commands (<command>BEGIN</>,
+     <command>COMMIT</>, etc) and commands that cannot be executed inside a
+     transaction block (such as <command>VACUUM</>).  This is because the
+     script file is implicitly executed within a transaction block.
+    </para>
+
+    <para>
+     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 <productname>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 <varname>comment</> parameter
+     in the control file, but instead use <command>COMMENT ON EXTENSION</>
+     within the script file to set the comment.
+    </para>
+
+   </sect2>
+
+   <sect2>
+    <title>Extension Relocatability</title>
+
+    <para>
+     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:
+    </para>
 
     <itemizedlist>
      <listitem>
       <para>
-        All functions accessed by the backend must present a C interface
-        to the backend;  these C functions can then call C++ functions.
-        For example, <literal>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.
-      </para>
-     </listitem>
-     <listitem>
-      <para>
-       Free memory using the appropriate deallocation method.  For example,
-       most backend memory is allocated using <function>palloc()</>, so use
-       <function>pfree()</> to free it, i.e. using C++
-       <function>delete()</> in such cases will fail.
+       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 <command>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 <literal>relocatable = true</> in its control
+       file.
       </para>
      </listitem>
+
      <listitem>
       <para>
-       Prevent exceptions from propagating into the C code (use a
-       catch-all block at the top level of all <literal>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 <option>-fno-exceptions</> to eliminate exceptions entirely;
-       in such cases, you must check for failures in your C++ code, e.g.
-       check for NULL returned by <function>new()</>.
+       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 <literal>search_path</> properties for SQL functions.
+       For such an extension, set <literal>relocatable = false</> in its
+       control file, and use <literal>@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
+       <literal>SCHEMA</> option of <command>CREATE EXTENSION</>.
       </para>
      </listitem>
+
      <listitem>
       <para>
-       If calling backend functions from C++ code, be sure that the
-       C++ call stack contains only plain old data structures
-       (<acronym>POD</>).  This is necessary because backend errors
-       generate a distant <function>longjmp()</> that does not properly
-       unroll a C++ call stack with non-POD objects.
+       If the extension does not support relocation at all, set
+       <literal>relocatable = false</> in its control file, and also set
+       <literal>schema</> to the name of the intended target schema.  This
+       will prevent use of the <literal>SCHEMA</> option of <command>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 <literal>@extschema@</>.  The <literal>@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.
       </para>
      </listitem>
     </itemizedlist>
+
+    <para>
+     In all cases, the script file will be executed with
+     <xref linkend="guc-search-path"> initially set to point to the target
+     schema; that is, <command>CREATE EXTENSION</> does the equivalent of
+     this:
+<programlisting>
+SET LOCAL search_path TO @extschema@;
+</programlisting>
+     This allows the objects created by the script file to go into the target
+     schema.  The script file can change <varname>search_path</> if it wishes,
+     but that is generally undesirable.  <varname>search_path</> is restored
+     to its previous setting upon completion of <command>CREATE EXTENSION</>.
+    </para>
+
+    <para>
+     The target schema is determined by the <varname>schema</> parameter in
+     the control file if that is given, otherwise by the <literal>SCHEMA</>
+     option of <command>CREATE EXTENSION</> if that is given, otherwise the
+     current default object creation schema (the first one in the caller's
+     <varname>search_path</>).  When the control file <varname>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.
+    </para>
+
+    <para>
+     If any prerequisite extensions are listed in <varname>requires</varname>
+     in the control file, their target schemas are appended to the initial
+     setting of <varname>search_path</>.  This allows their objects to be
+     visible to the new extension's script file.
+    </para>
+
+    <para>
+     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 <varname>search_path</> during creation of dependent
+     extensions.
+    </para>
+   </sect2>
+
+   <sect2>
+    <title>Extension Configuration Tables</title>
+
+    <para>
+     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
+     <application>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.
+    </para>
+
+    <para>
+     To solve this problem, an extension's script file can mark a table
+     it has created as a configuration table, which will cause
+     <application>pg_dump</> to include the table's contents (not its
+     definition) in dumps.  To do that, call the function
+     <function>pg_extension_config_dump(regclass, text)</> after creating the
+     table, for example
+<programlisting>
+CREATE TABLE my_config (key text, value text);
+
+SELECT pg_catalog.pg_extension_config_dump('my_config', '');
+</programlisting>
+     Any number of tables can be marked this way.
+    </para>
+
+    <para>
+     When the second argument of <function>pg_extension_config_dump</> is
+     an empty string, the entire contents of the table are dumped by
+     <application>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 <function>pg_extension_config_dump</> provides
+     a <literal>WHERE</> condition that selects the data to be dumped.
+     For example, you might do
+<programlisting>
+CREATE TABLE my_config (key text, value text, standard_entry boolean);
+
+SELECT pg_catalog.pg_extension_config_dump('my_config', 'WHERE NOT standard_entry');
+</programlisting>
+     and then make sure that <structfield>standard_entry</> is true only
+     in the rows created by the extension's script.
+    </para>
+
+    <para>
+     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.
+    </para>
+   </sect2>
+
+   <sect2>
+    <title>Extension Example</title>
+
+    <para>
+     Here is a complete example of an <acronym>SQL</>-only
+     extension, a two-element composite type that can store any type of value
+     in its slots, which are named <quote>k</> and <quote>v</>.  Non-text
+     values are automatically coerced to text for storage.
+    </para>
+
+    <para>
+     The script file <filename>pair.sql</> looks like this:
+
+<programlisting><![CDATA[
+CREATE TYPE pair AS ( k text, v text );
+
+CREATE OR REPLACE FUNCTION pair(anyelement, text)
+RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+CREATE OR REPLACE FUNCTION pair(text, anyelement)
+RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+CREATE OR REPLACE FUNCTION pair(anyelement, anyelement)
+RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+CREATE OR REPLACE FUNCTION pair(text, text)
+RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair;';
+
+CREATE OPERATOR ~> (LEFTARG = text, RIGHTARG = anyelement, PROCEDURE = pair);
+CREATE OPERATOR ~> (LEFTARG = anyelement, RIGHTARG = text, PROCEDURE = pair);
+CREATE OPERATOR ~> (LEFTARG = anyelement, RIGHTARG = anyelement, PROCEDURE = pair);
+CREATE OPERATOR ~> (LEFTARG = text, RIGHTARG = text, PROCEDURE = pair);
+]]>
+</programlisting>
+    </para>
+
+    <para>
+     The control file <filename>pair.control</> looks like this:
+
+<programlisting>
+# pair extension
+comment = 'A key/value pair data type'
+version = '0.1.2'
+relocatable = true
+</programlisting>
+    </para>
+
+    <para>
+     While you hardly need a makefile to install these two files into the
+     correct directory, you could use a <filename>Makefile</> containing this:
+
+<programlisting>
+EXTENSION = pair
+DATA = pair.sql
+
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+</programlisting>
+
+     This makefile relies on <acronym>PGXS</acronym>, which is described
+     in <xref linkend="extend-pgxs">.  The command <literal>make install</>
+     will then install the control and script files into the correct
+     directory as reported by <application>pg_config</>.
+    </para>
+
+    <para>
+     Once the files are installed, use the
+     <xref linkend="sql-createextension"> command to load the objects into
+     any particular database.
+    </para>
+   </sect2>
+  </sect1>
+
+  <sect1 id="extend-pgxs">
+   <title>Extension Building Infrastructure</title>
+
+   <indexterm zone="extend-pgxs">
+    <primary>pgxs</primary>
+   </indexterm>
+
+   <para>
+    If you are thinking about distributing your
+    <productname>PostgreSQL</> extension modules, setting up a
+    portable build system for them can be fairly difficult.  Therefore
+    the <productname>PostgreSQL</> installation provides a build
+    infrastructure for extensions, called <acronym>PGXS</acronym>, so
+    that simple extension modules can be built simply against an
+    already installed server.  <acronym>PGXS</acronym> is mainly intended
+    for extensions that include C code, although it can be used for
+    pure-SQL extensions too.  Note that <acronym>PGXS</acronym> is not
+    intended to be a universal build system framework that can be used
+    to build any software interfacing to <productname>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.
    </para>
 
    <para>
-    In summary, it is best to place C++ code behind a wall of
-    <literal>extern C</> functions that interface to the backend,
-    and avoid exception, memory, and call stack leakage.
+    To use the <acronym>PGXS</acronym> infrastructure for your extension,
+    you must write a simple makefile.
+    In the makefile, you need to set some variables
+    and finally include the global <acronym>PGXS</acronym> makefile.
+    Here is an example that builds an extension module named
+    <literal>isbn_issn</literal>, consisting of a shared library containing
+    some C code, an extension control file, a SQL script, and a documentation
+    text file:
+<programlisting>
+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)
+</programlisting>
+    The last three lines should always be the same.  Earlier in the
+    file, you assign variables or add custom
+    <application>make</application> rules.
    </para>
+
+   <para>
+    Set one of these three variables to specify what is built:
+
+    <variablelist>
+     <varlistentry>
+      <term><varname>MODULES</varname></term>
+      <listitem>
+       <para>
+        list of shared-library objects to be built from source files with same
+        stem (do not include library suffixes in this list)
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>MODULE_big</varname></term>
+      <listitem>
+       <para>
+        a shared library to build from multiple source files
+        (list object files in <varname>OBJS</varname>)
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>PROGRAM</varname></term>
+      <listitem>
+       <para>
+        an executable program to build
+        (list object files in <varname>OBJS</varname>)
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+
+    The following variables can also be set:
+
+    <variablelist>
+     <varlistentry>
+      <term><varname>MODULEDIR</varname></term>
+      <listitem>
+       <para>
+        subdirectory into which EXTENSION, DATA and DOCS files should be
+        installed (if not set, default is <literal>contrib</literal>)
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>EXTENSION</varname></term>
+      <listitem>
+       <para>
+        extension name(s); for each name you must provide an
+        <literal><replaceable>extension</replaceable>.control</literal> file,
+        which will be installed into
+        <literal><replaceable>prefix</replaceable>/share/$MODULEDIR</literal>
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>DATA</varname></term>
+      <listitem>
+       <para>
+        random files to install into <literal><replaceable>prefix</replaceable>/share/$MODULEDIR</literal>
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>DATA_built</varname></term>
+      <listitem>
+       <para>
+        random files to install into
+        <literal><replaceable>prefix</replaceable>/share/$MODULEDIR</literal>,
+        which need to be built first
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>DATA_TSEARCH</varname></term>
+      <listitem>
+       <para>
+        random files to install under
+        <literal><replaceable>prefix</replaceable>/share/tsearch_data</literal>
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>DOCS</varname></term>
+      <listitem>
+       <para>
+        random files to install under
+        <literal><replaceable>prefix</replaceable>/doc/$MODULEDIR</literal>
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>SCRIPTS</varname></term>
+      <listitem>
+       <para>
+        script files (not binaries) to install into
+        <literal><replaceable>prefix</replaceable>/bin</literal>
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>SCRIPTS_built</varname></term>
+      <listitem>
+       <para>
+        script files (not binaries) to install into
+        <literal><replaceable>prefix</replaceable>/bin</literal>,
+        which need to be built first
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>REGRESS</varname></term>
+      <listitem>
+       <para>
+        list of regression test cases (without suffix), see below
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>EXTRA_CLEAN</varname></term>
+      <listitem>
+       <para>
+        extra files to remove in <literal>make clean</literal>
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>PG_CPPFLAGS</varname></term>
+      <listitem>
+       <para>
+        will be added to <varname>CPPFLAGS</varname>
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>PG_LIBS</varname></term>
+      <listitem>
+       <para>
+        will be added to <varname>PROGRAM</varname> link line
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>SHLIB_LINK</varname></term>
+      <listitem>
+       <para>
+        will be added to <varname>MODULE_big</varname> link line
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>PG_CONFIG</varname></term>
+      <listitem>
+       <para>
+        path to <application>pg_config</> program for the
+        <productname>PostgreSQL</productname> installation to build against
+        (typically just <literal>pg_config</> to use the first one in your
+        <varname>PATH</>)
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+   </para>
+
+   <para>
+    Put this makefile as <literal>Makefile</literal> in the directory
+    which holds your extension. Then you can do
+    <literal>make</literal> to compile, and then <literal>make
+    install</literal> to install your module.  By default, the extension is
+    compiled and installed for the
+    <productname>PostgreSQL</productname> installation that
+    corresponds to the first <command>pg_config</command> program
+    found in your <varname>PATH</>.  You can use a different installation by
+    setting <varname>PG_CONFIG</varname> to point to its
+    <command>pg_config</command> program, either within the makefile
+    or on the <literal>make</literal> command line.
+   </para>
+
+   <caution>
+    <para>
+     Changing <varname>PG_CONFIG</varname> only works when building
+     against <productname>PostgreSQL</productname> 8.3 or later.
+     With older releases it does not work to set it to anything except
+     <literal>pg_config</>; you must alter your <varname>PATH</>
+     to select the installation to build against.
+    </para>
+   </caution>
+
+   <para>
+    The scripts listed in the <varname>REGRESS</> variable are used for
+    regression testing of your module, which can be invoked by <literal>make
+    installcheck</literal> after doing <literal>make install</>.  For this to
+    work you must have a running <productname>PostgreSQL</productname> server.
+    The script files listed in <varname>REGRESS</> must appear in a
+    subdirectory named <literal>sql/</literal> in your extension's directory.
+    These files must have extension <literal>.sql</literal>, which must not be
+    included in the <varname>REGRESS</varname> list in the makefile.  For each
+    test there should also be a file containing the expected output in a
+    subdirectory named <literal>expected/</literal>, with the same stem and
+    extension <literal>.out</literal>.  <literal>make installcheck</literal>
+    executes each test script with <application>psql</>, and compares the
+    resulting output to the matching expected file.  Any differences will be
+    written to the file <literal>regression.diffs</literal> in <command>diff
+    -c</command> format.  Note that trying to run a test that is missing its
+    expected file will be reported as <quote>trouble</quote>, so make sure you
+    have all expected files.
+   </para>
+
+   <tip>
+    <para>
+     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 <literal>results/</literal>
+     directory, then copy them to <literal>expected/</literal> if they match
+     what you expect from the test.
+    </para>
+
+   </tip>
   </sect1>
 
  </chapter>
index c44d11ef91b916523502454dfc18b20d92f1aab8..ba85cae0837e5c6246ac6ad832c42c8187a39eb7 100644 (file)
@@ -11,6 +11,7 @@ Complete list of usable sgml source files in this directory.
 <!entity alterDatabase      system "alter_database.sgml">
 <!entity alterDefaultPrivileges system "alter_default_privileges.sgml">
 <!entity alterDomain        system "alter_domain.sgml">
+<!entity alterExtension     system "alter_extension.sgml">
 <!entity alterForeignDataWrapper system "alter_foreign_data_wrapper.sgml">
 <!entity alterForeignTable  system "alter_foreign_table.sgml">
 <!entity alterFunction      system "alter_function.sgml">
@@ -50,6 +51,7 @@ Complete list of usable sgml source files in this directory.
 <!entity createConversion   system "create_conversion.sgml">
 <!entity createDatabase     system "create_database.sgml">
 <!entity createDomain       system "create_domain.sgml">
+<!entity createExtension    system "create_extension.sgml">
 <!entity createForeignDataWrapper system "create_foreign_data_wrapper.sgml">
 <!entity createForeignTable system "create_foreign_table.sgml">
 <!entity createFunction     system "create_function.sgml">
@@ -86,6 +88,7 @@ Complete list of usable sgml source files in this directory.
 <!entity dropConversion     system "drop_conversion.sgml">
 <!entity dropDatabase       system "drop_database.sgml">
 <!entity dropDomain         system "drop_domain.sgml">
+<!entity dropExtension      system "drop_extension.sgml">
 <!entity dropForeignDataWrapper system "drop_foreign_data_wrapper.sgml">
 <!entity dropForeignTable   system "drop_foreign_table.sgml">
 <!entity dropFunction       system "drop_function.sgml">
diff --git a/doc/src/sgml/ref/alter_extension.sgml b/doc/src/sgml/ref/alter_extension.sgml
new file mode 100644 (file)
index 0000000..1b29d27
--- /dev/null
@@ -0,0 +1,98 @@
+<!--
+doc/src/sgml/ref/alter_extension.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-ALTEREXTENSION">
+ <refmeta>
+  <refentrytitle>ALTER EXTENSION</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>ALTER EXTENSION</refname>
+  <refpurpose>
+   change the definition of an extension
+  </refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-alterextension">
+  <primary>ALTER EXTENSION</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+ALTER EXTENSION <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replaceable class="PARAMETER">new_schema</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>ALTER EXTENSION</command> changes the definition of an existing extension.
+   Currently there is only one subform:
+
+   <variablelist>
+   <varlistentry>
+    <term><literal>SET SCHEMA</literal></term>
+    <listitem>
+     <para>
+      This form moves the extension's objects into another schema. The
+      extension has to be <firstterm>relocatable</> for this command to
+      succeed. See <xref linkend="extend-extensions"> for details.
+     </para>
+    </listitem>
+   </varlistentry>
+   </variablelist>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+   <para>
+    <variablelist>
+     <varlistentry>
+      <term><replaceable class="PARAMETER">name</replaceable></term>
+      <listitem>
+       <para>
+        The name of an installed extension.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="PARAMETER">new_schema</replaceable></term>
+      <listitem>
+       <para>
+        The new schema for the extension.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   To change the schema of the extension <literal>hstore</literal>
+   to <literal>utils</literal>:
+<programlisting>
+ALTER EXTENSION hstore SET SCHEMA utils;
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1 id="SQL-ALTEREXTENSION-see-also">
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createextension"></member>
+   <member><xref linkend="sql-dropextension"></member>
+  </simplelist>
+ </refsect1>
+</refentry>
index f1a1605df3c48a9039fa94970998de63a0880e77..e1fe0c16f9a2c0bcf23cdc799376e0d934b60950 100644 (file)
@@ -31,6 +31,7 @@ COMMENT ON
   CONVERSION <replaceable class="PARAMETER">object_name</replaceable> |
   DATABASE <replaceable class="PARAMETER">object_name</replaceable> |
   DOMAIN <replaceable class="PARAMETER">object_name</replaceable> |
+  EXTENSION <replaceable class="PARAMETER">object_name</replaceable> |
   FOREIGN TABLE <replaceable class="PARAMETER">object_name</replaceable> |
   FUNCTION <replaceable class="PARAMETER">function_name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] ) |
   INDEX <replaceable class="PARAMETER">object_name</replaceable> |
diff --git a/doc/src/sgml/ref/create_extension.sgml b/doc/src/sgml/ref/create_extension.sgml
new file mode 100644 (file)
index 0000000..961cab3
--- /dev/null
@@ -0,0 +1,118 @@
+<!--
+doc/src/sgml/ref/create_extension.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-CREATEEXTENSION">
+ <refmeta>
+  <refentrytitle>CREATE EXTENSION</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE EXTENSION</refname>
+  <refpurpose>install an extension</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-createextension">
+  <primary>CREATE EXTENSION</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE EXTENSION <replaceable class="parameter">extension_name</replaceable>
+    [ WITH ] [ SCHEMA [=] <replaceable class="parameter">schema</replaceable> ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE EXTENSION</command> loads a new extension into the current
+   database.  There must not be an extension of the same name already loaded.
+  </para>
+
+  <para>
+   Loading an extension essentially amounts to running the extension's script
+   file.  The script will typically create new <acronym>SQL</> objects such as
+   functions, data types, operators and index support methods.
+   <command>CREATE EXTENSION</command> additionally records the identities
+   of all the created objects, so that they can be dropped again if
+   <command>DROP EXTENSION</command> is issued.
+  </para>
+
+  <para>
+   For information about writing new extensions, see
+   <xref linkend="extend-extensions">.
+  </para>
+
+  <para>
+   Only superusers can execute <command>CREATE EXTENSION</command>.
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+    <variablelist>
+     <varlistentry>
+      <term><replaceable class="parameter">extension_name</replaceable></term>
+      <listitem>
+       <para>
+        The name of the extension to be
+        installed. <productname>PostgreSQL</productname> will create the
+        extension using details from the file
+        <literal>SHAREDIR/contrib/</literal><replaceable class="parameter">extension</replaceable><literal>.control</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="parameter">schema</replaceable></term>
+      <listitem>
+       <para>
+        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.
+       </para>
+      </listitem>
+     </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Install the <link linkend="hstore">hstore</link> extension into the
+   current database:
+<programlisting>
+CREATE EXTENSION hstore;
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>CREATE EXTENSION</command> is a <productname>PostgreSQL</>
+   extension.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-alterextension"></member>
+   <member><xref linkend="sql-dropextension"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_extension.sgml b/doc/src/sgml/ref/drop_extension.sgml
new file mode 100644 (file)
index 0000000..1e09ec4
--- /dev/null
@@ -0,0 +1,121 @@
+<!--
+doc/src/sgml/ref/drop_extension.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-DROPEXTENSION">
+ <refmeta>
+  <refentrytitle>DROP EXTENSION</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP EXTENSION</refname>
+  <refpurpose>remove an extension</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-dropextension">
+  <primary>DROP EXTENSION</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP EXTENSION [ IF EXISTS ] <replaceable class="PARAMETER">extension_name</replaceable> [, ...] [ CASCADE | RESTRICT ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP EXTENSION</command> removes extensions from the database.
+   Dropping an extension causes its component objects to be dropped as well.
+  </para>
+
+  <para>
+   An extension can only be dropped by a superuser.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the extension does not exist. A notice is issued
+      in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="PARAMETER">extension_name</replaceable></term>
+    <listitem>
+     <para>
+      The name of an installed extension.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>CASCADE</literal></term>
+    <listitem>
+     <para>
+      Automatically drop objects that depend on the extension.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESTRICT</literal></term>
+    <listitem>
+     <para>
+      Refuse to drop the extension if any objects depend on it (other than
+      its own member objects and other extensions listed in the same
+      <command>DROP</> command).  This is the default.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   To remove the extension <literal>hstore</literal> from the current
+   database:
+<programlisting>
+DROP EXTENSION hstore;
+</programlisting>
+   This command will fail if any of <literal>hstore</literal>'s objects
+   are in use in the database, for example if any tables have columns
+   of the <type>hstore</> type.  Add the <literal>CASCADE</> option to
+   forcibly remove those dependent objects as well.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>DROP EXTENSION</command> is a <productname>PostgreSQL</>
+   extension.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createextension"></member>
+   <member><xref linkend="sql-alterextension"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
index eacae71cdc7fb3cc71b510289e29804a58319298..cdf1abfa95639f46cbc027600e7c19ed8a16cd2c 100644 (file)
@@ -1357,7 +1357,6 @@ testdb=&gt;
         </listitem>
       </varlistentry>
 
-
       <varlistentry>
         <term><literal>\du[+] [ <link linkend="APP-PSQL-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
         <listitem>
@@ -1371,6 +1370,19 @@ testdb=&gt;
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><literal>\dx[+] [ <link linkend="APP-PSQL-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+        <listitem>
+        <para>
+        Lists installed extensions.
+        If <replaceable class="parameter">pattern</replaceable>
+        is specified, only those extensions whose names match the pattern
+        are listed.
+        If the form <literal>\dx+</literal> is used, all the objects belonging
+        to each matching extension are listed.
+        </para>
+        </listitem>
+      </varlistentry>
 
       <varlistentry>
         <term><literal>\edit</> (or <literal>\e</>) <literal> <optional> <replaceable class="parameter">filename</> </optional> <optional> <replaceable class="parameter">line_number</> </optional> </literal></term>
index 6ee8e5bcff82dab89d70cd483aa5c47866e52dc7..47cd01f58e2782973426030991ec9cd60ae8e88b 100644 (file)
@@ -39,6 +39,7 @@
    &alterDatabase;
    &alterDefaultPrivileges;
    &alterDomain;
+   &alterExtension;
    &alterForeignDataWrapper;
    &alterForeignTable;
    &alterFunction;
@@ -78,6 +79,7 @@
    &createConversion;
    &createDatabase;
    &createDomain;
+   &createExtension;
    &createForeignDataWrapper;
    &createForeignTable;
    &createFunction;
    &dropConversion;
    &dropDatabase;
    &dropDomain;
+   &dropExtension;
    &dropForeignDataWrapper;
    &dropForeignTable;
    &dropFunction;
index 2288f1b0e6409235e2e6cbd8accb6a0e12692554..4902c058a96647153172f000d9bbadcb9782ca25 100644 (file)
@@ -3520,9 +3520,8 @@ if TG_OP = 'INSERT' and NEW.col1 = ... then
 
      <listitem>
       <para>
-       Add data and documentation installation location control to <link
-       linkend="xfunc-c-pgxs"><acronym>PGXS</></link> Makefiles
-       (Mark Cave-Ayland)
+       Add data and documentation installation location control to
+       <acronym>PGXS</> Makefiles (Mark Cave-Ayland)
       </para>
      </listitem>
 
index 4ad50ec0cb5494c9ace89e2e9ba73ee6bdb04f20..4f2c23fab7a2780df841112d26c16f9a0df1296d 100644 (file)
@@ -2392,273 +2392,6 @@ concat_text(PG_FUNCTION_ARGS)
 
 &dfunc;
 
-   <sect2 id="xfunc-c-pgxs">
-    <title>Extension Building Infrastructure</title>
-
-   <indexterm zone="xfunc-c-pgxs">
-    <primary>pgxs</primary>
-   </indexterm>
-
-   <para>
-    If you are thinking about distributing your
-    <productname>PostgreSQL</> extension modules, setting up a
-    portable build system for them can be fairly difficult.  Therefore
-    the <productname>PostgreSQL</> installation provides a build
-    infrastructure for extensions, called <acronym>PGXS</acronym>, 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 <productname>PostgreSQL</>;
-    it simply automates common build rules for simple server extension
-    modules.  For more complicated packages, you need to write your
-    own build system.
-   </para>
-
-   <para>
-    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 <acronym>PGXS</acronym> makefile.
-    Here is an example that builds an extension module named
-    <literal>isbn_issn</literal> consisting of a shared library, an
-    SQL script, and a documentation text file:
-<programlisting>
-MODULES = isbn_issn
-DATA_built = isbn_issn.sql
-DOCS = README.isbn_issn
-
-PG_CONFIG = pg_config
-PGXS := $(shell $(PG_CONFIG) --pgxs)
-include $(PGXS)
-</programlisting>
-    The last three lines should always be the same.  Earlier in the
-    file, you assign variables or add custom
-    <application>make</application> rules.
-   </para>
-
-   <para>
-    Set one of these three variables to specify what is built:
-
-    <variablelist>
-     <varlistentry>
-      <term><varname>MODULES</varname></term>
-      <listitem>
-       <para>
-        list of shared objects to be built from source files with same
-        stem (do not include suffix in this list)
-       </para>
-      </listitem>
-     </varlistentry>
-
-     <varlistentry>
-      <term><varname>MODULE_big</varname></term>
-      <listitem>
-       <para>
-        a shared object to build from multiple source files
-        (list object files in <varname>OBJS</varname>)
-       </para>
-      </listitem>
-     </varlistentry>
-
-     <varlistentry>
-      <term><varname>PROGRAM</varname></term>
-      <listitem>
-       <para>
-        a binary program to build
-        (list object files in <varname>OBJS</varname>)
-       </para>
-      </listitem>
-     </varlistentry>
-    </variablelist>
-
-    The following variables can also be set:
-
-    <variablelist>
-     <varlistentry>
-      <term><varname>MODULEDIR</varname></term>
-      <listitem>
-       <para>
-        subdirectory into which DATA and DOCS files should be
-        installed (if not set, default is <literal>contrib</literal>)
-       </para>
-      </listitem>
-     </varlistentry>
-
-     <varlistentry>
-      <term><varname>DATA</varname></term>
-      <listitem>
-       <para>
-        random files to install into <literal><replaceable>prefix</replaceable>/share/$MODULEDIR</literal>
-       </para>
-      </listitem>
-     </varlistentry>
-
-     <varlistentry>
-      <term><varname>DATA_built</varname></term>
-      <listitem>
-       <para>
-        random files to install into
-        <literal><replaceable>prefix</replaceable>/share/$MODULEDIR</literal>,
-        which need to be built first
-       </para>
-      </listitem>
-     </varlistentry>
-
-     <varlistentry>
-      <term><varname>DATA_TSEARCH</varname></term>
-      <listitem>
-       <para>
-        random files to install under
-        <literal><replaceable>prefix</replaceable>/share/tsearch_data</literal>
-       </para>
-      </listitem>
-     </varlistentry>
-
-     <varlistentry>
-      <term><varname>DOCS</varname></term>
-      <listitem>
-       <para>
-        random files to install under
-        <literal><replaceable>prefix</replaceable>/doc/$MODULEDIR</literal>
-       </para>
-      </listitem>
-     </varlistentry>
-
-     <varlistentry>
-      <term><varname>SCRIPTS</varname></term>
-      <listitem>
-       <para>
-        script files (not binaries) to install into
-        <literal><replaceable>prefix</replaceable>/bin</literal>
-       </para>
-      </listitem>
-     </varlistentry>
-
-     <varlistentry>
-      <term><varname>SCRIPTS_built</varname></term>
-      <listitem>
-       <para>
-        script files (not binaries) to install into
-        <literal><replaceable>prefix</replaceable>/bin</literal>,
-        which need to be built first
-       </para>
-      </listitem>
-     </varlistentry>
-
-     <varlistentry>
-      <term><varname>REGRESS</varname></term>
-      <listitem>
-       <para>
-        list of regression test cases (without suffix), see below
-       </para>
-      </listitem>
-     </varlistentry>
-
-     <varlistentry>
-      <term><varname>EXTRA_CLEAN</varname></term>
-      <listitem>
-       <para>
-        extra files to remove in <literal>make clean</literal>
-       </para>
-      </listitem>
-     </varlistentry>
-
-     <varlistentry>
-      <term><varname>PG_CPPFLAGS</varname></term>
-      <listitem>
-       <para>
-        will be added to <varname>CPPFLAGS</varname>
-       </para>
-      </listitem>
-     </varlistentry>
-
-     <varlistentry>
-      <term><varname>PG_LIBS</varname></term>
-      <listitem>
-       <para>
-        will be added to <varname>PROGRAM</varname> link line
-       </para>
-      </listitem>
-     </varlistentry>
-
-     <varlistentry>
-      <term><varname>SHLIB_LINK</varname></term>
-      <listitem>
-       <para>
-        will be added to <varname>MODULE_big</varname> link line
-       </para>
-      </listitem>
-     </varlistentry>
-
-     <varlistentry>
-      <term><varname>PG_CONFIG</varname></term>
-      <listitem>
-       <para>
-        path to <application>pg_config</> program for the
-        <productname>PostgreSQL</productname> installation to build against
-        (typically just <literal>pg_config</> to use the first one in your
-        <varname>PATH</>)
-       </para>
-      </listitem>
-     </varlistentry>
-    </variablelist>
-   </para>
-
-   <para>
-    Put this makefile as <literal>Makefile</literal> in the directory
-    which holds your extension. Then you can do
-    <literal>make</literal> to compile, and later <literal>make
-    install</literal> to install your module.  By default, the extension is
-    compiled and installed for the
-    <productname>PostgreSQL</productname> installation that
-    corresponds to the first <command>pg_config</command> program
-    found in your path.  You can use a different installation by
-    setting <varname>PG_CONFIG</varname> to point to its
-    <command>pg_config</command> program, either within the makefile
-    or on the <literal>make</literal> command line.
-   </para>
-
-   <caution>
-    <para>
-     Changing <varname>PG_CONFIG</varname> only works when building
-     against <productname>PostgreSQL</productname> 8.3 or later.
-     With older releases it does not work to set it to anything except
-     <literal>pg_config</>; you must alter your <varname>PATH</>
-     to select the installation to build against.
-    </para>
-   </caution>
-
-   <para>
-    The scripts listed in the <varname>REGRESS</> variable are used for
-    regression testing of your module, just like <literal>make
-    installcheck</literal> is used for the main
-    <productname>PostgreSQL</productname> server.  For this to work you need
-    to have a subdirectory named <literal>sql/</literal> 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 <literal>.sql</literal>, which
-    should not be included in the <varname>REGRESS</varname> list in the
-    makefile.  For each test there should be a file containing the expected
-    result in a subdirectory named <literal>expected/</literal>, with extension
-    <literal>.out</literal>.  The tests are run by executing <literal>make
-    installcheck</literal>, and the resulting output will be compared to the
-    expected files.  The differences will be written to the file
-    <literal>regression.diffs</literal> in <command>diff -c</command> format.
-    Note that trying to run a test which is missing the expected file will be
-    reported as <quote>trouble</quote>, so make sure you have all expected
-    files.
-   </para>
-
-   <tip>
-    <para>
-     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 <literal>results/</literal> directory), and copying them to
-     <literal>expected/</literal> if they match what you want from the test.
-    </para>
-
-   </tip>
-  </sect2>
-
-
    <sect2>
     <title>Composite-type Arguments</title>
 
@@ -3385,4 +3118,68 @@ if (!ptr)
 </programlisting>
     </para>
    </sect2>
+
+   <sect2 id="extend-Cpp">
+    <title>Using C++ for Extensibility</title>
+
+    <indexterm zone="extend-Cpp">
+     <primary>C++</primary>
+    </indexterm>
+
+    <para>
+     Although the <productname>PostgreSQL</productname> backend is written in
+     C, it is possible to write extensions in C++ if these guidelines are
+     followed:
+
+     <itemizedlist>
+      <listitem>
+       <para>
+         All functions accessed by the backend must present a C interface
+         to the backend;  these C functions can then call C++ functions.
+         For example, <literal>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.
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+        Free memory using the appropriate deallocation method.  For example,
+        most backend memory is allocated using <function>palloc()</>, so use
+        <function>pfree()</> to free it.  Using C++
+        <function>delete</> in such cases will fail.
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+        Prevent exceptions from propagating into the C code (use a catch-all
+        block at the top level of all <literal>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
+        <option>-fno-exceptions</> to eliminate exceptions entirely; in such
+        cases, you must check for failures in your C++ code, e.g.  check for
+        NULL returned by <function>new()</>.
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+        If calling backend functions from C++ code, be sure that the
+        C++ call stack contains only plain old data structures
+        (<acronym>POD</>).  This is necessary because backend errors
+        generate a distant <function>longjmp()</> that does not properly
+        unroll a C++ call stack with non-POD objects.
+       </para>
+      </listitem>
+     </itemizedlist>
+    </para>
+
+    <para>
+     In summary, it is best to place C++ code behind a wall of
+     <literal>extern C</> functions that interface to the backend,
+     and avoid exception, memory, and call stack leakage.
+    </para>
+   </sect2>
+
   </sect1>
index 7cffde1769231c122be3a48f30c7b411da289386..45aca8dd7f74b45fab8e405eeaca9d42b2a7437f 100644 (file)
@@ -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 \
index ba0ea178ac016f21206bc22e9b6ffd6867b25c18..5c5f750a06915aabd9c0653b1820fcd2a3cb543e 100644 (file)
@@ -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,
index 14c69f3faa88a574ed0afef4ca04d7ae940ea816..d9b272a71220a70fe28518642beb79986b1de5aa 100644 (file)
@@ -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;
index c4608f7f1732e2f9022a29c4c68180dac20c2183..82989acc08839aff0f21cc50f77d43d17c67aff1 100644 (file)
@@ -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);
        }
index b2f66d75ac52e7ada95fa26fbd05c4a9b804236d..1ef6a9d24e2dca4bb9e3599d967d1d5fa3807c28 100644 (file)
@@ -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);
index 370051d91e32feadeb905bbfc9ad21634dc2024c..b2ce148d625ae151ef4fb69863a203bee5ce6dba 100644 (file)
@@ -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
  *
index c78aa019bff733160c4bb43fefbed87b93b45572..172f99196cc371c2a90ae9469134842581fe51d9 100644 (file)
@@ -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);
 
index c70483a7390afa1205a32fe3203476bac54eb15d..ccd0fe1997def001bb782f1f819d682657d76a64 100644 (file)
@@ -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);
 }
index 2ab87d2df5b02e45e5f86f645f139108c749d362..3f3877da286661e45e36a0addcebd1bc2c9f8ed2 100644 (file)
@@ -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 */
index 8ceaab1fb12d939229515d3a4b8171b62e983c40..9b574179ff9a80307d3950686c063cdff58de837 100644 (file)
@@ -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 */
index 4fa1453b14f7135644315636bbdb5c59a83144b1..987026c8358c65aa536be67647023eea468cdc3c 100644 (file)
@@ -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
index 9d2a7322457dd4b3a67b37b3a03a40b8d18f5e0a..0aadbc56adb20751b698057654bbefd979b05d92 100644 (file)
@@ -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 \
index 1c6ae0243e4cb2f20a840dc81af8320717b9e01d..2c9340accf12a33ed346606cac16e81540def39f 100644 (file)
@@ -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;
 }
 
 
index 59a439413e1be4e3dd2ad384b93835470294c1b6..4c4f356e79086699518651e6fb8fb52ca96be130 100644 (file)
@@ -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);
index 2e8c4df9272dd2433fd1e6b53d930541497ccc20..bbb3f344093b678d305cbd0194afa62b98b6ef0c 100644 (file)
@@ -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,
index da024df5f0ca82fc7745c4ab9ce0b21769f81fbc..b5e4420ca8d983464a695acfcd87b11cc709b3e5 100644 (file)
@@ -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 (file)
index 0000000..5003203
--- /dev/null
@@ -0,0 +1,1401 @@
+/*-------------------------------------------------------------------------
+ *
+ * extension.c
+ *       Commands to manipulate extensions
+ *
+ * Extensions in PostgreSQL allow management of collections of SQL objects.
+ *
+ * All we need internally to manage an extension is an OID so that the
+ * dependent objects can be associated with it.  An extension is created by
+ * populating the pg_extension catalog from a "control" file.
+ * The extension control file is parsed with the same parser we use for
+ * postgresql.conf and recovery.conf.  An extension also has an installation
+ * script file, containing SQL commands to create the extension's objects.
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *       src/backend/commands/extension.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <dirent.h>
+#include <unistd.h>
+
+#include "access/sysattr.h"
+#include "access/xact.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_depend.h"
+#include "catalog/pg_extension.h"
+#include "catalog/pg_namespace.h"
+#include "catalog/pg_type.h"
+#include "commands/alter.h"
+#include "commands/comment.h"
+#include "commands/extension.h"
+#include "commands/trigger.h"
+#include "executor/executor.h"
+#include "funcapi.h"
+#include "mb/pg_wchar.h"
+#include "miscadmin.h"
+#include "tcop/tcopprot.h"
+#include "tcop/utility.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/snapmgr.h"
+#include "utils/tqual.h"
+
+
+bool                   creating_extension = false;
+Oid                            CurrentExtensionObject = InvalidOid;
+
+/*
+ * Internal data structure to hold the results of parsing a control file
+ */
+typedef struct ExtensionControlFile
+{
+       char       *name;                       /* name of the extension */
+       char       *script;                     /* filename of the installation script */
+       char       *version;        /* version ID, if any */
+       char       *comment;            /* comment, if any */
+       char       *schema;                     /* target schema (allowed if !relocatable) */
+       bool            relocatable;    /* is ALTER EXTENSION SET SCHEMA supported? */
+       int                     encoding;               /* encoding of the script file, or -1 */
+       List       *requires;           /* names of prerequisite extensions */
+} ExtensionControlFile;
+
+
+/*
+ * get_extension_oid - given an extension name, look up the OID
+ *
+ * If missing_ok is false, throw an error if extension name not found.  If
+ * true, just return InvalidOid.
+ */
+Oid
+get_extension_oid(const char *extname, bool missing_ok)
+{
+       Oid                     result;
+       Relation        rel;
+       SysScanDesc scandesc;
+       HeapTuple       tuple;
+       ScanKeyData entry[1];
+
+       rel = heap_open(ExtensionRelationId, AccessShareLock);
+
+       ScanKeyInit(&entry[0],
+                               Anum_pg_extension_extname,
+                               BTEqualStrategyNumber, F_NAMEEQ,
+                               CStringGetDatum(extname));
+
+       scandesc = systable_beginscan(rel, ExtensionNameIndexId, true,
+                                                                 SnapshotNow, 1, entry);
+
+       tuple = systable_getnext(scandesc);
+
+       /* We assume that there can be at most one matching tuple */
+       if (HeapTupleIsValid(tuple))
+               result = HeapTupleGetOid(tuple);
+       else
+               result = InvalidOid;
+
+       systable_endscan(scandesc);
+
+       heap_close(rel, AccessShareLock);
+
+       if (!OidIsValid(result) && !missing_ok)
+        ereport(ERROR,
+                (errcode(ERRCODE_UNDEFINED_OBJECT),
+                 errmsg("extension \"%s\" does not exist",
+                        extname)));
+
+       return result;
+}
+
+/*
+ * get_extension_name - given an extension OID, look up the name
+ *
+ * Returns a palloc'd string, or NULL if no such extension.
+ */
+char *
+get_extension_name(Oid ext_oid)
+{
+       char       *result;
+       Relation        rel;
+       SysScanDesc scandesc;
+       HeapTuple       tuple;
+       ScanKeyData entry[1];
+
+       rel = heap_open(ExtensionRelationId, AccessShareLock);
+
+       ScanKeyInit(&entry[0],
+                               ObjectIdAttributeNumber,
+                               BTEqualStrategyNumber, F_OIDEQ,
+                               ObjectIdGetDatum(ext_oid));
+
+       scandesc = systable_beginscan(rel, ExtensionOidIndexId, true,
+                                                                 SnapshotNow, 1, entry);
+
+       tuple = systable_getnext(scandesc);
+
+       /* We assume that there can be at most one matching tuple */
+       if (HeapTupleIsValid(tuple))
+               result = pstrdup(NameStr(((Form_pg_extension) GETSTRUCT(tuple))->extname));
+       else
+               result = NULL;
+
+       systable_endscan(scandesc);
+
+       heap_close(rel, AccessShareLock);
+
+       return result;
+}
+
+/*
+ * get_extension_schema - given an extension OID, fetch its extnamespace
+ *
+ * Returns InvalidOid if no such extension.
+ */
+static Oid
+get_extension_schema(Oid ext_oid)
+{
+       Oid                     result;
+       Relation        rel;
+       SysScanDesc scandesc;
+       HeapTuple       tuple;
+       ScanKeyData entry[1];
+
+       rel = heap_open(ExtensionRelationId, AccessShareLock);
+
+       ScanKeyInit(&entry[0],
+                               ObjectIdAttributeNumber,
+                               BTEqualStrategyNumber, F_OIDEQ,
+                               ObjectIdGetDatum(ext_oid));
+
+       scandesc = systable_beginscan(rel, ExtensionOidIndexId, true,
+                                                                 SnapshotNow, 1, entry);
+
+       tuple = systable_getnext(scandesc);
+
+       /* We assume that there can be at most one matching tuple */
+       if (HeapTupleIsValid(tuple))
+               result = ((Form_pg_extension) GETSTRUCT(tuple))->extnamespace;
+       else
+               result = InvalidOid;
+
+       systable_endscan(scandesc);
+
+       heap_close(rel, AccessShareLock);
+
+       return result;
+}
+
+/*
+ * Utility functions to handle extension-related path names
+ */
+static bool
+is_extension_control_filename(const char *filename)
+{
+       const char *extension = strrchr(filename, '.');
+
+       return (extension != NULL) && (strcmp(extension, ".control") == 0);
+}
+
+static char *
+get_extension_control_directory(void)
+{
+       char            sharepath[MAXPGPATH];
+       char       *result;
+
+       get_share_path(my_exec_path, sharepath);
+       result = (char *) palloc(MAXPGPATH);
+       snprintf(result, MAXPGPATH, "%s/contrib", sharepath);
+
+       return result;
+}
+
+static char *
+get_extension_control_filename(const char *extname)
+{
+       char            sharepath[MAXPGPATH];
+       char       *result;
+
+       get_share_path(my_exec_path, sharepath);
+       result = (char *) palloc(MAXPGPATH);
+       snprintf(result, MAXPGPATH, "%s/contrib/%s.control", sharepath, extname);
+
+       return result;
+}
+
+/*
+ * Given a relative pathname such as "name.sql", return the full path to
+ * the script file.  If given an absolute name, just return it.
+ */
+static char *
+get_extension_absolute_path(const char *filename)
+{
+       char            sharepath[MAXPGPATH];
+       char       *result;
+
+       if (is_absolute_path(filename))
+               return pstrdup(filename);
+
+       get_share_path(my_exec_path, sharepath);
+       result = (char *) palloc(MAXPGPATH);
+    snprintf(result, MAXPGPATH, "%s/contrib/%s", sharepath, filename);
+
+       return result;
+}
+
+
+/*
+ * Read the control file for the specified extension.
+ *
+ * The control file is supposed to be very short, half a dozen lines, and
+ * reading it is only allowed to superuser, so we don't worry about
+ * memory allocation risks here.  Also note that we don't worry about
+ * what encoding it's in; all values are expected to be ASCII.
+ */
+static ExtensionControlFile *
+read_extension_control_file(const char *extname)
+{
+       char       *filename = get_extension_control_filename(extname);
+       FILE       *file;
+       ExtensionControlFile *control;
+       ConfigVariable *item,
+                                  *head = NULL,
+                                  *tail = NULL;
+
+       /*
+        * Parse the file content, using GUC's file parsing code
+        */
+       if ((file = AllocateFile(filename, "r")) == NULL)
+               ereport(ERROR,
+                               (errcode_for_file_access(),
+                                errmsg("could not open extension control file \"%s\": %m",
+                                               filename)));
+
+       ParseConfigFp(file, filename, 0, ERROR, &head, &tail);
+
+       FreeFile(file);
+
+       /*
+        * Set up default values.  Pointer fields are initially null.
+        */
+       control = (ExtensionControlFile *) palloc0(sizeof(ExtensionControlFile));
+       control->name = pstrdup(extname);
+       control->relocatable = false;
+       control->encoding = -1;
+
+       /*
+        * Convert the ConfigVariable list into ExtensionControlFile entries.
+        */
+       for (item = head; item != NULL; item = item->next)
+       {
+               if (strcmp(item->name, "script") == 0)
+               {
+                       control->script = pstrdup(item->value);
+               }
+               else if (strcmp(item->name, "version") == 0)
+               {
+                       control->version = pstrdup(item->value);
+               }
+               else if (strcmp(item->name, "comment") == 0)
+               {
+                       control->comment = pstrdup(item->value);
+               }
+               else if (strcmp(item->name, "schema") == 0)
+               {
+                       control->schema = pstrdup(item->value);
+               }
+               else if (strcmp(item->name, "relocatable") == 0)
+               {
+                       if (!parse_bool(item->value, &control->relocatable))
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                                errmsg("parameter \"%s\" requires a Boolean value",
+                                                               item->name)));
+               }
+               else if (strcmp(item->name, "encoding") == 0)
+               {
+                       control->encoding = pg_valid_server_encoding(item->value);
+                       if (control->encoding < 0)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_UNDEFINED_OBJECT),
+                                                errmsg("\"%s\" is not a valid encoding name",
+                                                               item->value)));
+               }
+               else if (strcmp(item->name, "requires") == 0)
+               {
+                       /* Need a modifiable copy of string */
+                       char       *rawnames = pstrdup(item->value);
+
+                       /* Parse string into list of identifiers */
+                       if (!SplitIdentifierString(rawnames, ',', &control->requires))
+                       {
+                               /* syntax error in name list */
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                                errmsg("parameter \"%s\" must be a list of extension names",
+                                                               item->name)));
+                       }
+               }
+               else
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("unrecognized parameter \"%s\" in file \"%s\"",
+                                                       item->name, filename)));
+       }
+
+       FreeConfigVariables(head);
+
+       if (control->relocatable && control->schema != NULL)
+               ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                errmsg("parameter \"schema\" cannot be specified when \"relocatable\" is true")));
+
+       /*
+        * script defaults to ${extension-name}.sql
+        */
+       if (control->script == NULL)
+       {
+               char    script[MAXPGPATH];
+
+               snprintf(script, MAXPGPATH, "%s.sql", control->name);
+               control->script = pstrdup(script);
+       }
+
+       return control;
+}
+
+/*
+ * Read the SQL script into a string, and convert to database encoding
+ */
+static char *
+read_extension_script_file(const ExtensionControlFile *control,
+                                                  const char *filename)
+{
+       int         src_encoding;
+       int         dest_encoding = GetDatabaseEncoding();
+       bytea      *content;
+       char       *src_str;
+       char       *dest_str;
+       int         len;
+
+       content = read_binary_file(filename, 0, -1);
+
+       /* use database encoding if not given */
+       if (control->encoding < 0)
+               src_encoding = dest_encoding;
+       else
+               src_encoding = control->encoding;
+
+       /* make sure that source string is valid in the expected encoding */
+       len = VARSIZE_ANY_EXHDR(content);
+       src_str = VARDATA_ANY(content);
+       pg_verify_mbstr_len(src_encoding, src_str, len, false);
+
+       /* convert the encoding to the database encoding */
+       dest_str = (char *) pg_do_encoding_conversion((unsigned char *) src_str,
+                                                                                                 len,
+                                                                                                 src_encoding,
+                                                                                                 dest_encoding);
+
+       /* if no conversion happened, we have to arrange for null termination */
+       if (dest_str == src_str)
+       {
+               dest_str = (char *) palloc(len + 1);
+               memcpy(dest_str, src_str, len);
+               dest_str[len] = '\0';
+       }
+
+       return dest_str;
+}
+
+/*
+ * Execute given SQL string.
+ *
+ * filename is used only to report errors.
+ *
+ * Note: it's tempting to just use SPI to execute the string, but that does
+ * not work very well.  The really serious problem is that SPI will parse,
+ * analyze, and plan the whole string before executing any of it; of course
+ * this fails if there are any plannable statements referring to objects
+ * created earlier in the script.  A lesser annoyance is that SPI insists
+ * on printing the whole string as errcontext in case of any error, and that
+ * could be very long.
+ */
+static void
+execute_sql_string(const char *sql, const char *filename)
+{
+       List       *raw_parsetree_list;
+       DestReceiver *dest;
+       ListCell   *lc1;
+
+       /*
+        * Parse the SQL string into a list of raw parse trees.
+        */
+       raw_parsetree_list = pg_parse_query(sql);
+
+       /* All output from SELECTs goes to the bit bucket */
+       dest = CreateDestReceiver(DestNone);
+
+       /*
+        * Do parse analysis, rule rewrite, planning, and execution for each raw
+        * parsetree.  We must fully execute each query before beginning parse
+        * analysis on the next one, since there may be interdependencies.
+        */
+       foreach(lc1, raw_parsetree_list)
+       {
+               Node       *parsetree = (Node *) lfirst(lc1);
+               List       *stmt_list;
+               ListCell   *lc2;
+
+               stmt_list = pg_analyze_and_rewrite(parsetree,
+                                                                                  sql,
+                                                                                  NULL,
+                                                                                  0);
+               stmt_list = pg_plan_queries(stmt_list, 0, NULL);
+
+               foreach(lc2, stmt_list)
+               {
+                       Node       *stmt = (Node *) lfirst(lc2);
+
+                       if (IsA(stmt, TransactionStmt))
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                errmsg("transaction control statements are not allowed within an extension script")));
+
+                       CommandCounterIncrement();
+
+                       PushActiveSnapshot(GetTransactionSnapshot());
+
+                       if (IsA(stmt, PlannedStmt) &&
+                               ((PlannedStmt *) stmt)->utilityStmt == NULL)
+                       {
+                               QueryDesc  *qdesc;
+
+                               qdesc = CreateQueryDesc((PlannedStmt *) stmt,
+                                                                               sql,
+                                                                               GetActiveSnapshot(), NULL,
+                                                                               dest, NULL, 0);
+
+                               AfterTriggerBeginQuery();
+                               ExecutorStart(qdesc, 0);
+                               ExecutorRun(qdesc, ForwardScanDirection, 0);
+                               AfterTriggerEndQuery(qdesc->estate);
+                               ExecutorEnd(qdesc);
+
+                               FreeQueryDesc(qdesc);
+                       }
+                       else
+                       {
+                               ProcessUtility(stmt,
+                                                          sql,
+                                                          NULL,
+                                                          false,       /* not top level */
+                                                          dest,
+                                                          NULL);
+                       }
+
+                       PopActiveSnapshot();
+               }
+       }
+
+       /* Be sure to advance the command counter after the last script command */
+       CommandCounterIncrement();
+}
+
+/*
+ * Execute the extension's script file
+ */
+static void
+execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
+                                                List *requiredSchemas,
+                                                const char *schemaName, Oid schemaOid)
+{
+       char       *filename = get_extension_absolute_path(control->script);
+       char       *save_client_min_messages = NULL,
+                          *save_log_min_messages = NULL,
+                          *save_search_path;
+       StringInfoData pathbuf;
+       ListCell   *lc;
+
+       /*
+        * Force client_min_messages and log_min_messages to be at least WARNING,
+        * so that we won't spam the user with useless NOTICE messages from common
+        * script actions like creating shell types.
+        *
+        * We use the equivalent of SET LOCAL to ensure the setting is undone
+        * upon error.
+        */
+       if (client_min_messages < WARNING)
+       {
+               save_client_min_messages =
+                       pstrdup(GetConfigOption("client_min_messages", false));
+               (void) set_config_option("client_min_messages", "warning",
+                                                                PGC_USERSET, PGC_S_SESSION,
+                                                                GUC_ACTION_LOCAL, true);
+       }
+
+       if (log_min_messages < WARNING)
+       {
+               save_log_min_messages =
+                       pstrdup(GetConfigOption("log_min_messages", false));
+               (void) set_config_option("log_min_messages", "warning",
+                                                                PGC_SUSET, PGC_S_SESSION,
+                                                                GUC_ACTION_LOCAL, true);
+       }
+
+       /*
+        * Set up the search path to contain the target schema, then the schemas
+        * of any prerequisite extensions, and nothing else.  In particular this
+        * makes the target schema be the default creation target namespace.
+        *
+        * Note: it might look tempting to use PushOverrideSearchPath for this,
+        * but we cannot do that.  We have to actually set the search_path GUC
+        * in case the extension script examines or changes it.
+        */
+       save_search_path = pstrdup(GetConfigOption("search_path", false));
+
+       initStringInfo(&pathbuf);
+       appendStringInfoString(&pathbuf, quote_identifier(schemaName));
+       foreach(lc, requiredSchemas)
+       {
+               Oid                     reqschema = lfirst_oid(lc);
+               char       *reqname = get_namespace_name(reqschema);
+
+               if (reqname)
+                       appendStringInfo(&pathbuf, ", %s", quote_identifier(reqname));
+       }
+
+       (void) set_config_option("search_path", pathbuf.data,
+                                                        PGC_USERSET, PGC_S_SESSION,
+                                                        GUC_ACTION_LOCAL, true);
+
+       /*
+        * Set creating_extension and related variables so that
+        * recordDependencyOnCurrentExtension and other functions do the right
+        * things.  On failure, ensure we reset these variables.
+        */
+       creating_extension = true;
+       CurrentExtensionObject = extensionOid;
+       PG_TRY();
+       {
+               char *sql = read_extension_script_file(control, filename);
+
+               /*
+                * If it's not relocatable, substitute the target schema name for
+                * occcurrences of @extschema@.
+                *
+                * For a relocatable extension, we just run the script as-is.
+                * There cannot be any need for @extschema@, else it wouldn't
+                * be relocatable.
+                */
+               if (!control->relocatable)
+               {
+                       const char   *qSchemaName = quote_identifier(schemaName);
+
+                       sql = text_to_cstring(
+                               DatumGetTextPP(
+                                       DirectFunctionCall3(replace_text,
+                                                                               CStringGetTextDatum(sql),
+                                                                               CStringGetTextDatum("@extschema@"),
+                                                                               CStringGetTextDatum(qSchemaName))));
+
+               }
+
+               execute_sql_string(sql, filename);
+       }
+       PG_CATCH();
+       {
+               creating_extension = false;
+               CurrentExtensionObject = InvalidOid;
+               PG_RE_THROW();
+       }
+       PG_END_TRY();
+
+       creating_extension = false;
+       CurrentExtensionObject = InvalidOid;
+
+       /*
+        * Restore GUC variables for the remainder of the current transaction.
+        * Again use SET LOCAL, so we won't affect the session value.
+        */
+       (void) set_config_option("search_path", save_search_path,
+                                                        PGC_USERSET, PGC_S_SESSION,
+                                                        GUC_ACTION_LOCAL, true);
+
+       if (save_client_min_messages != NULL)
+               (void) set_config_option("client_min_messages", save_client_min_messages,
+                                                                PGC_USERSET, PGC_S_SESSION,
+                                                                GUC_ACTION_LOCAL, true);
+       if (save_log_min_messages != NULL)
+               (void) set_config_option("log_min_messages", save_log_min_messages,
+                                                                PGC_SUSET, PGC_S_SESSION,
+                                                                GUC_ACTION_LOCAL, true);
+}
+
+/*
+ * CREATE EXTENSION
+ */
+void
+CreateExtension(CreateExtensionStmt *stmt)
+{
+       DefElem    *d_schema = NULL;
+       char       *schemaName;
+       Oid                     schemaOid;
+       Oid                     extowner = GetUserId();
+       ExtensionControlFile *control;
+       List       *requiredExtensions;
+       List       *requiredSchemas;
+       Relation        rel;
+       Datum           values[Natts_pg_extension];
+       bool            nulls[Natts_pg_extension];
+       HeapTuple       tuple;
+       Oid                     extensionOid;
+       ObjectAddress myself;
+       ObjectAddress nsp;
+       ListCell   *lc;
+
+       /* Must be super user */
+       if (!superuser())
+               ereport(ERROR,
+                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                errmsg("permission denied to create extension \"%s\"",
+                                               stmt->extname),
+                                errhint("Must be superuser to create an extension.")));
+
+       /*
+        * We use global variables to track the extension being created, so we
+        * can create only one extension at the same time.
+        */
+       if (creating_extension)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("nested CREATE EXTENSION is not supported")));
+
+       /*
+        * Check for duplicate extension name.  The unique index on
+        * pg_extension.extname would catch this anyway, and serves as a backstop
+        * in case of race conditions; but this is a friendlier error message.
+        */
+       if (get_extension_oid(stmt->extname, true) != InvalidOid)
+               ereport(ERROR,
+                               (errcode(ERRCODE_DUPLICATE_OBJECT),
+                                errmsg("extension \"%s\" already exists", stmt->extname)));
+
+       /*
+        * Read the control file.  Note we assume that it does not contain
+        * any non-ASCII data, so there is no need to worry about encoding
+        * at this point.
+        */
+       control = read_extension_control_file(stmt->extname);
+
+       /*
+        * Read the statement option list
+        */
+       foreach(lc, stmt->options)
+       {
+               DefElem    *defel = (DefElem *) lfirst(lc);
+
+               if (strcmp(defel->defname, "schema") == 0)
+               {
+                       if (d_schema)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("conflicting or redundant options")));
+                       d_schema = defel;
+               }
+               else
+                       elog(ERROR, "unrecognized option: %s", defel->defname);
+       }
+
+       /*
+        * Determine the target schema to install the extension into
+        */
+       if (d_schema && d_schema->arg)
+       {
+               /*
+                * User given schema, CREATE EXTENSION ... WITH SCHEMA ...
+                *
+                * It's an error to give a schema different from control->schema if
+                * control->schema is specified.
+                */
+               schemaName = strVal(d_schema->arg);
+
+               if (control->schema != NULL &&
+                       strcmp(control->schema, schemaName) != 0)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("extension \"%s\" must be installed in schema \"%s\"",
+                                                       control->name,
+                                                       control->schema)));
+
+               /* If the user is giving us the schema name, it must exist already */
+               schemaOid = get_namespace_oid(schemaName, false);
+       }
+       else if (control->schema != NULL)
+       {
+               /*
+                * The extension is not relocatable and the author gave us a schema
+                * for it.  We create the schema here if it does not already exist.
+                */
+               schemaName = control->schema;
+               schemaOid = get_namespace_oid(schemaName, true);
+
+               if (schemaOid == InvalidOid)
+               {
+                       schemaOid = NamespaceCreate(schemaName, extowner);
+                       /* Advance cmd counter to make the namespace visible */
+                       CommandCounterIncrement();
+               }
+       }
+       else
+       {
+               /*
+                * Else, use the current default creation namespace, which is the
+                * first explicit entry in the search_path.
+                */
+               List *search_path = fetch_search_path(false);
+
+               if (search_path == NIL)                         /* probably can't happen */
+                       elog(ERROR, "there is no default creation target");
+               schemaOid = linitial_oid(search_path);
+               schemaName = get_namespace_name(schemaOid);
+               if (schemaName == NULL)                         /* recently-deleted namespace? */
+                       elog(ERROR, "there is no default creation target");
+
+               list_free(search_path);
+       }
+
+       /*
+        * If we didn't already know user is superuser, we would probably want
+        * to do pg_namespace_aclcheck(schemaOid, extowner, ACL_CREATE) here.
+        */
+
+       /*
+        * Look up the prerequisite extensions, and build lists of their OIDs
+        * and the OIDs of their target schemas.
+        */
+       requiredExtensions = NIL;
+       requiredSchemas = NIL;
+       foreach(lc, control->requires)
+       {
+               char       *curreq = (char *) lfirst(lc);
+               Oid                     reqext;
+               Oid                     reqschema;
+
+               /*
+                * We intentionally don't use get_extension_oid's default error
+                * message here, because it would be confusing in this context.
+                */
+               reqext = get_extension_oid(curreq, true);
+               if (!OidIsValid(reqext))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_UNDEFINED_OBJECT),
+                                        errmsg("required extension \"%s\" is not installed",
+                                                       curreq)));
+               reqschema = get_extension_schema(reqext);
+               requiredExtensions = lappend_oid(requiredExtensions, reqext);
+               requiredSchemas = lappend_oid(requiredSchemas, reqschema);
+       }
+
+       /*
+        * Insert new tuple into pg_extension.
+        */
+       rel = heap_open(ExtensionRelationId, RowExclusiveLock);
+
+       memset(values, 0, sizeof(values));
+       memset(nulls, 0, sizeof(nulls));
+
+       values[Anum_pg_extension_extname - 1] =
+               DirectFunctionCall1(namein, CStringGetDatum(control->name));
+       values[Anum_pg_extension_extowner - 1] = ObjectIdGetDatum(extowner);
+       values[Anum_pg_extension_extnamespace - 1] = ObjectIdGetDatum(schemaOid);
+       values[Anum_pg_extension_extrelocatable - 1] = BoolGetDatum(control->relocatable);
+
+       if (control->version == NULL)
+               nulls[Anum_pg_extension_extversion - 1] = true;
+       else
+               values[Anum_pg_extension_extversion - 1] =
+                       CStringGetTextDatum(control->version);
+
+       nulls[Anum_pg_extension_extconfig - 1] = true;
+       nulls[Anum_pg_extension_extcondition - 1] = true;
+
+       tuple = heap_form_tuple(rel->rd_att, values, nulls);
+
+       extensionOid = simple_heap_insert(rel, tuple);
+       CatalogUpdateIndexes(rel, tuple);
+
+       heap_freetuple(tuple);
+       heap_close(rel, RowExclusiveLock);
+
+       /*
+        * Apply any comment on extension
+        */
+       if (control->comment != NULL)
+               CreateComments(extensionOid, ExtensionRelationId, 0, control->comment);
+
+       /*
+        * Record dependencies on owner, schema, and prerequisite extensions
+        */
+       recordDependencyOnOwner(ExtensionRelationId, extensionOid, extowner);
+
+       myself.classId = ExtensionRelationId;
+       myself.objectId = extensionOid;
+       myself.objectSubId = 0;
+
+       nsp.classId = NamespaceRelationId;
+       nsp.objectId = schemaOid;
+       nsp.objectSubId = 0;
+
+       recordDependencyOn(&myself, &nsp, DEPENDENCY_NORMAL);
+
+       foreach(lc, requiredExtensions)
+       {
+               Oid                     reqext = lfirst_oid(lc);
+               ObjectAddress otherext;
+
+               otherext.classId = ExtensionRelationId;
+               otherext.objectId = reqext;
+               otherext.objectSubId = 0;
+
+               recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL);
+       }
+
+       /*
+        * Finally, execute the extension script to create the member objects
+        */
+       execute_extension_script(extensionOid, control, requiredSchemas,
+                                                        schemaName, schemaOid);
+}
+
+
+/*
+ *     RemoveExtensions
+ *             Implements DROP EXTENSION.
+ */
+void
+RemoveExtensions(DropStmt *drop)
+{
+       ObjectAddresses *objects;
+       ListCell   *cell;
+
+       /*
+        * First we identify all the extensions, then we delete them in a single
+        * performMultipleDeletions() call.  This is to avoid unwanted DROP
+        * RESTRICT errors if one of the extensions depends on another.
+        */
+       objects = new_object_addresses();
+
+       foreach(cell, drop->objects)
+       {
+               List       *names = (List *) lfirst(cell);
+               char       *extensionName;
+               Oid                     extensionId;
+               ObjectAddress object;
+
+               if (list_length(names) != 1)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("extension name cannot be qualified")));
+               extensionName = strVal(linitial(names));
+
+               extensionId = get_extension_oid(extensionName, drop->missing_ok);
+
+               if (!OidIsValid(extensionId))
+               {
+                       ereport(NOTICE,
+                                       (errmsg("extension \"%s\" does not exist, skipping",
+                                                       extensionName)));
+                       continue;
+               }
+
+               /*
+                * Permission check.  For now, insist on superuser-ness; later we
+                * might want to relax that to being owner of the extension.
+                */
+               if (!superuser())
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                        errmsg("permission denied to drop extension \"%s\"",
+                                                       extensionName),
+                                        errhint("Must be superuser to drop an extension.")));
+
+               object.classId = ExtensionRelationId;
+               object.objectId = extensionId;
+               object.objectSubId = 0;
+
+               add_exact_object_address(&object, objects);
+       }
+
+       /*
+        * Do the deletions.  Objects contained in the extension(s) are removed by
+        * means of their dependency links to the extensions.
+        */
+       performMultipleDeletions(objects, drop->behavior);
+
+       free_object_addresses(objects);
+}
+
+
+/*
+ * Guts of extension deletion.
+ *
+ * All we need do here is remove the pg_extension tuple itself.  Everything
+ * else is taken care of by the dependency infrastructure.
+ */
+void
+RemoveExtensionById(Oid extId)
+{
+       Relation        rel;
+       SysScanDesc scandesc;
+       HeapTuple       tuple;
+       ScanKeyData entry[1];
+
+       rel = heap_open(ExtensionRelationId, RowExclusiveLock);
+
+       ScanKeyInit(&entry[0],
+                               ObjectIdAttributeNumber,
+                               BTEqualStrategyNumber, F_OIDEQ,
+                               ObjectIdGetDatum(extId));
+       scandesc = systable_beginscan(rel, ExtensionOidIndexId, true,
+                                                                 SnapshotNow, 1, entry);
+
+       tuple = systable_getnext(scandesc);
+
+       /* We assume that there can be at most one matching tuple */
+       if (HeapTupleIsValid(tuple))
+               simple_heap_delete(rel, &tuple->t_self);
+
+       systable_endscan(scandesc);
+
+       heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * This function lists the extensions available in the control directory
+ * (each of which might or might not actually be installed).  We parse each
+ * available control file and report the interesting fields.
+ *
+ * The system view pg_available_extensions provides a user interface to this
+ * SRF, adding information about whether the extensions are installed in the
+ * current DB.
+ */
+Datum
+pg_available_extensions(PG_FUNCTION_ARGS)
+{
+       ReturnSetInfo      *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+       TupleDesc                       tupdesc;
+       Tuplestorestate    *tupstore;
+       MemoryContext           per_query_ctx;
+       MemoryContext           oldcontext;
+       char                       *location;
+       DIR                                *dir;
+       struct dirent      *de;
+
+       if (!superuser())
+               ereport(ERROR,
+                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                (errmsg("must be superuser to list available extensions"))));
+
+       /* check to see if caller supports us returning a tuplestore */
+       if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("set-valued function called in context that cannot accept a set")));
+       if (!(rsinfo->allowedModes & SFRM_Materialize))
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("materialize mode required, but it is not " \
+                                               "allowed in this context")));
+
+       /* Build a tuple descriptor for our result type */
+       if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+               elog(ERROR, "return type must be a row type");
+
+       per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+       oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+       tupstore = tuplestore_begin_heap(true, false, work_mem);
+       rsinfo->returnMode = SFRM_Materialize;
+       rsinfo->setResult = tupstore;
+       rsinfo->setDesc = tupdesc;
+
+       MemoryContextSwitchTo(oldcontext);
+
+       location = get_extension_control_directory();
+       dir  = AllocateDir(location);
+
+       /*
+        * If the control directory doesn't exist, we want to silently return
+        * an empty set.  Any other error will be reported by ReadDir.
+        */
+       if (dir == NULL && errno == ENOENT)
+       {
+               /* do nothing */
+       }
+       else
+       {
+               while ((de = ReadDir(dir, location)) != NULL)
+               {
+                       ExtensionControlFile *control;
+                       char       *extname;
+                       Datum           values[4];
+                       bool            nulls[4];
+
+                       if (!is_extension_control_filename(de->d_name))
+                               continue;
+
+                       /* extract extension name from 'name.control' filename */
+                       extname = pstrdup(de->d_name);
+                       *strrchr(extname, '.') = '\0';
+
+                       control = read_extension_control_file(extname);
+
+                       memset(values, 0, sizeof(values));
+                       memset(nulls, 0, sizeof(nulls));
+
+                       /* name */
+                       values[0] = DirectFunctionCall1(namein,
+                                                                                       CStringGetDatum(control->name));
+                       /* version */
+                       if (control->version == NULL)
+                               nulls[1] = true;
+                       else
+                               values[1] = CStringGetTextDatum(control->version);
+                       /* relocatable */
+                       values[2] = BoolGetDatum(control->relocatable);
+                       /* comment */
+                       if (control->comment == NULL)
+                               nulls[3] = true;
+                       else
+                               values[3] = CStringGetTextDatum(control->comment);
+
+                       tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+               }
+
+               FreeDir(dir);
+       }
+
+       /* clean up and return the tuplestore */
+       tuplestore_donestoring(tupstore);
+
+       return (Datum) 0;
+}
+
+/*
+ * pg_extension_config_dump
+ *
+ * Record information about a configuration table that belongs to an
+ * extension being created, but whose contents should be dumped in whole
+ * or in part during pg_dump.
+ */
+Datum
+pg_extension_config_dump(PG_FUNCTION_ARGS)
+{
+       Oid                     tableoid = PG_GETARG_OID(0);
+       text       *wherecond = PG_GETARG_TEXT_P(1);
+       char       *tablename;
+       Relation        extRel;
+       ScanKeyData     key[1];
+       SysScanDesc     extScan;
+       HeapTuple       extTup;
+       Datum           arrayDatum;
+       Datum           elementDatum;
+       int                     arrayIndex;
+       bool            isnull;
+       Datum           repl_val[Natts_pg_extension];
+       bool            repl_null[Natts_pg_extension];
+       bool            repl_repl[Natts_pg_extension];
+       ArrayType  *a;
+
+       /*
+        * We only allow this to be called from an extension's SQL script.
+        * We shouldn't need any permissions check beyond that.
+        */
+       if (!creating_extension)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("pg_extension_config_dump() can only be called "
+                                               "from a SQL script executed by CREATE EXTENSION")));
+
+       /*
+        * Check that the table exists and is a member of the extension being
+        * created.  This ensures that we don't need to register a dependency
+        * to protect the extconfig entry.
+        */
+       tablename = get_rel_name(tableoid);
+       if (tablename == NULL)
+               ereport(ERROR,
+                               (errcode(ERRCODE_UNDEFINED_TABLE),
+                                errmsg("OID %u does not refer to a table", tableoid)));
+       if (getExtensionOfObject(RelationRelationId, tableoid) !=
+               CurrentExtensionObject)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("table \"%s\" is not a member of the extension being created",
+                                               tablename)));
+
+       /*
+        * Add the table OID and WHERE condition to the extension's extconfig
+        * and extcondition arrays.
+        */
+
+       /* Find the pg_extension tuple */
+       extRel = heap_open(ExtensionRelationId, RowExclusiveLock);
+
+       ScanKeyInit(&key[0],
+                               ObjectIdAttributeNumber,
+                               BTEqualStrategyNumber, F_OIDEQ,
+                               ObjectIdGetDatum(CurrentExtensionObject));
+
+       extScan = systable_beginscan(extRel, ExtensionOidIndexId, true,
+                                                                SnapshotNow, 1, key);
+
+       extTup = systable_getnext(extScan);
+
+       if (!HeapTupleIsValid(extTup)) /* should not happen */
+               elog(ERROR, "extension with oid %u does not exist",
+                        CurrentExtensionObject);
+
+       memset(repl_val, 0, sizeof(repl_val));
+       memset(repl_null, false, sizeof(repl_null));
+       memset(repl_repl, false, sizeof(repl_repl));
+
+       /* Build or modify the extconfig value */
+       elementDatum = ObjectIdGetDatum(tableoid);
+
+       arrayDatum = heap_getattr(extTup, Anum_pg_extension_extconfig,
+                                                         RelationGetDescr(extRel), &isnull);
+       if (isnull)
+       {
+               a = construct_array(&elementDatum, 1,
+                                                       OIDOID,
+                                                       sizeof(Oid), true, 'i');
+       }
+       else
+       {
+               a = DatumGetArrayTypeP(arrayDatum);
+               Assert(ARR_ELEMTYPE(a) == OIDOID);
+               Assert(ARR_NDIM(a) == 1);
+               Assert(ARR_LBOUND(a)[0] == 1);
+
+               arrayIndex = ARR_DIMS(a)[0] + 1; /* add after end */
+
+               a = array_set(a, 1, &arrayIndex,
+                                         elementDatum,
+                                         false,
+                                         -1 /* varlena array */ ,
+                                         sizeof(Oid) /* OID's typlen */ ,
+                                         true /* OID's typbyval */ ,
+                                         'i' /* OID's typalign */ );
+       }
+       repl_val[Anum_pg_extension_extconfig - 1] = PointerGetDatum(a);
+       repl_repl[Anum_pg_extension_extconfig - 1] = true;
+
+       /* Build or modify the extcondition value */
+       elementDatum = PointerGetDatum(wherecond);
+
+       arrayDatum = heap_getattr(extTup, Anum_pg_extension_extcondition,
+                                                         RelationGetDescr(extRel), &isnull);
+       if (isnull)
+       {
+               a = construct_array(&elementDatum, 1,
+                                                       TEXTOID,
+                                                       -1, false, 'i');
+       }
+       else
+       {
+               a = DatumGetArrayTypeP(arrayDatum);
+               Assert(ARR_ELEMTYPE(a) == TEXTOID);
+               Assert(ARR_NDIM(a) == 1);
+               Assert(ARR_LBOUND(a)[0] == 1);
+
+               arrayIndex = ARR_DIMS(a)[0] + 1; /* add after end */
+
+               a = array_set(a, 1, &arrayIndex,
+                                         elementDatum,
+                                         false,
+                                         -1 /* varlena array */ ,
+                                         -1 /* TEXT's typlen */ ,
+                                         false /* TEXT's typbyval */ ,
+                                         'i' /* TEXT's typalign */ );
+       }
+       repl_val[Anum_pg_extension_extcondition - 1] = PointerGetDatum(a);
+       repl_repl[Anum_pg_extension_extcondition - 1] = true;
+
+       extTup = heap_modify_tuple(extTup, RelationGetDescr(extRel),
+                                                          repl_val, repl_null, repl_repl);
+
+       simple_heap_update(extRel, &extTup->t_self, extTup);
+       CatalogUpdateIndexes(extRel, extTup);
+
+       systable_endscan(extScan);
+
+       heap_close(extRel, RowExclusiveLock);
+
+       PG_RETURN_VOID();
+}
+
+/*
+ * Execute ALTER EXTENSION SET SCHEMA
+ */
+void
+AlterExtensionNamespace(List *names, const char *newschema)
+{
+       char       *extensionName;
+       Oid                     extensionOid;
+       Oid                     nspOid;
+       Oid                     oldNspOid = InvalidOid;
+       Relation        extRel;
+       ScanKeyData     key[2];
+       SysScanDesc     extScan;
+       HeapTuple       extTup;
+       Form_pg_extension extForm;
+       Relation        depRel;
+       SysScanDesc     depScan;
+       HeapTuple       depTup;
+
+       if (list_length(names) != 1)
+               ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                errmsg("extension name cannot be qualified")));
+       extensionName = strVal(linitial(names));
+
+       extensionOid = get_extension_oid(extensionName, false);
+
+       nspOid = LookupCreationNamespace(newschema);
+
+       /* this might later become an ownership test */
+       if (!superuser())
+               ereport(ERROR,
+                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                (errmsg("must be superuser to use ALTER EXTENSION"))));
+
+       /* Locate the pg_extension tuple */
+       extRel = heap_open(ExtensionRelationId, RowExclusiveLock);
+
+       ScanKeyInit(&key[0],
+                               ObjectIdAttributeNumber,
+                               BTEqualStrategyNumber, F_OIDEQ,
+                               ObjectIdGetDatum(extensionOid));
+
+       extScan = systable_beginscan(extRel, ExtensionOidIndexId, true,
+                                                                SnapshotNow, 1, key);
+
+       extTup = systable_getnext(extScan);
+
+       if (!HeapTupleIsValid(extTup)) /* should not happen */
+               elog(ERROR, "extension with oid %u does not exist", extensionOid);
+
+       /* Copy tuple so we can modify it below */
+       extTup = heap_copytuple(extTup);
+       extForm = (Form_pg_extension) GETSTRUCT(extTup);
+
+       systable_endscan(extScan);
+
+       /*
+        * If the extension is already in the target schema, just silently
+        * do nothing.
+        */
+       if (extForm->extnamespace == nspOid)
+       {
+               heap_close(extRel, RowExclusiveLock);
+               return;
+       }
+
+       /* Check extension is supposed to be relocatable */
+       if (!extForm->extrelocatable)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("extension \"%s\" does not support SET SCHEMA",
+                                               NameStr(extForm->extname))));
+
+       /*
+        * Scan pg_depend to find objects that depend directly on the extension,
+        * and alter each one's schema.
+        */
+       depRel = heap_open(DependRelationId, AccessShareLock);
+
+       ScanKeyInit(&key[0],
+                               Anum_pg_depend_refclassid,
+                               BTEqualStrategyNumber, F_OIDEQ,
+                               ObjectIdGetDatum(ExtensionRelationId));
+       ScanKeyInit(&key[1],
+                               Anum_pg_depend_refobjid,
+                               BTEqualStrategyNumber, F_OIDEQ,
+                               ObjectIdGetDatum(extensionOid));
+
+       depScan = systable_beginscan(depRel, DependReferenceIndexId, true,
+                                                                SnapshotNow, 2, key);
+
+       while (HeapTupleIsValid(depTup = systable_getnext(depScan)))
+       {
+               Form_pg_depend pg_depend = (Form_pg_depend) GETSTRUCT(depTup);
+               ObjectAddress dep;
+               Oid dep_oldNspOid;
+
+               /*
+                * Ignore non-membership dependencies.  (Currently, the only other
+                * case we could see here is a normal dependency from another
+                * extension.)
+                */
+               if (pg_depend->deptype != DEPENDENCY_EXTENSION)
+                       continue;
+
+               dep.classId = pg_depend->classid;
+               dep.objectId = pg_depend->objid;
+               dep.objectSubId = pg_depend->objsubid;
+
+               if (dep.objectSubId != 0)               /* should not happen */
+                       elog(ERROR, "extension should not have a sub-object dependency");
+
+               dep_oldNspOid = AlterObjectNamespace_oid(dep.classId,
+                                                                                                dep.objectId,
+                                                                                                nspOid);
+
+               /*
+                * Remember previous namespace of first object that has one
+                */
+               if (oldNspOid == InvalidOid && dep_oldNspOid != InvalidOid)
+                       oldNspOid = dep_oldNspOid;
+
+               /*
+                * If not all the objects had the same old namespace (ignoring any
+                * that are not in namespaces), complain.
+                */
+               if (dep_oldNspOid != InvalidOid && dep_oldNspOid != oldNspOid)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("extension \"%s\" does not support SET SCHEMA",
+                                                       NameStr(extForm->extname)),
+                                        errdetail("%s is not in the extension's schema \"%s\"",
+                                                          getObjectDescription(&dep),
+                                                          get_namespace_name(oldNspOid))));
+       }
+
+       systable_endscan(depScan);
+
+       relation_close(depRel, AccessShareLock);
+
+       /* Now adjust pg_extension.extnamespace */
+       extForm->extnamespace = nspOid;
+
+       simple_heap_update(extRel, &extTup->t_self, extTup);
+       CatalogUpdateIndexes(extRel, extTup);
+
+       heap_close(extRel, RowExclusiveLock);
+
+       /* update dependencies to point to the new schema */
+       changeDependencyFor(ExtensionRelationId, extensionOid,
+                                               NamespaceRelationId, oldNspOid, nspOid);
+}
index 3a0ea9a632395544f0714976b9441d9815c06301..a2b5358e16fe7e9daf178fcd4f9d2b2115a9360c 100644 (file)
@@ -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);
index dad65ee8ffaf06eab4b98aa17d732f65dde037f7..3f25b3bf02a232e4a57bf5679a1e66970335b5c4 100644 (file)
@@ -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;
 }
 
 
index 662b9420387f90bf3c9b6e656ed7b90583319d98..68072dd42184de43b18143d333cc6196eaf0fdf8 100644 (file)
@@ -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;
 }
index 35bb76162d2c9d394abfcd694c4567722fe50898..b4374a62f4f0830bfb7b91b8326b314584260884 100644 (file)
@@ -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;
 }
index 3860105b266d87b804dd136b7587f3bfd35f5849..b36f31ee6d52f5619fbb5eadea4fd9ad73c33a64 100644 (file)
@@ -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;
index c0a4e6f954a28fee2c1177d658b23d766f18275d..f67e9b9b16290f496f8d4343cbfd6a0f19c5f544 100644 (file)
@@ -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
index 7afd2f896ed4b017d8c516603dd84e0d1c69b2cd..81f129dff6bc4f11a290657d6179e13f11fafffc 100644 (file)
@@ -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;
 }
 
 /*
index 25d0f3596e14c47da1775b431897d5f4f167a369..fb9d67a30a5f214aab73e74e1ca147e73bf761b8 100644 (file)
@@ -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;
 }
index 9b2c874d6d0001b0c8246cb74f14679d351513ae..851186146dd78f0f40f619380a6d0b1eb8c5ecf6 100644 (file)
@@ -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;
index 837eafaaccb673b046cea646b36d367c11246282..00d23ccfa560b47612d3f0c8a11eae6d5292070b 100644 (file)
@@ -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;
index a1bcf02f5be530bec49ccf689b06b1afd761d598..4c4536b9be31c5ab8fb9ac07eda91e0abf6d1fb9 100644 (file)
@@ -191,7 +191,7 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
                AlterDefaultPrivilegesStmt DefACLAction
                AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
                ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
-               CreateDomainStmt CreateGroupStmt CreateOpClassStmt
+               CreateDomainStmt CreateExtensionStmt CreateGroupStmt CreateOpClassStmt
                CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
                CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
                CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
@@ -227,9 +227,9 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
 %type <dbehavior>      opt_drop_behavior
 
 %type <list>   createdb_opt_list alterdb_opt_list copy_opt_list
-                               transaction_mode_list
+                               transaction_mode_list create_extension_opt_list
 %type <defelt> createdb_opt_item alterdb_opt_item copy_opt_item
-                               transaction_mode_item
+                               transaction_mode_item create_extension_opt_item
 
 %type <ival>   opt_lock lock_type cast_context
 %type <ival>   vacuum_option_list vacuum_option_elem
@@ -492,7 +492,8 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
        DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
 
        EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EXCEPT
-       EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT
+       EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
+       EXTENSION EXTERNAL EXTRACT
 
        FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
        FREEZE FROM FULL FUNCTION FUNCTIONS
@@ -692,6 +693,7 @@ stmt :
                        | CreateCastStmt
                        | CreateConversionStmt
                        | CreateDomainStmt
+                       | CreateExtensionStmt
                        | CreateFdwStmt
                        | CreateForeignServerStmt
                        | CreateForeignTableStmt
@@ -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 ] <objname> |
@@ -4577,6 +4611,7 @@ comment_type:
                        | VIEW                                                          { $$ = OBJECT_VIEW; }
                        | CONVERSION_P                                          { $$ = OBJECT_CONVERSION; }
                        | TABLESPACE                                            { $$ = OBJECT_TABLESPACE; }
+                       | EXTENSION                                             { $$ = OBJECT_EXTENSION; }
                        | ROLE                                                          { $$ = OBJECT_ROLE; }
                        | FOREIGN TABLE                                         { $$ = OBJECT_FOREIGN_TABLE; }
                ;
@@ -6271,6 +6306,14 @@ AlterObjectSchemaStmt:
                                        n->newschema = $6;
                                        $$ = (Node *)n;
                                }
+                       | ALTER EXTENSION any_name SET SCHEMA name
+                               {
+                                       AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+                                       n->objectType = OBJECT_EXTENSION;
+                                       n->object = $3;
+                                       n->newschema = $6;
+                                       $$ = (Node *)n;
+                               }
                        | ALTER FUNCTION function_with_argtypes SET SCHEMA name
                                {
                                        AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
@@ -11462,6 +11505,7 @@ unreserved_keyword:
                        | EXCLUSIVE
                        | EXECUTE
                        | EXPLAIN
+                       | EXTENSION
                        | EXTERNAL
                        | FAMILY
                        | FIRST_P
index 99c397b4f201a8eabd2563a359cf1125431c2ac3..fecc4e27fa37d3323ba72ad57d090296d02fa014 100644 (file)
@@ -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
index af2eba01d614642f28cd12a0c213739f6d33962b..10a4438995f94d0a0d8412616528046bf7ec9089 100644 (file)
@@ -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:
index 93bc401c2d157931e0ba6f9627f559f052a799f9..c3ec98aa5e26e5993ff4fb1f788cee046a934665 100644 (file)
@@ -82,22 +82,16 @@ convert_and_check_filename(text *arg)
 /*
  * Read a section of a file, returning it as bytea
  *
- * We read the whole of the file when bytes_to_read is nagative.
+ * Caller is responsible for all permissions checking.
+ *
+ * We read the whole of the file when bytes_to_read is negative.
  */
-static bytea *
-read_binary_file(text *filename_t, int64 seek_offset, int64 bytes_to_read)
+bytea *
+read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read)
 {
        bytea      *buf;
        size_t          nbytes;
        FILE       *file;
-       char       *filename;
-
-       if (!superuser())
-               ereport(ERROR,
-                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                                (errmsg("must be superuser to read files"))));
-
-       filename = convert_and_check_filename(filename_t);
 
        if (bytes_to_read < 0)
        {
@@ -146,7 +140,6 @@ read_binary_file(text *filename_t, int64 seek_offset, int64 bytes_to_read)
        SET_VARSIZE(buf, nbytes + VARHDRSZ);
 
        FreeFile(file);
-       pfree(filename);
 
        return buf;
 }
@@ -156,9 +149,11 @@ read_binary_file(text *filename_t, int64 seek_offset, int64 bytes_to_read)
  * in the database encoding.
  */
 static text *
-read_text_file(text *filename, int64 seek_offset, int64 bytes_to_read)
+read_text_file(const char *filename, int64 seek_offset, int64 bytes_to_read)
 {
-       bytea *buf = read_binary_file(filename, seek_offset, bytes_to_read);
+       bytea      *buf;
+
+       buf = read_binary_file(filename, seek_offset, bytes_to_read);
 
        /* Make sure the input is valid */
        pg_verifymbstr(VARDATA(buf), VARSIZE(buf) - VARHDRSZ, false);
@@ -176,13 +171,21 @@ pg_read_file(PG_FUNCTION_ARGS)
        text       *filename_t = PG_GETARG_TEXT_P(0);
        int64           seek_offset = PG_GETARG_INT64(1);
        int64           bytes_to_read = PG_GETARG_INT64(2);
+       char       *filename;
+
+       if (!superuser())
+               ereport(ERROR,
+                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                (errmsg("must be superuser to read files"))));
+
+       filename = convert_and_check_filename(filename_t);
 
        if (bytes_to_read < 0)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                 errmsg("requested length cannot be negative")));
 
-       PG_RETURN_TEXT_P(read_text_file(filename_t, seek_offset, bytes_to_read));
+       PG_RETURN_TEXT_P(read_text_file(filename, seek_offset, bytes_to_read));
 }
 
 /*
@@ -192,8 +195,16 @@ Datum
 pg_read_file_all(PG_FUNCTION_ARGS)
 {
        text       *filename_t = PG_GETARG_TEXT_P(0);
+       char       *filename;
+
+       if (!superuser())
+               ereport(ERROR,
+                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                (errmsg("must be superuser to read files"))));
+
+       filename = convert_and_check_filename(filename_t);
 
-       PG_RETURN_TEXT_P(read_text_file(filename_t, 0, -1));
+       PG_RETURN_TEXT_P(read_text_file(filename, 0, -1));
 }
 
 /*
@@ -205,13 +216,21 @@ pg_read_binary_file(PG_FUNCTION_ARGS)
        text       *filename_t = PG_GETARG_TEXT_P(0);
        int64           seek_offset = PG_GETARG_INT64(1);
        int64           bytes_to_read = PG_GETARG_INT64(2);
+       char       *filename;
+
+       if (!superuser())
+               ereport(ERROR,
+                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                (errmsg("must be superuser to read files"))));
+
+       filename = convert_and_check_filename(filename_t);
 
        if (bytes_to_read < 0)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                 errmsg("requested length cannot be negative")));
 
-       PG_RETURN_BYTEA_P(read_binary_file(filename_t, seek_offset, bytes_to_read));
+       PG_RETURN_BYTEA_P(read_binary_file(filename, seek_offset, bytes_to_read));
 }
 
 /*
@@ -221,8 +240,16 @@ Datum
 pg_read_binary_file_all(PG_FUNCTION_ARGS)
 {
        text       *filename_t = PG_GETARG_TEXT_P(0);
+       char       *filename;
+
+       if (!superuser())
+               ereport(ERROR,
+                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                (errmsg("must be superuser to read files"))));
+
+       filename = convert_and_check_filename(filename_t);
 
-       PG_RETURN_BYTEA_P(read_binary_file(filename_t, 0, -1));
+       PG_RETURN_BYTEA_P(read_binary_file(filename, 0, -1));
 }
 
 /*
index 55cc3126e1e34b4c0162289bf62aa7afe907cd6d..cc0db663200f09309280e460f8021c751dea0d2e 100644 (file)
@@ -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");
index 49c570016ad092699ce913e903a760a07ba26f7f..dec96bc0253119e77c454febc377ec0abcbf3fdf 100644 (file)
+++ b/