Support ALTER THING .. DEPENDS ON EXTENSION
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Tue, 5 Apr 2016 21:38:54 +0000 (18:38 -0300)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Tue, 5 Apr 2016 21:38:54 +0000 (18:38 -0300)
This introduces a new dependency type which marks an object as depending
on an extension, such that if the extension is dropped, the object
automatically goes away; and also, if the database is dumped, the object
is included in the dump output.  Currently the grammar supports this for
indexes, triggers, materialized views and functions only, although the
utility code is generic so adding support for more object types is a
matter of touching the parser rules only.

Author: Abhijit Menon-Sen
Reviewed-by: Alexander Korotkov, Álvaro Herrera
Discussion: http://www.postgresql.org/message-id/20160115062649.GA5068@toroid.org

23 files changed:
doc/src/sgml/catalogs.sgml
doc/src/sgml/ref/alter_function.sgml
doc/src/sgml/ref/alter_index.sgml
doc/src/sgml/ref/alter_materialized_view.sgml
doc/src/sgml/ref/alter_trigger.sgml
src/backend/catalog/dependency.c
src/backend/catalog/objectaddress.c
src/backend/commands/alter.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/parser/gram.y
src/backend/tcop/utility.c
src/include/catalog/catversion.h
src/include/catalog/dependency.h
src/include/catalog/objectaddress.h
src/include/commands/alter.h
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/include/parser/kwlist.h
src/test/modules/test_extensions/Makefile
src/test/modules/test_extensions/expected/test_extdepend.out [new file with mode: 0644]
src/test/modules/test_extensions/sql/test_extdepend.sql [new file with mode: 0644]
src/tools/pgindent/typedefs.list

index bb752294c3fa3edbf9174f7d8e2362ccf94d1795..725bacee5dd13e8c979f15ca736eb064164154d2 100644 (file)
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><symbol>DEPENDENCY_AUTO_EXTENSION</> (<literal>x</>)</term>
+     <listitem>
+      <para>
+       The dependent object is not a member of the extension that is the
+       referenced object (and so should not be ignored by pg_dump), but
+       cannot function without it and should be dropped when the
+       extension itself is. The dependent object may be dropped on its
+       own as well.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><symbol>DEPENDENCY_PIN</> (<literal>p</>)</term>
      <listitem>
index f40363e4aa5f5cbf913de5d931f537cc1f2f6382..865d52b5919ee0367ff481aaaaedfb86746c9bea 100644 (file)
@@ -29,6 +29,8 @@ ALTER FUNCTION <replaceable>name</replaceable> ( [ [ <replaceable class="paramet
     OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_USER | SESSION_USER }
 ALTER FUNCTION <replaceable>name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] )
     SET SCHEMA <replaceable>new_schema</replaceable>
+ALTER FUNCTION <replaceable>name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] )
+    DEPENDS ON EXTENSION <replaceable>extension_name</replaceable>
 
 <phrase>where <replaceable class="PARAMETER">action</replaceable> is one of:</phrase>
 
@@ -148,6 +150,15 @@ ALTER FUNCTION <replaceable>name</replaceable> ( [ [ <replaceable class="paramet
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><replaceable class="parameter">extension_name</replaceable></term>
+    <listitem>
+     <para>
+      The name of the extension that the function is to depend on.
+     </para>
+    </listitem>
+   </varlistentry>
+
     <varlistentry>
      <term><literal>CALLED ON NULL INPUT</literal></term>
      <term><literal>RETURNS NULL ON NULL INPUT</literal></term>
@@ -299,6 +310,15 @@ ALTER FUNCTION sqrt(integer) SET SCHEMA maths;
 </programlisting>
   </para>
 
+  <para>
+   To mark the function <literal>sqrt</literal> for type
+   <type>integer</type> as being dependent on the extension
+   <literal>mathlib</literal>:
+<programlisting>
+ALTER FUNCTION sqrt(integer) DEPENDS ON EXTENSION mathlib;
+</programlisting>
+  </para>
+
   <para>
    To adjust the search path that is automatically set for a function:
 <programlisting>
index ee3e3de4d6fe1cc179aa37c3a3360c946877cba2..54f3a56d3c03dc1bf1887066e4c78cbde95f39c3 100644 (file)
@@ -23,6 +23,7 @@ PostgreSQL documentation
 <synopsis>
 ALTER INDEX [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> RENAME TO <replaceable class="PARAMETER">new_name</replaceable>
 ALTER INDEX [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> SET TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable>
+ALTER INDEX <replaceable class="PARAMETER">name</replaceable> DEPENDS ON EXTENSION <replaceable class="PARAMETER">extension_name</replaceable>
 ALTER INDEX [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> SET ( <replaceable class="PARAMETER">storage_parameter</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
 ALTER INDEX [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> RESET ( <replaceable class="PARAMETER">storage_parameter</replaceable> [, ... ] )
 ALTER INDEX ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> [ OWNED BY <replaceable class="PARAMETER">role_name</replaceable> [, ... ] ]
@@ -82,6 +83,16 @@ ALTER INDEX ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>DEPENDS ON EXTENSION</literal></term>
+    <listitem>
+     <para>
+      This form marks the index as dependent on the extension, such that if the
+      extension is dropped, the index will automatically be dropped as well.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>SET ( <replaceable class="PARAMETER">storage_parameter</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )</literal></term>
     <listitem>
@@ -147,6 +158,15 @@ ALTER INDEX ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">extension_name</replaceable></term>
+      <listitem>
+       <para>
+        The name of the extension that the index is to depend on.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="PARAMETER">storage_parameter</replaceable></term>
       <listitem>
index 8807e01c365900623f8e45211238150dc80023d7..b5c44bfabf43542fd8bbfe1464a9bdaab896f734 100644 (file)
@@ -23,6 +23,8 @@ PostgreSQL documentation
 <synopsis>
 ALTER MATERIALIZED VIEW [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     <replaceable class="PARAMETER">action</replaceable> [, ... ]
+ALTER MATERIALIZED VIEW <replaceable class="PARAMETER">name</replaceable>
+    DEPENDS ON EXTENSION <replaceable class="PARAMETER">extension_name</replaceable>
 ALTER MATERIALIZED VIEW [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     RENAME [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> TO <replaceable class="PARAMETER">new_column_name</replaceable>
 ALTER MATERIALIZED VIEW [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
@@ -67,6 +69,12 @@ ALTER MATERIALIZED VIEW ALL IN TABLESPACE <replaceable class="parameter">name</r
    anyway.)
   </para>
 
+  <para>
+   The <literal>DEPENDS ON EXTENSION</literal> form marks the materialized view
+   as dependent on an extension, such that the materialized view will
+   automatically be dropped if the extension is dropped.
+  </para>
+
   <para>
    The statement subforms and actions available for
    <command>ALTER MATERIALIZED VIEW</command> are a subset of those available
@@ -99,6 +107,15 @@ ALTER MATERIALIZED VIEW ALL IN TABLESPACE <replaceable class="parameter">name</r
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><replaceable class="PARAMETER">extension_name</replaceable></term>
+     <listitem>
+      <para>
+       The name of the extension that the materialized view is to depend on.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><replaceable class="PARAMETER">new_column_name</replaceable></term>
      <listitem>
index c63b5dfa02b1102221d57fe1772af5aea4649390..47eef6e5e88de20e6737b0f78093dcd18b9a2400 100644 (file)
@@ -22,6 +22,7 @@ PostgreSQL documentation
  <refsynopsisdiv>
 <synopsis>
 ALTER TRIGGER <replaceable class="PARAMETER">name</replaceable> ON <replaceable class="PARAMETER">table_name</replaceable> RENAME TO <replaceable class="PARAMETER">new_name</replaceable>
+ALTER TRIGGER <replaceable class="PARAMETER">name</replaceable> ON <replaceable class="PARAMETER">table_name</replaceable> DEPENDS ON EXTENSION <replaceable class="PARAMETER">extension_name</replaceable>
 </synopsis>
  </refsynopsisdiv>
 
@@ -32,7 +33,9 @@ ALTER TRIGGER <replaceable class="PARAMETER">name</replaceable> ON <replaceable
    <command>ALTER TRIGGER</command> changes properties of an existing
    trigger.  The <literal>RENAME</literal> clause changes the name of
    the given trigger without otherwise changing the trigger
-   definition.
+   definition.  The <literal>DEPENDS ON EXTENSION</literal> clause marks
+   the trigger as dependent on an extension, such that if the extension is
+   dropped, the trigger will automatically be dropped as well.
   </para>
 
   <para>
@@ -70,6 +73,15 @@ ALTER TRIGGER <replaceable class="PARAMETER">name</replaceable> ON <replaceable
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="PARAMETER">extension_name</replaceable></term>
+    <listitem>
+     <para>
+      The name of the extension that the trigger is to depend on.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
@@ -92,6 +104,12 @@ ALTER TRIGGER <replaceable class="PARAMETER">name</replaceable> ON <replaceable
    To rename an existing trigger:
 <programlisting>
 ALTER TRIGGER emp_stamp ON emp RENAME TO emp_track_chgs;
+</programlisting></para>
+
+  <para>
+   To mark a trigger as being dependent on an extension:
+<programlisting>
+ALTER TRIGGER emp_stamp ON emp DEPENDS ON EXTENSION emplib;
 </programlisting></para>
  </refsect1>
 
index 17f9de1ff942f325fcb27aad45c1effe9efbc8f0..79595a9d230d6a0188a61512268f5b47263b658f 100644 (file)
@@ -589,6 +589,7 @@ findDependentObjects(const ObjectAddress *object,
        {
            case DEPENDENCY_NORMAL:
            case DEPENDENCY_AUTO:
+           case DEPENDENCY_AUTO_EXTENSION:
                /* no problem */
                break;
            case DEPENDENCY_INTERNAL:
@@ -788,6 +789,7 @@ findDependentObjects(const ObjectAddress *object,
                subflags = DEPFLAG_NORMAL;
                break;
            case DEPENDENCY_AUTO:
+           case DEPENDENCY_AUTO_EXTENSION:
                subflags = DEPFLAG_AUTO;
                break;
            case DEPENDENCY_INTERNAL:
index cb3ba853f4ede38e341d6e8d4964e4b5030e915b..13244610db1720a87a2ad98e1e8d12eeb6021242 100644 (file)
@@ -1015,6 +1015,31 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
    return address;
 }
 
+/*
+ * Return an ObjectAddress based on a RangeVar and an object name. The
+ * name of the relation identified by the RangeVar is prepended to the
+ * (possibly empty) list passed in as objname. This is useful to find
+ * the ObjectAddress of objects that depend on a relation. All other
+ * considerations are exactly as for get_object_address above.
+ */
+ObjectAddress
+get_object_address_rv(ObjectType objtype, RangeVar *rel, List *objname,
+                     List *objargs, Relation *relp, LOCKMODE lockmode,
+                     bool missing_ok)
+{
+   if (rel)
+   {
+       objname = lcons(makeString(rel->relname), objname);
+       if (rel->schemaname)
+           objname = lcons(makeString(rel->schemaname), objname);
+       if (rel->catalogname)
+           objname = lcons(makeString(rel->catalogname), objname);
+   }
+
+   return get_object_address(objtype, objname, objargs,
+                             relp, lockmode, missing_ok);
+}
+
 /*
  * Find an ObjectAddress for a type of object that is identified by an
  * unqualified name.
index 5af0f2ffdf346163306ebe33e1f36443f8b45e7d..7e39422ecd66153c40e92e10481146e861f1a171 100644 (file)
@@ -390,6 +390,43 @@ ExecRenameStmt(RenameStmt *stmt)
    }
 }
 
+/*
+ * Executes an ALTER OBJECT / DEPENDS ON [EXTENSION] statement.
+ *
+ * Return value is the address of the altered object.  refAddress is an output
+ * argument which, if not null, receives the address of the object that the
+ * altered object now depends on.
+ */
+ObjectAddress
+ExecAlterObjectDependsStmt(AlterObjectDependsStmt *stmt, ObjectAddress *refAddress)
+{
+   ObjectAddress   address;
+   ObjectAddress   refAddr;
+   Relation        rel;
+
+   address =
+       get_object_address_rv(stmt->objectType, stmt->relation, stmt->objname,
+                             stmt->objargs, &rel, AccessExclusiveLock, false);
+
+   /*
+    * If a relation was involved, it would have been opened and locked.
+    * We don't need the relation here, but we'll retain the lock until
+    * commit.
+    */
+   if (rel)
+       heap_close(rel, NoLock);
+
+   refAddr = get_object_address(OBJECT_EXTENSION, list_make1(stmt->extname),
+                                NULL, &rel, AccessExclusiveLock, false);
+   Assert(rel == NULL);
+   if (refAddress)
+       *refAddress = refAddr;
+
+   recordDependencyOn(&address, refAddress, DEPENDENCY_AUTO_EXTENSION);
+
+   return address;
+}
+
 /*
  * Executes an ALTER OBJECT / SET SCHEMA statement.  Based on the object
  * type, the function appropriate to that type is executed.
index f4e4a91ba53a72d692e5b25fe6ef69c6ee02efbb..1e123d89cbb76a5339e6f46aaa96b1f8605c9db5 100644 (file)
@@ -3204,6 +3204,20 @@ _copyRenameStmt(const RenameStmt *from)
    return newnode;
 }
 
+static AlterObjectDependsStmt *
+_copyAlterObjectDependsStmt(const AlterObjectDependsStmt *from)
+{
+   AlterObjectDependsStmt *newnode = makeNode(AlterObjectDependsStmt);
+
+   COPY_SCALAR_FIELD(objectType);
+   COPY_NODE_FIELD(relation);
+   COPY_NODE_FIELD(objname);
+   COPY_NODE_FIELD(objargs);
+   COPY_NODE_FIELD(extname);
+
+   return newnode;
+}
+
 static AlterObjectSchemaStmt *
 _copyAlterObjectSchemaStmt(const AlterObjectSchemaStmt *from)
 {
@@ -4682,6 +4696,9 @@ copyObject(const void *from)
        case T_RenameStmt:
            retval = _copyRenameStmt(from);
            break;
+       case T_AlterObjectDependsStmt:
+           retval = _copyAlterObjectDependsStmt(from);
+           break;
        case T_AlterObjectSchemaStmt:
            retval = _copyAlterObjectSchemaStmt(from);
            break;
index 854c062d32ff858b9cc371f027ec6b10a69b753e..6c0509602cd44d20dc28383450ba61a022a7e7dd 100644 (file)
@@ -1325,6 +1325,18 @@ _equalRenameStmt(const RenameStmt *a, const RenameStmt *b)
    return true;
 }
 
+static bool
+_equalAlterObjectDependsStmt(const AlterObjectDependsStmt *a, const AlterObjectDependsStmt *b)
+{
+   COMPARE_SCALAR_FIELD(objectType);
+   COMPARE_NODE_FIELD(relation);
+   COMPARE_NODE_FIELD(objname);
+   COMPARE_NODE_FIELD(objargs);
+   COMPARE_NODE_FIELD(extname);
+
+   return true;
+}
+
 static bool
 _equalAlterObjectSchemaStmt(const AlterObjectSchemaStmt *a, const AlterObjectSchemaStmt *b)
 {
@@ -3004,6 +3016,9 @@ equal(const void *a, const void *b)
        case T_RenameStmt:
            retval = _equalRenameStmt(a, b);
            break;
+       case T_AlterObjectDependsStmt:
+           retval = _equalAlterObjectDependsStmt(a, b);
+           break;
        case T_AlterObjectSchemaStmt:
            retval = _equalAlterObjectSchemaStmt(a, b);
            break;
index 12733528eb28b8181381ee5a618efad8bfa5e757..18ec5f03d81c6226eadf49d6184dc293d80f06cb 100644 (file)
@@ -233,7 +233,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
        AlterEventTrigStmt
        AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
        AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
-       AlterObjectSchemaStmt AlterOwnerStmt AlterOperatorStmt AlterSeqStmt AlterSystemStmt AlterTableStmt
+       AlterObjectDependsStmt AlterObjectSchemaStmt AlterOwnerStmt
+       AlterOperatorStmt AlterSeqStmt AlterSystemStmt AlterTableStmt
        AlterTblSpcStmt AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
        AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
        AlterRoleStmt AlterRoleSetStmt AlterPolicyStmt
@@ -578,7 +579,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
    CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
 
    DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
-   DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
+   DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
    DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
 
    EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
@@ -767,6 +768,7 @@ stmt :
            | AlterForeignTableStmt
            | AlterFunctionStmt
            | AlterGroupStmt
+           | AlterObjectDependsStmt
            | AlterObjectSchemaStmt
            | AlterOwnerStmt
            | AlterOperatorStmt
@@ -8025,6 +8027,55 @@ opt_set_data: SET DATA_P                         { $$ = 1; }
            | /*EMPTY*/                             { $$ = 0; }
        ;
 
+/*****************************************************************************
+ *
+ * ALTER THING name DEPENDS ON EXTENSION name
+ *
+ *****************************************************************************/
+
+AlterObjectDependsStmt:
+           ALTER FUNCTION function_with_argtypes DEPENDS ON EXTENSION name
+               {
+                   AlterObjectDependsStmt *n = makeNode(AlterObjectDependsStmt);
+                   n->objectType = OBJECT_FUNCTION;
+                   n->relation = NULL;
+                   n->objname = $3->funcname;
+                   n->objargs = $3->funcargs;
+                   n->extname = makeString($7);
+                   $$ = (Node *)n;
+               }
+           | ALTER TRIGGER name ON qualified_name DEPENDS ON EXTENSION name
+               {
+                   AlterObjectDependsStmt *n = makeNode(AlterObjectDependsStmt);
+                   n->objectType = OBJECT_TRIGGER;
+                   n->relation = $5;
+                   n->objname = list_make1(makeString($3));
+                   n->objargs = NIL;
+                   n->extname = makeString($9);
+                   $$ = (Node *)n;
+               }
+           | ALTER MATERIALIZED VIEW qualified_name DEPENDS ON EXTENSION name
+               {
+                   AlterObjectDependsStmt *n = makeNode(AlterObjectDependsStmt);
+                   n->objectType = OBJECT_MATVIEW;
+                   n->relation = $4;
+                   n->objname = NIL;
+                   n->objargs = NIL;
+                   n->extname = makeString($8);
+                   $$ = (Node *)n;
+               }
+           | ALTER INDEX qualified_name DEPENDS ON EXTENSION name
+               {
+                   AlterObjectDependsStmt *n = makeNode(AlterObjectDependsStmt);
+                   n->objectType = OBJECT_INDEX;
+                   n->relation = $3;
+                   n->objname = NIL;
+                   n->objargs = NIL;
+                   n->extname = makeString($7);
+                   $$ = (Node *)n;
+               }
+       ;
+
 /*****************************************************************************
  *
  * ALTER THING name SET SCHEMA name
@@ -13726,6 +13777,7 @@ unreserved_keyword:
            | DELETE_P
            | DELIMITER
            | DELIMITERS
+           | DEPENDS
            | DICTIONARY
            | DISABLE_P
            | DISCARD
index 4d0aac979fcc799507609cb83438a0ca927aa99e..ac50c2a03d18629ace71e3e926dfcf757791fbf8 100644 (file)
@@ -147,6 +147,7 @@ check_xact_readonly(Node *parsetree)
        case T_AlterFunctionStmt:
        case T_AlterRoleStmt:
        case T_AlterRoleSetStmt:
+       case T_AlterObjectDependsStmt:
        case T_AlterObjectSchemaStmt:
        case T_AlterOwnerStmt:
        case T_AlterOperatorStmt:
@@ -836,6 +837,19 @@ standard_ProcessUtility(Node *parsetree,
            }
            break;
 
+       case T_AlterObjectDependsStmt:
+           {
+               AlterObjectDependsStmt *stmt = (AlterObjectDependsStmt *) parsetree;
+
+               if (EventTriggerSupportsObjectType(stmt->objectType))
+                   ProcessUtilitySlow(parsetree, queryString,
+                                      context, params,
+                                      dest, completionTag);
+               else
+                   ExecAlterObjectDependsStmt(stmt, NULL);
+           }
+           break;
+
        case T_AlterObjectSchemaStmt:
            {
                AlterObjectSchemaStmt *stmt = (AlterObjectSchemaStmt *) parsetree;
@@ -1472,6 +1486,12 @@ ProcessUtilitySlow(Node *parsetree,
                address = ExecRenameStmt((RenameStmt *) parsetree);
                break;
 
+           case T_AlterObjectDependsStmt:
+               address =
+                   ExecAlterObjectDependsStmt((AlterObjectDependsStmt *) parsetree,
+                                              &secondaryObject);
+               break;
+
            case T_AlterObjectSchemaStmt:
                address =
                    ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree,
@@ -2192,6 +2212,10 @@ CreateCommandTag(Node *parsetree)
            tag = AlterObjectTypeCommandTag(((RenameStmt *) parsetree)->renameType);
            break;
 
+       case T_AlterObjectDependsStmt:
+           tag = AlterObjectTypeCommandTag(((AlterObjectDependsStmt *) parsetree)->objectType);
+           break;
+
        case T_AlterObjectSchemaStmt:
            tag = AlterObjectTypeCommandTag(((AlterObjectSchemaStmt *) parsetree)->objectType);
            break;
@@ -2822,6 +2846,10 @@ GetCommandLogLevel(Node *parsetree)
            lev = LOGSTMT_DDL;
            break;
 
+       case T_AlterObjectDependsStmt:
+           lev = LOGSTMT_DDL;
+           break;
+
        case T_AlterObjectSchemaStmt:
            lev = LOGSTMT_DDL;
            break;
index 2be9aa9f49c2290c85ff3a86dc9f7480545bfd9f..440bfba7f9129a041fe1eafd1e684fcdfd7b69b5 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 201604052
+#define CATALOG_VERSION_NO 201604053
 
 #endif
index d41abc4e48091891f32e56e34bbb269629ca8fac..09b36c5c78514697d7d5ddb8d1383c6eba4d602c 100644 (file)
  * this dependency type acts the same as an internal dependency, but it's
  * kept separate for clarity and to simplify pg_dump.
  *
+ * DEPENDENCY_AUTO_EXTENSION ('x'): the dependent object is not a member
+ * of the extension that is the referenced object (and so should not be
+ * ignored by pg_dump), but cannot function without the extension and
+ * should be dropped when the extension itself is.  The dependent object
+ * may be dropped on its own as well.
+ *
  * DEPENDENCY_PIN ('p'): there is no dependent object; this type of entry
  * is a signal that the system itself depends on the referenced object,
  * and so that object must never be deleted.  Entries of this type are
@@ -70,6 +76,7 @@ typedef enum DependencyType
    DEPENDENCY_AUTO = 'a',
    DEPENDENCY_INTERNAL = 'i',
    DEPENDENCY_EXTENSION = 'e',
+   DEPENDENCY_AUTO_EXTENSION = 'x',
    DEPENDENCY_PIN = 'p'
 } DependencyType;
 
index 640f7ffb1c521de3b909ec8f9fcfcdcd076020a5..87aa41497d6302ff8d5f0dededcbafebd0a7a714 100644 (file)
@@ -44,6 +44,10 @@ extern ObjectAddress get_object_address(ObjectType objtype, List *objname,
                   List *objargs, Relation *relp,
                   LOCKMODE lockmode, bool missing_ok);
 
+extern ObjectAddress get_object_address_rv(ObjectType objtype, RangeVar *rel,
+                  List *objname, List *objargs, Relation *relp,
+                  LOCKMODE lockmode, bool missing_ok);
+
 extern void check_object_ownership(Oid roleid,
                       ObjectType objtype, ObjectAddress address,
                       List *objname, List *objargs, Relation relation);
index cf92e3e859809d994d7b35f6d3a40bc17eae22a7..e116bc73b7465cf689b73303c16852a92861d8b4 100644 (file)
@@ -21,6 +21,8 @@
 
 extern ObjectAddress ExecRenameStmt(RenameStmt *stmt);
 
+extern ObjectAddress ExecAlterObjectDependsStmt(AlterObjectDependsStmt *stmt,
+                          ObjectAddress *refAddress);
 extern ObjectAddress ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
                          ObjectAddress *oldSchemaAddr);
 extern Oid AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
index 734df771eb2bb0f76b3fd9f3d22a5239f2154859..d888b41fd986c3a9e393e6f92c9baa97e201f1cc 100644 (file)
@@ -368,6 +368,7 @@ typedef enum NodeTag
    T_DeclareCursorStmt,
    T_CreateTableSpaceStmt,
    T_DropTableSpaceStmt,
+   T_AlterObjectDependsStmt,
    T_AlterObjectSchemaStmt,
    T_AlterOwnerStmt,
    T_AlterOperatorStmt,
index 8b958b422cbcb9f13f4ac880207bd79e0d8dfd02..714cf1550fd1e158cd88786c2e8576dccd18eeb4 100644 (file)
@@ -2535,6 +2535,20 @@ typedef struct RenameStmt
    bool        missing_ok;     /* skip error if missing? */
 } RenameStmt;
 
+/* ----------------------
+ * ALTER object DEPENDS ON EXTENSION extname
+ * ----------------------
+ */
+typedef struct AlterObjectDependsStmt
+{
+   NodeTag     type;
+   ObjectType  objectType;     /* OBJECT_FUNCTION, OBJECT_TRIGGER, etc */
+   RangeVar   *relation;       /* in case a table is involved */
+   List       *objname;        /* name of the object */
+   List       *objargs;        /* argument types, if applicable */
+   Value      *extname;        /* extension name */
+} AlterObjectDependsStmt;
+
 /* ----------------------
  *     ALTER object SET SCHEMA Statement
  * ----------------------
index 7de3404aa227f01b64cd4e5bb21dca281a3bf77a..17ffef53a70cbf1358fd5fa6803452a45b44a053 100644 (file)
@@ -125,6 +125,7 @@ PG_KEYWORD("definer", DEFINER, UNRESERVED_KEYWORD)
 PG_KEYWORD("delete", DELETE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
 PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
+PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD)
 PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
 PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
 PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
index 5691357c070682a481afe0d65d1b6082e221c610..283233782a045e14a1e0f1abaeb0561aab7c8f3b 100644 (file)
@@ -9,7 +9,7 @@ DATA = test_ext1--1.0.sql test_ext2--1.0.sql test_ext3--1.0.sql \
       test_ext4--1.0.sql test_ext5--1.0.sql test_ext_cyclic1--1.0.sql \
       test_ext_cyclic2--1.0.sql
 
-REGRESS = test_extensions
+REGRESS = test_extensions test_extdepend
 
 ifdef USE_PGXS
 PG_CONFIG = pg_config
diff --git a/src/test/modules/test_extensions/expected/test_extdepend.out b/src/test/modules/test_extensions/expected/test_extdepend.out
new file mode 100644 (file)
index 0000000..11e441d
--- /dev/null
@@ -0,0 +1,152 @@
+--
+-- test ALTER THING name DEPENDS ON EXTENSION
+--
+-- Common setup for all tests
+CREATE TABLE test_extdep_commands (command text);
+COPY test_extdep_commands FROM stdin;
+SELECT * FROM test_extdep_commands;
+                                 command                                 
+-------------------------------------------------------------------------
+  CREATE SCHEMA test_ext
+  CREATE EXTENSION test_ext5 SCHEMA test_ext
+  SET search_path TO test_ext
+  CREATE TABLE a (a1 int)
+  CREATE FUNCTION b() RETURNS TRIGGER LANGUAGE plpgsql AS               +
+    $$ BEGIN NEW.a1 := NEW.a1 + 42; RETURN NEW; END; $$
+  ALTER FUNCTION b() DEPENDS ON EXTENSION test_ext5
+  CREATE TRIGGER c BEFORE INSERT ON a FOR EACH ROW EXECUTE PROCEDURE b()
+  ALTER TRIGGER c ON a DEPENDS ON EXTENSION test_ext5
+  CREATE MATERIALIZED VIEW d AS SELECT * FROM a
+  ALTER MATERIALIZED VIEW d DEPENDS ON EXTENSION test_ext5
+  CREATE INDEX e ON a (a1)
+  ALTER INDEX e DEPENDS ON EXTENSION test_ext5
+  RESET search_path
+(17 rows)
+
+-- First, test that dependent objects go away when the extension is dropped.
+SELECT * FROM test_extdep_commands \gexec
+ CREATE SCHEMA test_ext
+ CREATE EXTENSION test_ext5 SCHEMA test_ext
+ SET search_path TO test_ext
+ CREATE TABLE a (a1 int)
+
+ CREATE FUNCTION b() RETURNS TRIGGER LANGUAGE plpgsql AS
+   $$ BEGIN NEW.a1 := NEW.a1 + 42; RETURN NEW; END; $$
+ ALTER FUNCTION b() DEPENDS ON EXTENSION test_ext5
+
+ CREATE TRIGGER c BEFORE INSERT ON a FOR EACH ROW EXECUTE PROCEDURE b()
+ ALTER TRIGGER c ON a DEPENDS ON EXTENSION test_ext5
+
+ CREATE MATERIALIZED VIEW d AS SELECT * FROM a
+ ALTER MATERIALIZED VIEW d DEPENDS ON EXTENSION test_ext5
+
+ CREATE INDEX e ON a (a1)
+ ALTER INDEX e DEPENDS ON EXTENSION test_ext5
+ RESET search_path
+-- make sure we have the right dependencies on the extension
+SELECT deptype, p.*
+  FROM pg_depend, pg_identify_object(classid, objid, objsubid) AS p
+ WHERE refclassid = 'pg_extension'::regclass AND
+       refobjid = (SELECT oid FROM pg_extension WHERE extname = 'test_ext5')
+ORDER BY type;
+ deptype |       type        |  schema  | name |    identity     
+---------+-------------------+----------+------+-----------------
+ x       | function          | test_ext |      | test_ext.b()
+ x       | index             | test_ext | e    | test_ext.e
+ x       | materialized view | test_ext | d    | test_ext.d
+ x       | trigger           |          |      | c on test_ext.a
+(4 rows)
+
+DROP EXTENSION test_ext5;
+-- anything still depending on the table?
+SELECT deptype, i.*
+  FROM pg_catalog.pg_depend, pg_identify_object(classid, objid, objsubid) i
+WHERE refclassid='pg_class'::regclass AND
+ refobjid='test_ext.a'::regclass AND NOT deptype IN ('i', 'a');
+ deptype | type | schema | name | identity 
+---------+------+--------+------+----------
+(0 rows)
+
+DROP SCHEMA test_ext CASCADE;
+NOTICE:  drop cascades to table test_ext.a
+-- Second test: If we drop the table, the objects are dropped too and no
+-- vestige remains in pg_depend.
+SELECT * FROM test_extdep_commands \gexec
+ CREATE SCHEMA test_ext
+ CREATE EXTENSION test_ext5 SCHEMA test_ext
+ SET search_path TO test_ext
+ CREATE TABLE a (a1 int)
+
+ CREATE FUNCTION b() RETURNS TRIGGER LANGUAGE plpgsql AS
+   $$ BEGIN NEW.a1 := NEW.a1 + 42; RETURN NEW; END; $$
+ ALTER FUNCTION b() DEPENDS ON EXTENSION test_ext5
+
+ CREATE TRIGGER c BEFORE INSERT ON a FOR EACH ROW EXECUTE PROCEDURE b()
+ ALTER TRIGGER c ON a DEPENDS ON EXTENSION test_ext5
+
+ CREATE MATERIALIZED VIEW d AS SELECT * FROM a
+ ALTER MATERIALIZED VIEW d DEPENDS ON EXTENSION test_ext5
+
+ CREATE INDEX e ON a (a1)
+ ALTER INDEX e DEPENDS ON EXTENSION test_ext5
+ RESET search_path
+DROP TABLE test_ext.a;     -- should fail, require cascade
+ERROR:  cannot drop table test_ext.a because other objects depend on it
+DETAIL:  materialized view test_ext.d depends on table test_ext.a
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE test_ext.a CASCADE;
+NOTICE:  drop cascades to materialized view test_ext.d
+-- anything still depending on the extension?  Should be only function b()
+SELECT deptype, i.*
+  FROM pg_catalog.pg_depend, pg_identify_object(classid, objid, objsubid) i
+ WHERE refclassid='pg_extension'::regclass AND
+ refobjid=(SELECT oid FROM pg_extension WHERE extname='test_ext5');
+ deptype |   type   |  schema  | name |   identity   
+---------+----------+----------+------+--------------
+ x       | function | test_ext |      | test_ext.b()
+(1 row)
+
+DROP EXTENSION test_ext5;
+DROP SCHEMA test_ext CASCADE;
+-- Third test: we can drop the objects individually
+SELECT * FROM test_extdep_commands \gexec
+ CREATE SCHEMA test_ext
+ CREATE EXTENSION test_ext5 SCHEMA test_ext
+ SET search_path TO test_ext
+ CREATE TABLE a (a1 int)
+
+ CREATE FUNCTION b() RETURNS TRIGGER LANGUAGE plpgsql AS
+   $$ BEGIN NEW.a1 := NEW.a1 + 42; RETURN NEW; END; $$
+ ALTER FUNCTION b() DEPENDS ON EXTENSION test_ext5
+
+ CREATE TRIGGER c BEFORE INSERT ON a FOR EACH ROW EXECUTE PROCEDURE b()
+ ALTER TRIGGER c ON a DEPENDS ON EXTENSION test_ext5
+
+ CREATE MATERIALIZED VIEW d AS SELECT * FROM a
+ ALTER MATERIALIZED VIEW d DEPENDS ON EXTENSION test_ext5
+
+ CREATE INDEX e ON a (a1)
+ ALTER INDEX e DEPENDS ON EXTENSION test_ext5
+ RESET search_path
+SET search_path TO test_ext;
+DROP TRIGGER c ON a;
+DROP FUNCTION b();
+DROP MATERIALIZED VIEW d;
+DROP INDEX e;
+SELECT deptype, i.*
+  FROM pg_catalog.pg_depend, pg_identify_object(classid, objid, objsubid) i
+ WHERE (refclassid='pg_extension'::regclass AND
+        refobjid=(SELECT oid FROM pg_extension WHERE extname='test_ext5'))
+   OR (refclassid='pg_class'::regclass AND refobjid='test_ext.a'::regclass)
+   AND NOT deptype IN ('i', 'a');
+ deptype | type | schema | name | identity 
+---------+------+--------+------+----------
+(0 rows)
+
+DROP TABLE a;
+DROP SCHEMA test_ext CASCADE;
+NOTICE:  drop cascades to extension test_ext5
diff --git a/src/test/modules/test_extensions/sql/test_extdepend.sql b/src/test/modules/test_extensions/sql/test_extdepend.sql
new file mode 100644 (file)
index 0000000..cf44145
--- /dev/null
@@ -0,0 +1,73 @@
+--
+-- test ALTER THING name DEPENDS ON EXTENSION
+--
+
+-- Common setup for all tests
+CREATE TABLE test_extdep_commands (command text);
+COPY test_extdep_commands FROM stdin;
+ CREATE SCHEMA test_ext
+ CREATE EXTENSION test_ext5 SCHEMA test_ext
+ SET search_path TO test_ext
+ CREATE TABLE a (a1 int)
+
+ CREATE FUNCTION b() RETURNS TRIGGER LANGUAGE plpgsql AS\n   $$ BEGIN NEW.a1 := NEW.a1 + 42; RETURN NEW; END; $$
+ ALTER FUNCTION b() DEPENDS ON EXTENSION test_ext5
+
+ CREATE TRIGGER c BEFORE INSERT ON a FOR EACH ROW EXECUTE PROCEDURE b()
+ ALTER TRIGGER c ON a DEPENDS ON EXTENSION test_ext5
+
+ CREATE MATERIALIZED VIEW d AS SELECT * FROM a
+ ALTER MATERIALIZED VIEW d DEPENDS ON EXTENSION test_ext5
+
+ CREATE INDEX e ON a (a1)
+ ALTER INDEX e DEPENDS ON EXTENSION test_ext5
+ RESET search_path
+\.
+
+SELECT * FROM test_extdep_commands;
+-- First, test that dependent objects go away when the extension is dropped.
+SELECT * FROM test_extdep_commands \gexec
+-- make sure we have the right dependencies on the extension
+SELECT deptype, p.*
+  FROM pg_depend, pg_identify_object(classid, objid, objsubid) AS p
+ WHERE refclassid = 'pg_extension'::regclass AND
+       refobjid = (SELECT oid FROM pg_extension WHERE extname = 'test_ext5')
+ORDER BY type;
+DROP EXTENSION test_ext5;
+-- anything still depending on the table?
+SELECT deptype, i.*
+  FROM pg_catalog.pg_depend, pg_identify_object(classid, objid, objsubid) i
+WHERE refclassid='pg_class'::regclass AND
+ refobjid='test_ext.a'::regclass AND NOT deptype IN ('i', 'a');
+DROP SCHEMA test_ext CASCADE;
+
+-- Second test: If we drop the table, the objects are dropped too and no
+-- vestige remains in pg_depend.
+SELECT * FROM test_extdep_commands \gexec
+DROP TABLE test_ext.a;     -- should fail, require cascade
+DROP TABLE test_ext.a CASCADE;
+-- anything still depending on the extension?  Should be only function b()
+SELECT deptype, i.*
+  FROM pg_catalog.pg_depend, pg_identify_object(classid, objid, objsubid) i
+ WHERE refclassid='pg_extension'::regclass AND
+ refobjid=(SELECT oid FROM pg_extension WHERE extname='test_ext5');
+DROP EXTENSION test_ext5;
+DROP SCHEMA test_ext CASCADE;
+
+-- Third test: we can drop the objects individually
+SELECT * FROM test_extdep_commands \gexec
+SET search_path TO test_ext;
+DROP TRIGGER c ON a;
+DROP FUNCTION b();
+DROP MATERIALIZED VIEW d;
+DROP INDEX e;
+
+SELECT deptype, i.*
+  FROM pg_catalog.pg_depend, pg_identify_object(classid, objid, objsubid) i
+ WHERE (refclassid='pg_extension'::regclass AND
+        refobjid=(SELECT oid FROM pg_extension WHERE extname='test_ext5'))
+   OR (refclassid='pg_class'::regclass AND refobjid='test_ext.a'::regclass)
+   AND NOT deptype IN ('i', 'a');
+
+DROP TABLE a;
+DROP SCHEMA test_ext CASCADE;
index c2511def9efdc2a0ebe3c5c962505256a4d69f6a..e293fc0bc8d801703d05c89913bcff4aeaa4369c 100644 (file)
@@ -67,6 +67,7 @@ AlterExtensionStmt
 AlterFdwStmt
 AlterForeignServerStmt
 AlterFunctionStmt
+AlterObjectDependsStmt
 AlterObjectSchemaStmt
 AlterOpFamilyStmt
 AlterOwnerStmt