From 5bc178b89f3ab93fb3845a941769c212f5eeaf1a Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Wed, 9 Feb 2011 11:55:32 -0500
Subject: [PATCH] Implement "ALTER EXTENSION ADD object".

This is an essential component of making the extension feature usable;
first because it's needed in the process of converting an existing
installation containing "loose" objects of an old contrib module into
the extension-based world, and second because we'll have to use it
in pg_dump --binary-upgrade, as per recent discussion.

Loosely based on part of Dimitri Fontaine's ALTER EXTENSION UPGRADE
patch.
---
 doc/src/sgml/extend.sgml              |  12 +
 doc/src/sgml/ref/alter_extension.sgml | 194 ++++++++++++--
 src/backend/commands/extension.c      |  59 ++++-
 src/backend/nodes/copyfuncs.c         |  16 ++
 src/backend/nodes/equalfuncs.c        |  14 +
 src/backend/parser/gram.y             | 186 +++++++++++++-
 src/backend/tcop/utility.c            | 357 ++++++++++----------------
 src/include/commands/extension.h      |   2 +
 src/include/nodes/nodes.h             |   1 +
 src/include/nodes/parsenodes.h        |  15 +-
 10 files changed, 601 insertions(+), 255 deletions(-)

diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 206eb6b9017..31ea0487f1e 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -331,6 +331,18 @@
     data; see below.)
    </para>
 
+   <para>
+    The kinds of SQL objects that can be members of an extension are shown in
+    the description of <xref linkend="sql-alterextension">.  Notably, objects
+    that are database-cluster-wide, such as databases, roles, and tablespaces,
+    cannot be extension members since an extension is only known within one
+    database.  (Although an extension script is not prohibited from creating
+    such objects, if it does so they will not be tracked as part of the
+    extension.)  Also notice that while a table can be a member of an
+    extension, its subsidiary objects such as indexes are not directly
+    considered members of the extension.
+   </para>
+
    <sect2>
     <title>Extension Files</title>
 
diff --git a/doc/src/sgml/ref/alter_extension.sgml b/doc/src/sgml/ref/alter_extension.sgml
index 1b29d274cd6..6613418fd23 100644
--- a/doc/src/sgml/ref/alter_extension.sgml
+++ b/doc/src/sgml/ref/alter_extension.sgml
@@ -23,7 +23,32 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-ALTER EXTENSION <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replaceable class="PARAMETER">new_schema</replaceable>
+ALTER EXTENSION <replaceable class="PARAMETER">extension_name</replaceable> SET SCHEMA <replaceable class="PARAMETER">new_schema</replaceable>
+ALTER EXTENSION <replaceable class="PARAMETER">extension_name</replaceable> ADD <replaceable class="PARAMETER">member_object</replaceable>
+
+<phrase>where <replaceable class="PARAMETER">member_object</replaceable> is:</phrase>
+
+  AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> (<replaceable class="PARAMETER">agg_type</replaceable> [, ...] ) |
+  CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
+  CONVERSION <replaceable class="PARAMETER">object_name</replaceable> |
+  DOMAIN <replaceable class="PARAMETER">object_name</replaceable> |
+  FOREIGN DATA WRAPPER <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> [, ...] ] ) |
+  OPERATOR <replaceable class="PARAMETER">operator_name</replaceable> (<replaceable class="PARAMETER">left_type</replaceable>, <replaceable class="PARAMETER">right_type</replaceable>) |
+  OPERATOR CLASS <replaceable class="PARAMETER">object_name</replaceable> USING <replaceable class="parameter">index_method</replaceable> |
+  OPERATOR FAMILY <replaceable class="PARAMETER">object_name</replaceable> USING <replaceable class="parameter">index_method</replaceable> |
+  [ PROCEDURAL ] LANGUAGE <replaceable class="PARAMETER">object_name</replaceable> |
+  SCHEMA <replaceable class="PARAMETER">object_name</replaceable> |
+  SEQUENCE <replaceable class="PARAMETER">object_name</replaceable> |
+  SERVER <replaceable class="PARAMETER">object_name</replaceable> |
+  TABLE <replaceable class="PARAMETER">object_name</replaceable> |
+  TEXT SEARCH CONFIGURATION <replaceable class="PARAMETER">object_name</replaceable> |
+  TEXT SEARCH DICTIONARY <replaceable class="PARAMETER">object_name</replaceable> |
+  TEXT SEARCH PARSER <replaceable class="PARAMETER">object_name</replaceable> |
+  TEXT SEARCH TEMPLATE <replaceable class="PARAMETER">object_name</replaceable> |
+  TYPE <replaceable class="PARAMETER">object_name</replaceable> |
+  VIEW <replaceable class="PARAMETER">object_name</replaceable>
 </synopsis>
  </refsynopsisdiv>
 
@@ -31,8 +56,8 @@ ALTER EXTENSION <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <re
   <title>Description</title>
 
   <para>
-   <command>ALTER EXTENSION</command> changes the definition of an existing extension.
-   Currently there is only one subform:
+   <command>ALTER EXTENSION</command> changes the definition of an installed
+   extension.  There are several subforms:
 
    <variablelist>
    <varlistentry>
@@ -41,37 +66,151 @@ ALTER EXTENSION <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <re
      <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.
+      succeed.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>ADD <replaceable class="PARAMETER">member_object</replaceable></literal></term>
+    <listitem>
+     <para>
+      This form adds an existing object to the extension.  This is mainly
+      useful in extension upgrade scripts.  The object will subsequently
+      be treated as a member of the extension; notably, it can only be
+      dropped by dropping the extension.
      </para>
     </listitem>
    </varlistentry>
    </variablelist>
+
+   See <xref linkend="extend-extensions"> for more information about these
+   operations.
+  </para>
+
+  <para>
+   Only superusers can execute <command>ALTER EXTENSION</command>.
   </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>
+   <variablelist>
+    <varlistentry>
+     <term><replaceable class="PARAMETER">extension_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>
+
+    <varlistentry>
+     <term><replaceable class="parameter">object_name</replaceable></term>
+     <term><replaceable class="parameter">agg_name</replaceable></term>
+     <term><replaceable class="parameter">function_name</replaceable></term>
+     <term><replaceable class="parameter">operator_name</replaceable></term>
+     <listitem>
+      <para>
+       The name of an object to be added to the extension.  Names of tables,
+       aggregates, domains, foreign tables, functions, operators,
+       operator classes, operator families, sequences, text search objects,
+       types, and views can be schema-qualified.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><replaceable class="parameter">agg_type</replaceable></term>
+     <listitem>
+      <para>
+       An input data type on which the aggregate function operates.
+       To reference a zero-argument aggregate function, write <literal>*</>
+       in place of the list of input data types.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><replaceable>source_type</replaceable></term>
+     <listitem>
+      <para>
+       The name of the source data type of the cast.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><replaceable>target_type</replaceable></term>
+     <listitem>
+      <para>
+       The name of the target data type of the cast.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><replaceable class="parameter">argmode</replaceable></term>
+
+     <listitem>
+      <para>
+       The mode of a function argument: <literal>IN</>, <literal>OUT</>,
+       <literal>INOUT</>, or <literal>VARIADIC</>.
+       If omitted, the default is <literal>IN</>.
+       Note that <command>ALTER EXTENSION</command> does not actually pay
+       any attention to <literal>OUT</> arguments, since only the input
+       arguments are needed to determine the function's identity.
+       So it is sufficient to list the <literal>IN</>, <literal>INOUT</>,
+       and <literal>VARIADIC</> arguments.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><replaceable class="parameter">argname</replaceable></term>
+
+     <listitem>
+      <para>
+       The name of a function argument.
+       Note that <command>ALTER EXTENSION</command> does not actually pay
+       any attention to argument names, since only the argument data
+       types are needed to determine the function's identity.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><replaceable class="parameter">argtype</replaceable></term>
+
+     <listitem>
+      <para>
+       The data type(s) of the function's arguments (optionally
+       schema-qualified), if any.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>PROCEDURAL</literal></term>
+
+     <listitem>
+      <para>
+       This is a noise word.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
   </para>
  </refsect1>
 
@@ -83,6 +222,13 @@ ALTER EXTENSION <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <re
    to <literal>utils</literal>:
 <programlisting>
 ALTER EXTENSION hstore SET SCHEMA utils;
+</programlisting>
+  </para>
+
+  <para>
+   To add an existing function to the <literal>hstore</literal> extension:
+<programlisting>
+ALTER EXTENSION hstore ADD FUNCTION populate_record(anyelement, hstore);
 </programlisting>
   </para>
  </refsect1>
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ee42d2e13df..41667fb3afc 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1134,7 +1134,7 @@ pg_extension_config_dump(PG_FUNCTION_ARGS)
 	if (getExtensionOfObject(RelationRelationId, tableoid) !=
 		CurrentExtensionObject)
 		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("table \"%s\" is not a member of the extension being created",
 						tablename)));
 
@@ -1392,3 +1392,60 @@ AlterExtensionNamespace(List *names, const char *newschema)
 	changeDependencyFor(ExtensionRelationId, extensionOid,
 						NamespaceRelationId, oldNspOid, nspOid);
 }
+
+/*
+ * Execute ALTER EXTENSION ADD
+ */
+void
+ExecAlterExtensionAddStmt(AlterExtensionAddStmt *stmt)
+{
+	ObjectAddress	extension;
+	ObjectAddress	object;
+	Relation		relation;
+
+	/*
+	 * For now, insist on superuser privilege.  Later we might want to
+	 * relax this to ownership of the target object and the extension.
+	 */
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 (errmsg("must be superuser to use ALTER EXTENSION"))));
+
+	/* Do this next to fail on nonexistent extension */
+	extension.classId = ExtensionRelationId;
+	extension.objectId = get_extension_oid(stmt->extname, false);
+	extension.objectSubId = 0;
+
+	/*
+	 * Translate the parser representation that identifies the object into
+	 * an ObjectAddress.  get_object_address() will throw an error if the
+	 * object does not exist, and will also acquire a lock on the object
+	 * to guard against concurrent DROP and ALTER EXTENSION ADD operations.
+	 */
+	object = get_object_address(stmt->objtype, stmt->objname, stmt->objargs,
+								&relation, ShareUpdateExclusiveLock);
+
+	/*
+	 * Complain if object is already attached to some extension.
+	 */
+	if (getExtensionOfObject(object.classId, object.objectId) != InvalidOid)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("%s is already a member of an extension",
+						getObjectDescription(&object))));
+
+	/*
+	 * OK, add the dependency.
+	 */
+	recordDependencyOn(&object, &extension, DEPENDENCY_EXTENSION);
+
+	/*
+	 * If get_object_address() opened the relation for us, we close it to keep
+	 * the reference count correct - but we retain any locks acquired by
+	 * get_object_address() until commit time, to guard against concurrent
+	 * activity.
+	 */
+	if (relation != NULL)
+		relation_close(relation, NoLock);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 851186146dd..3d898326d7a 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3250,6 +3250,19 @@ _copyCreateExtensionStmt(CreateExtensionStmt *from)
 	return newnode;
 }
 
+static AlterExtensionAddStmt *
+_copyAlterExtensionAddStmt(AlterExtensionAddStmt *from)
+{
+	AlterExtensionAddStmt *newnode = makeNode(AlterExtensionAddStmt);
+
+	COPY_STRING_FIELD(extname);
+	COPY_SCALAR_FIELD(objtype);
+	COPY_NODE_FIELD(objname);
+	COPY_NODE_FIELD(objargs);
+
+	return newnode;
+}
+
 static CreateFdwStmt *
 _copyCreateFdwStmt(CreateFdwStmt *from)
 {
@@ -4252,6 +4265,9 @@ copyObject(void *from)
 		case T_CreateExtensionStmt:
 			retval = _copyCreateExtensionStmt(from);
 			break;
+		case T_AlterExtensionAddStmt:
+			retval = _copyAlterExtensionAddStmt(from);
+			break;
 		case T_CreateFdwStmt:
 			retval = _copyCreateFdwStmt(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 00d23ccfa56..9baa7862e6c 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1654,6 +1654,17 @@ _equalCreateExtensionStmt(CreateExtensionStmt *a, CreateExtensionStmt *b)
 	return true;
 }
 
+static bool
+_equalAlterExtensionAddStmt(AlterExtensionAddStmt *a, AlterExtensionAddStmt *b)
+{
+	COMPARE_STRING_FIELD(extname);
+	COMPARE_SCALAR_FIELD(objtype);
+	COMPARE_NODE_FIELD(objname);
+	COMPARE_NODE_FIELD(objargs);
+
+	return true;
+}
+
 static bool
 _equalCreateFdwStmt(CreateFdwStmt *a, CreateFdwStmt *b)
 {
@@ -2857,6 +2868,9 @@ equal(void *a, void *b)
 		case T_CreateExtensionStmt:
 			retval = _equalCreateExtensionStmt(a, b);
 			break;
+		case T_AlterExtensionAddStmt:
+			retval = _equalAlterExtensionAddStmt(a, b);
+			break;
 		case T_CreateFdwStmt:
 			retval = _equalCreateFdwStmt(a, b);
 			break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 4c4536b9be3..a61f3dc7ba4 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -185,7 +185,7 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
 		AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
 		AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
 		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
-		AlterForeignTableStmt
+		AlterExtensionAddStmt AlterForeignTableStmt
 		AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
 		AlterRoleStmt AlterRoleSetStmt
 		AlterDefaultPrivilegesStmt DefACLAction
@@ -664,6 +664,7 @@ stmt :
 			| AlterDefaultPrivilegesStmt
 			| AlterDomainStmt
 			| AlterEnumStmt
+			| AlterExtensionAddStmt
 			| AlterFdwStmt
 			| AlterForeignServerStmt
 			| AlterForeignTableStmt
@@ -3248,6 +3249,189 @@ create_extension_opt_item:
 				}
 		;
 
+/*****************************************************************************
+ *
+ * ALTER EXTENSION name ADD object-identifier
+ *
+ *****************************************************************************/
+
+AlterExtensionAddStmt:
+			ALTER EXTENSION name ADD_P AGGREGATE func_name aggr_args
+				{
+					AlterExtensionAddStmt *n = makeNode(AlterExtensionAddStmt);
+					n->extname = $3;
+					n->objtype = OBJECT_AGGREGATE;
+					n->objname = $6;
+					n->objargs = $7;
+					$$ = (Node *)n;
+				}
+			| ALTER EXTENSION name ADD_P CAST '(' Typename AS Typename ')'
+				{
+					AlterExtensionAddStmt *n = makeNode(AlterExtensionAddStmt);
+					n->extname = $3;
+					n->objtype = OBJECT_CAST;
+					n->objname = list_make1($7);
+					n->objargs = list_make1($9);
+					$$ = (Node *) n;
+				}
+			| ALTER EXTENSION name ADD_P CONVERSION_P any_name
+				{
+					AlterExtensionAddStmt *n = makeNode(AlterExtensionAddStmt);
+					n->extname = $3;
+					n->objtype = OBJECT_CONVERSION;
+					n->objname = $6;
+					$$ = (Node *)n;
+				}
+			| ALTER EXTENSION name ADD_P DOMAIN_P any_name
+				{
+					AlterExtensionAddStmt *n = makeNode(AlterExtensionAddStmt);
+					n->extname = $3;
+					n->objtype = OBJECT_DOMAIN;
+					n->objname = $6;
+					$$ = (Node *)n;
+				}
+			| ALTER EXTENSION name ADD_P FUNCTION function_with_argtypes
+				{
+					AlterExtensionAddStmt *n = makeNode(AlterExtensionAddStmt);
+					n->extname = $3;
+					n->objtype = OBJECT_FUNCTION;
+					n->objname = $6->funcname;
+					n->objargs = $6->funcargs;
+					$$ = (Node *)n;
+				}
+			| ALTER EXTENSION name ADD_P opt_procedural LANGUAGE name
+				{
+					AlterExtensionAddStmt *n = makeNode(AlterExtensionAddStmt);
+					n->extname = $3;
+					n->objtype = OBJECT_LANGUAGE;
+					n->objname = list_make1(makeString($7));
+					$$ = (Node *)n;
+				}
+			| ALTER EXTENSION name ADD_P OPERATOR any_operator oper_argtypes
+				{
+					AlterExtensionAddStmt *n = makeNode(AlterExtensionAddStmt);
+					n->extname = $3;
+					n->objtype = OBJECT_OPERATOR;
+					n->objname = $6;
+					n->objargs = $7;
+					$$ = (Node *)n;
+				}
+			| ALTER EXTENSION name ADD_P OPERATOR CLASS any_name USING access_method
+				{
+					AlterExtensionAddStmt *n = makeNode(AlterExtensionAddStmt);
+					n->extname = $3;
+					n->objtype = OBJECT_OPCLASS;
+					n->objname = $7;
+					n->objargs = list_make1(makeString($9));
+					$$ = (Node *)n;
+				}
+			| ALTER EXTENSION name ADD_P OPERATOR FAMILY any_name USING access_method
+				{
+					AlterExtensionAddStmt *n = makeNode(AlterExtensionAddStmt);
+					n->extname = $3;
+					n->objtype = OBJECT_OPFAMILY;
+					n->objname = $7;
+					n->objargs = list_make1(makeString($9));
+					$$ = (Node *)n;
+				}
+			| ALTER EXTENSION name ADD_P SCHEMA name
+				{
+					AlterExtensionAddStmt *n = makeNode(AlterExtensionAddStmt);
+					n->extname = $3;
+					n->objtype = OBJECT_SCHEMA;
+					n->objname = list_make1(makeString($6));
+					$$ = (Node *)n;
+				}
+			| ALTER EXTENSION name ADD_P TABLE any_name
+				{
+					AlterExtensionAddStmt *n = makeNode(AlterExtensionAddStmt);
+					n->extname = $3;
+					n->objtype = OBJECT_TABLE;
+					n->objname = $6;
+					$$ = (Node *)n;
+				}
+			| ALTER EXTENSION name ADD_P TEXT_P SEARCH PARSER any_name
+				{
+					AlterExtensionAddStmt *n = makeNode(AlterExtensionAddStmt);
+					n->extname = $3;
+					n->objtype = OBJECT_TSPARSER;
+					n->objname = $8;
+					$$ = (Node *)n;
+				}
+			| ALTER EXTENSION name ADD_P TEXT_P SEARCH DICTIONARY any_name
+				{
+					AlterExtensionAddStmt *n = makeNode(AlterExtensionAddStmt);
+					n->extname = $3;
+					n->objtype = OBJECT_TSDICTIONARY;
+					n->objname = $8;
+					$$ = (Node *)n;
+				}
+			| ALTER EXTENSION name ADD_P TEXT_P SEARCH TEMPLATE any_name
+				{
+					AlterExtensionAddStmt *n = makeNode(AlterExtensionAddStmt);
+					n->extname = $3;
+					n->objtype = OBJECT_TSTEMPLATE;
+					n->objname = $8;
+					$$ = (Node *)n;
+				}
+			| ALTER EXTENSION name ADD_P TEXT_P SEARCH CONFIGURATION any_name
+				{
+					AlterExtensionAddStmt *n = makeNode(AlterExtensionAddStmt);
+					n->extname = $3;
+					n->objtype = OBJECT_TSCONFIGURATION;
+					n->objname = $8;
+					$$ = (Node *)n;
+				}
+			| ALTER EXTENSION name ADD_P SEQUENCE any_name
+				{
+					AlterExtensionAddStmt *n = makeNode(AlterExtensionAddStmt);
+					n->extname = $3;
+					n->objtype = OBJECT_SEQUENCE;
+					n->objname = $6;
+					$$ = (Node *)n;
+				}
+			| ALTER EXTENSION name ADD_P VIEW any_name
+				{
+					AlterExtensionAddStmt *n = makeNode(AlterExtensionAddStmt);
+					n->extname = $3;
+					n->objtype = OBJECT_VIEW;
+					n->objname = $6;
+					$$ = (Node *)n;
+				}
+			| ALTER EXTENSION name ADD_P FOREIGN TABLE any_name
+				{
+					AlterExtensionAddStmt *n = makeNode(AlterExtensionAddStmt);
+					n->extname = $3;
+					n->objtype = OBJECT_FOREIGN_TABLE;
+					n->objname = $7;
+					$$ = (Node *)n;
+				}
+			| ALTER EXTENSION name ADD_P FOREIGN DATA_P WRAPPER name
+				{
+					AlterExtensionAddStmt *n = makeNode(AlterExtensionAddStmt);
+					n->extname = $3;
+					n->objtype = OBJECT_FDW;
+					n->objname = list_make1(makeString($8));
+					$$ = (Node *)n;
+				}
+			| ALTER EXTENSION name ADD_P SERVER name
+				{
+					AlterExtensionAddStmt *n = makeNode(AlterExtensionAddStmt);
+					n->extname = $3;
+					n->objtype = OBJECT_FOREIGN_SERVER;
+					n->objname = list_make1(makeString($6));
+					$$ = (Node *)n;
+				}
+			| ALTER EXTENSION name ADD_P TYPE_P any_name
+				{
+					AlterExtensionAddStmt *n = makeNode(AlterExtensionAddStmt);
+					n->extname = $3;
+					n->objtype = OBJECT_TYPE;
+					n->objname = $6;
+					$$ = (Node *)n;
+				}
+		;
+
 /*****************************************************************************
  *
  * 		QUERY:
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 10a4438995f..9d1562af7dc 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -212,6 +212,7 @@ check_xact_readonly(Node *parsetree)
 		case T_AlterTSDictionaryStmt:
 		case T_AlterTSConfigurationStmt:
 		case T_CreateExtensionStmt:
+		case T_AlterExtensionAddStmt:
 		case T_CreateFdwStmt:
 		case T_AlterFdwStmt:
 		case T_DropFdwStmt:
@@ -600,6 +601,10 @@ standard_ProcessUtility(Node *parsetree,
 			CreateExtension((CreateExtensionStmt *) parsetree);
 			break;
 
+		case T_AlterExtensionAddStmt:
+			ExecAlterExtensionAddStmt((AlterExtensionAddStmt *) parsetree);
+			break;
+
 		case T_CreateFdwStmt:
 			CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
 			break;
@@ -1421,6 +1426,123 @@ QueryReturnsTuples(Query *parsetree)
 #endif
 
 
+/*
+ * AlterObjectTypeCommandTag
+ *		helper function for CreateCommandTag
+ *
+ * This covers most cases where ALTER is used with an ObjectType enum.
+ */
+static const char *
+AlterObjectTypeCommandTag(ObjectType objtype)
+{
+	const char *tag;
+
+	switch (objtype)
+	{
+		case OBJECT_AGGREGATE:
+			tag = "ALTER AGGREGATE";
+			break;
+		case OBJECT_ATTRIBUTE:
+			tag = "ALTER TYPE";
+			break;
+		case OBJECT_CAST:
+			tag = "ALTER CAST";
+			break;
+		case OBJECT_COLUMN:
+			tag = "ALTER TABLE";
+			break;
+		case OBJECT_CONSTRAINT:
+			tag = "ALTER TABLE";
+			break;
+		case OBJECT_CONVERSION:
+			tag = "ALTER CONVERSION";
+			break;
+		case OBJECT_DATABASE:
+			tag = "ALTER DATABASE";
+			break;
+		case OBJECT_DOMAIN:
+			tag = "ALTER DOMAIN";
+			break;
+		case OBJECT_EXTENSION:
+			tag = "ALTER EXTENSION";
+			break;
+		case OBJECT_FDW:
+			tag = "ALTER FOREIGN DATA WRAPPER";
+			break;
+		case OBJECT_FOREIGN_SERVER:
+			tag = "ALTER SERVER";
+			break;
+		case OBJECT_FOREIGN_TABLE:
+			tag = "ALTER FOREIGN TABLE";
+			break;
+		case OBJECT_FUNCTION:
+			tag = "ALTER FUNCTION";
+			break;
+		case OBJECT_INDEX:
+			tag = "ALTER INDEX";
+			break;
+		case OBJECT_LANGUAGE:
+			tag = "ALTER LANGUAGE";
+			break;
+		case OBJECT_LARGEOBJECT:
+			tag = "ALTER LARGE OBJECT";
+			break;
+		case OBJECT_OPCLASS:
+			tag = "ALTER OPERATOR CLASS";
+			break;
+		case OBJECT_OPERATOR:
+			tag = "ALTER OPERATOR";
+			break;
+		case OBJECT_OPFAMILY:
+			tag = "ALTER OPERATOR FAMILY";
+			break;
+		case OBJECT_ROLE:
+			tag = "ALTER ROLE";
+			break;
+		case OBJECT_RULE:
+			tag = "ALTER RULE";
+			break;
+		case OBJECT_SCHEMA:
+			tag = "ALTER SCHEMA";
+			break;
+		case OBJECT_SEQUENCE:
+			tag = "ALTER SEQUENCE";
+			break;
+		case OBJECT_TABLE:
+			tag = "ALTER TABLE";
+			break;
+		case OBJECT_TABLESPACE:
+			tag = "ALTER TABLESPACE";
+			break;
+		case OBJECT_TRIGGER:
+			tag = "ALTER TRIGGER";
+			break;
+		case OBJECT_TSCONFIGURATION:
+			tag = "ALTER TEXT SEARCH CONFIGURATION";
+			break;
+		case OBJECT_TSDICTIONARY:
+			tag = "ALTER TEXT SEARCH DICTIONARY";
+			break;
+		case OBJECT_TSPARSER:
+			tag = "ALTER TEXT SEARCH PARSER";
+			break;
+		case OBJECT_TSTEMPLATE:
+			tag = "ALTER TEXT SEARCH TEMPLATE";
+			break;
+		case OBJECT_TYPE:
+			tag = "ALTER TYPE";
+			break;
+		case OBJECT_VIEW:
+			tag = "ALTER VIEW";
+			break;
+		default:
+			tag = "???";
+			break;
+	}
+
+	return tag;
+}
+
 /*
  * CreateCommandTag
  *		utility to get a string representation of the command operation,
@@ -1558,6 +1680,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "CREATE EXTENSION";
 			break;
 
+		case T_AlterExtensionAddStmt:
+			tag = "ALTER EXTENSION";
+			break;
+
 		case T_CreateFdwStmt:
 			tag = "CREATE FOREIGN DATA WRAPPER";
 			break;
@@ -1665,235 +1791,19 @@ CreateCommandTag(Node *parsetree)
 			break;
 
 		case T_RenameStmt:
-			switch (((RenameStmt *) parsetree)->renameType)
-			{
-				case OBJECT_AGGREGATE:
-					tag = "ALTER AGGREGATE";
-					break;
-				case OBJECT_CONVERSION:
-					tag = "ALTER CONVERSION";
-					break;
-				case OBJECT_DATABASE:
-					tag = "ALTER DATABASE";
-					break;
-				case OBJECT_FUNCTION:
-					tag = "ALTER FUNCTION";
-					break;
-				case OBJECT_INDEX:
-					tag = "ALTER INDEX";
-					break;
-				case OBJECT_LANGUAGE:
-					tag = "ALTER LANGUAGE";
-					break;
-				case OBJECT_OPCLASS:
-					tag = "ALTER OPERATOR CLASS";
-					break;
-				case OBJECT_OPFAMILY:
-					tag = "ALTER OPERATOR FAMILY";
-					break;
-				case OBJECT_ROLE:
-					tag = "ALTER ROLE";
-					break;
-				case OBJECT_SCHEMA:
-					tag = "ALTER SCHEMA";
-					break;
-				case OBJECT_SEQUENCE:
-					tag = "ALTER SEQUENCE";
-					break;
-				case OBJECT_COLUMN:
-					{
-						RenameStmt *stmt = (RenameStmt *) parsetree;
-						if (stmt->relationType == OBJECT_FOREIGN_TABLE)
-							tag = "ALTER FOREIGN TABLE";
-						else
-							tag = "ALTER TABLE";
-					}
-					break;
-				case OBJECT_TABLE:
-					tag = "ALTER TABLE";
-					break;
-				case OBJECT_TABLESPACE:
-					tag = "ALTER TABLESPACE";
-					break;
-				case OBJECT_TRIGGER:
-					tag = "ALTER TRIGGER";
-					break;
-				case OBJECT_VIEW:
-					tag = "ALTER VIEW";
-					break;
-				case OBJECT_FOREIGN_TABLE:
-					tag = "ALTER FOREIGN TABLE";
-					break;
-				case OBJECT_TSPARSER:
-					tag = "ALTER TEXT SEARCH PARSER";
-					break;
-				case OBJECT_TSDICTIONARY:
-					tag = "ALTER TEXT SEARCH DICTIONARY";
-					break;
-				case OBJECT_TSTEMPLATE:
-					tag = "ALTER TEXT SEARCH TEMPLATE";
-					break;
-				case OBJECT_TSCONFIGURATION:
-					tag = "ALTER TEXT SEARCH CONFIGURATION";
-					break;
-				case OBJECT_ATTRIBUTE:
-				case OBJECT_TYPE:
-					tag = "ALTER TYPE";
-					break;
-				default:
-					tag = "???";
-					break;
-			}
+			tag = AlterObjectTypeCommandTag(((RenameStmt *) parsetree)->renameType);
 			break;
 
 		case T_AlterObjectSchemaStmt:
-			switch (((AlterObjectSchemaStmt *) parsetree)->objectType)
-			{
-				case OBJECT_AGGREGATE:
-					tag = "ALTER AGGREGATE";
-					break;
-				case OBJECT_CONVERSION:
-					tag = "ALTER CONVERSION";
-					break;
-				case OBJECT_DOMAIN:
-					tag = "ALTER DOMAIN";
-					break;
-				case OBJECT_EXTENSION:
-					tag = "ALTER EXTENSION";
-					break;
-				case OBJECT_OPERATOR:
-					tag = "ALTER OPERATOR";
-					break;
-				case OBJECT_OPCLASS:
-					tag = "ALTER OPERATOR CLASS";
-					break;
-				case OBJECT_OPFAMILY:
-					tag = "ALTER OPERATOR FAMILY";
-					break;
-				case OBJECT_FUNCTION:
-					tag = "ALTER FUNCTION";
-					break;
-				case OBJECT_SEQUENCE:
-					tag = "ALTER SEQUENCE";
-					break;
-				case OBJECT_TABLE:
-					tag = "ALTER TABLE";
-					break;
-				case OBJECT_TYPE:
-					tag = "ALTER TYPE";
-					break;
-				case OBJECT_TSPARSER:
-					tag = "ALTER TEXT SEARCH PARSER";
-					break;
-				case OBJECT_TSDICTIONARY:
-					tag = "ALTER TEXT SEARCH DICTIONARY";
-					break;
-				case OBJECT_TSTEMPLATE:
-					tag = "ALTER TEXT SEARCH TEMPLATE";
-					break;
-				case OBJECT_TSCONFIGURATION:
-					tag = "ALTER TEXT SEARCH CONFIGURATION";
-					break;
-				case OBJECT_VIEW:
-					tag = "ALTER VIEW";
-					break;
-				case OBJECT_FOREIGN_TABLE:
-					tag = "ALTER FOREIGN TABLE";
-					break;
-				default:
-					tag = "???";
-					break;
-			}
+			tag = AlterObjectTypeCommandTag(((AlterObjectSchemaStmt *) parsetree)->objectType);
 			break;
 
 		case T_AlterOwnerStmt:
-			switch (((AlterOwnerStmt *) parsetree)->objectType)
-			{
-				case OBJECT_AGGREGATE:
-					tag = "ALTER AGGREGATE";
-					break;
-				case OBJECT_CONVERSION:
-					tag = "ALTER CONVERSION";
-					break;
-				case OBJECT_DATABASE:
-					tag = "ALTER DATABASE";
-					break;
-				case OBJECT_DOMAIN:
-					tag = "ALTER DOMAIN";
-					break;
-				case OBJECT_FUNCTION:
-					tag = "ALTER FUNCTION";
-					break;
-				case OBJECT_LANGUAGE:
-					tag = "ALTER LANGUAGE";
-					break;
-				case OBJECT_LARGEOBJECT:
-					tag = "ALTER LARGE OBJECT";
-					break;
-				case OBJECT_OPERATOR:
-					tag = "ALTER OPERATOR";
-					break;
-				case OBJECT_OPCLASS:
-					tag = "ALTER OPERATOR CLASS";
-					break;
-				case OBJECT_OPFAMILY:
-					tag = "ALTER OPERATOR FAMILY";
-					break;
-				case OBJECT_SCHEMA:
-					tag = "ALTER SCHEMA";
-					break;
-				case OBJECT_TABLESPACE:
-					tag = "ALTER TABLESPACE";
-					break;
-				case OBJECT_TYPE:
-					tag = "ALTER TYPE";
-					break;
-				case OBJECT_TSCONFIGURATION:
-					tag = "ALTER TEXT SEARCH CONFIGURATION";
-					break;
-				case OBJECT_TSDICTIONARY:
-					tag = "ALTER TEXT SEARCH DICTIONARY";
-					break;
-				case OBJECT_FDW:
-					tag = "ALTER FOREIGN DATA WRAPPER";
-					break;
-				case OBJECT_FOREIGN_SERVER:
-					tag = "ALTER SERVER";
-					break;
-				case OBJECT_FOREIGN_TABLE:
-					tag = "ALTER FOREIGN TABLE";
-					break;
-				default:
-					tag = "???";
-					break;
-			}
+			tag = AlterObjectTypeCommandTag(((AlterOwnerStmt *) parsetree)->objectType);
 			break;
 
 		case T_AlterTableStmt:
-			switch (((AlterTableStmt *) parsetree)->relkind)
-			{
-				case OBJECT_TABLE:
-					tag = "ALTER TABLE";
-					break;
-				case OBJECT_INDEX:
-					tag = "ALTER INDEX";
-					break;
-				case OBJECT_SEQUENCE:
-					tag = "ALTER SEQUENCE";
-					break;
-				case OBJECT_TYPE:
-					tag = "ALTER TYPE";
-					break;
-				case OBJECT_VIEW:
-					tag = "ALTER VIEW";
-					break;
-				case OBJECT_FOREIGN_TABLE:
-					tag = "ALTER FOREIGN TABLE";
-					break;
-				default:
-					tag = "???";
-					break;
-			}
+			tag = AlterObjectTypeCommandTag(((AlterTableStmt *) parsetree)->relkind);
 			break;
 
 		case T_AlterDomainStmt:
@@ -2391,18 +2301,13 @@ GetCommandLogLevel(Node *parsetree)
 			break;
 
 		case T_CreateTableSpaceStmt:
-			lev = LOGSTMT_DDL;
-			break;
-
 		case T_DropTableSpaceStmt:
-			lev = LOGSTMT_DDL;
-			break;
-
 		case T_AlterTableSpaceOptionsStmt:
 			lev = LOGSTMT_DDL;
 			break;
 
 		case T_CreateExtensionStmt:
+		case T_AlterExtensionAddStmt:
 			lev = LOGSTMT_DDL;
 			break;
 
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 10d08935a00..d0e94556f50 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -32,6 +32,8 @@ extern void CreateExtension(CreateExtensionStmt *stmt);
 extern void RemoveExtensions(DropStmt *stmt);
 extern void RemoveExtensionById(Oid extId);
 
+extern void ExecAlterExtensionAddStmt(AlterExtensionAddStmt *stmt);
+
 extern Oid	get_extension_oid(const char *extname, bool missing_ok);
 extern char *get_extension_name(Oid ext_oid);
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 1ca5f1ef9a1..1ce97386315 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -356,6 +356,7 @@ typedef enum NodeTag
 	T_SecLabelStmt,
 	T_CreateForeignTableStmt,
 	T_CreateExtensionStmt,
+	T_AlterExtensionAddStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5de4dbd5ec1..2116c94d0d3 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1060,13 +1060,13 @@ typedef struct SetOperationStmt
 /*
  * When a command can act on several kinds of objects with only one
  * parse structure required, use these constants to designate the
- * object type.
+ * object type.  Note that commands typically don't support all the types.
  */
 
 typedef enum ObjectType
 {
 	OBJECT_AGGREGATE,
-	OBJECT_ATTRIBUTE,			/* type's attribute, when distinct from column */
+	OBJECT_ATTRIBUTE,		/* type's attribute, when distinct from column */
 	OBJECT_CAST,
 	OBJECT_COLUMN,
 	OBJECT_CONSTRAINT,
@@ -1535,7 +1535,7 @@ typedef struct AlterTableSpaceOptionsStmt
 } AlterTableSpaceOptionsStmt;
 
 /* ----------------------
- *		Create Extension Statement
+ *		Create/Alter Extension Statements
  * ----------------------
  */
 
@@ -1546,6 +1546,15 @@ typedef struct CreateExtensionStmt
 	List	   *options;		/* List of DefElem nodes */
 } CreateExtensionStmt;
 
+typedef struct AlterExtensionAddStmt
+{
+	NodeTag		type;
+	char	   *extname;		/* Extension's name */
+	ObjectType	objtype;		/* Object's type */
+	List	   *objname;		/* Qualified name of the object */
+	List	   *objargs;		/* Arguments if needed (eg, for functions) */
+} AlterExtensionAddStmt;
+
 /* ----------------------
  *		Create/Drop FOREIGN DATA WRAPPER Statements
  * ----------------------
-- 
GitLab