modules.patch

application/octet-stream

Filename: modules.patch
Type: application/octet-stream
Part: 0
Message: support for CREATE MODULE

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
File+
doc/src/sgml/ref/allfiles.sgml 3 0
doc/src/sgml/ref/alter_module.sgml 119 0
doc/src/sgml/ref/create_module.sgml 191 0
doc/src/sgml/ref/drop_module.sgml 110 0
src/backend/catalog/aclchk.c 325 0
src/backend/catalog/dependency.c 6 0
src/backend/catalog/genbki.pl 8 0
src/backend/catalog/Makefile 3 2
src/backend/catalog/namespace.c 346 51
src/backend/catalog/objectaddress.c 91 1
src/backend/catalog/pg_aggregate.c 1 0
src/backend/catalog/pg_module.c 118 0
src/backend/catalog/pg_proc.c 11 2
src/backend/commands/alter.c 3 0
src/backend/commands/dropcmds.c 5 0
src/backend/commands/event_trigger.c 6 0
src/backend/commands/functioncmds.c 45 8
src/backend/commands/indexcmds.c 1 1
src/backend/commands/Makefile 1 0
src/backend/commands/modulecmds.c 513 0
src/backend/commands/opclasscmds.c 2 2
src/backend/commands/seclabel.c 1 0
src/backend/commands/statscmds.c 1 1
src/backend/commands/tablecmds.c 1 0
src/backend/commands/typecmds.c 4 0
src/backend/nodes/list.c 0 4
src/backend/parser/gram.y 506 34
src/backend/parser/parse_func.c 17 2
src/backend/parser/parse_oper.c 1 1
src/backend/parser/parse_type.c 1 1
src/backend/parser/parse_utilcmd.c 79 0
src/backend/tcop/utility.c 174 13
src/backend/utils/adt/acl.c 4 0
src/backend/utils/cache/lsyscache.c 26 0
src/backend/utils/cache/syscache.c 31 3
src/backend/utils/errcodes.txt 3 0
src/bin/pg_dump/common.c 23 0
src/bin/pg_dump/pg_dump.c 160 5
src/bin/pg_dump/pg_dump.h 15 1
src/bin/pg_dump/pg_dump_sort.c 5 0
src/include/catalog/catversion.h 2 1
src/include/catalog/dependency.h 1 0
src/include/catalog/namespace.h 10 3
src/include/catalog/pg_module.dat 14 0
src/include/catalog/pg_module.h 64 0
src/include/catalog/pg_namespace.dat 1 1
src/include/catalog/pg_proc.h 5 1
src/include/commands/defrem.h 5 0
src/include/commands/modulecmds.h 29 0
src/include/nodes/nodes.h 5 0
src/include/nodes/parsenodes.h 72 1
src/include/parser/kwlist.h 1 0
src/include/parser/parse_utilcmd.h 1 0
src/include/tcop/cmdtaglist.h 6 0
src/include/tcop/utility.h 8 1
src/include/utils/acl.h 5 0
src/include/utils/lsyscache.h 1 0
src/include/utils/syscache.h 2 0
src/test/regress/expected/create_module.out 202 0
src/test/regress/expected/misc_sanity.out 2 1
src/test/regress/expected/oidjoins.out 1 0
src/test/regress/expected/regproc.out 2 2
src/test/regress/expected/sanity_check.out 1 0
src/test/regress/parallel_schedule 1 1
src/test/regress/sql/create_module.sql 132 0
src/tools/pgindent/typedefs.list 4 0
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index d67270ccc3..9d69aa24ff 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -22,6 +22,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY alterLanguage      SYSTEM "alter_language.sgml">
 <!ENTITY alterLargeObject   SYSTEM "alter_large_object.sgml">
 <!ENTITY alterMaterializedView SYSTEM "alter_materialized_view.sgml">
+<!ENTITY alterModule        SYSTEM "alter_module.sgml">
 <!ENTITY alterOperator      SYSTEM "alter_operator.sgml">
 <!ENTITY alterOperatorClass SYSTEM "alter_opclass.sgml">
 <!ENTITY alterOperatorFamily SYSTEM "alter_opfamily.sgml">
@@ -83,6 +84,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY createRole         SYSTEM "create_role.sgml">
 <!ENTITY createRule         SYSTEM "create_rule.sgml">
 <!ENTITY createSchema       SYSTEM "create_schema.sgml">
+<!ENTITY createModule       SYSTEM "create_module.sgml">
 <!ENTITY createSequence     SYSTEM "create_sequence.sgml">
 <!ENTITY createServer       SYSTEM "create_server.sgml">
 <!ENTITY createStatistics   SYSTEM "create_statistics.sgml">
@@ -132,6 +134,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropRoutine        SYSTEM "drop_routine.sgml">
 <!ENTITY dropRule           SYSTEM "drop_rule.sgml">
 <!ENTITY dropSchema         SYSTEM "drop_schema.sgml">
+<!ENTITY dropModule         SYSTEM "drop_module.sgml">
 <!ENTITY dropSequence       SYSTEM "drop_sequence.sgml">
 <!ENTITY dropServer         SYSTEM "drop_server.sgml">
 <!ENTITY dropStatistics     SYSTEM "drop_statistics.sgml">
diff --git a/doc/src/sgml/ref/alter_module.sgml b/doc/src/sgml/ref/alter_module.sgml
new file mode 100644
index 0000000000..84905ecde4
--- /dev/null
+++ b/doc/src/sgml/ref/alter_module.sgml
@@ -0,0 +1,119 @@
+<!--
+doc/src/sgml/ref/alter_module.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-altermodule">
+ <indexterm zone="sql-altermodule">
+  <primary>ALTER MODULE</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>ALTER MODULE</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>ALTER MODULE</refname>
+  <refpurpose>change the definition of a module</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+ALTER MODULE <replaceable>name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+ALTER MODULE <replaceable>name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
+ALTER MODULE <replaceable>module_name</replaceable> CREATE [ OR REPLACE ] FUNCTION
+    <replaceable class="parameter">function_name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [ { DEFAULT | = } <replaceable class="parameter">default_expr</replaceable> ] [, ...] ] )
+    [ RETURNS <replaceable class="parameter">rettype</replaceable>
+      | RETURNS TABLE ( <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">column_type</replaceable> [, ...] ) ]
+  { LANGUAGE <replaceable class="parameter">lang_name</replaceable>
+    | TRANSFORM { FOR TYPE <replaceable class="parameter">type_name</replaceable> } [, ... ]
+    | WINDOW
+    | { IMMUTABLE | STABLE | VOLATILE }
+    | [ NOT ] LEAKPROOF
+    | { CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT }
+    | { [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER }
+    | PARALLEL { UNSAFE | RESTRICTED | SAFE }
+    | COST <replaceable class="parameter">execution_cost</replaceable>
+    | ROWS <replaceable class="parameter">result_rows</replaceable>
+    | SUPPORT <replaceable class="parameter">support_function</replaceable>
+    | SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
+    | AS '<replaceable class="parameter">definition</replaceable>'
+    | AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+    | <replaceable class="parameter">sql_body</replaceable>
+  } ...
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>ALTER MODULE</command> changes the definition of a module.
+  </para>
+
+  <para>
+   You must own the module to use <command>ALTER MODULE</command>.
+  </para>
+
+  <para>
+   <command>CREATE FUNCTION</command> defines a new function in the module.
+   <command>CREATE OR REPLACE FUNCTION</command> will either create a
+   new function, or replace an existing definition in the module.
+   To be able to define a function, the user must have the
+   <literal>CREATE</literal> privilege on the module.
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable>name</replaceable></term>
+    <listitem>
+     <para>
+      The name of an existing module.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable>new_name</replaceable></term>
+    <listitem>
+     <para>
+      The new name of the module.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_owner</replaceable></term>
+    <listitem>
+     <para>
+      The new owner of the module.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+
+  For parameters to ALTER MODULE module_name CREATE [ OR REPLACE ] FUNCTION ..., see
+  <xref linkend="sql-createfunction"/>.
+  For parameters to ALTER MODULE module_name CREATE [ OR REPLACE ] PROCEDURE ..., see
+  <xref linkend="sql-createprocedure"/>.
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createmodule"/></member>
+   <member><xref linkend="sql-dropmodule"/></member>
+   <member><xref linkend="sql-createfunction"/></member>
+   <member><xref linkend="sql-createprocedure"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/create_module.sgml b/doc/src/sgml/ref/create_module.sgml
new file mode 100644
index 0000000000..892ee5f532
--- /dev/null
+++ b/doc/src/sgml/ref/create_module.sgml
@@ -0,0 +1,191 @@
+<!--
+doc/src/sgml/ref/create_module.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-createmodule">
+ <indexterm zone="sql-createmodule">
+  <primary>CREATE MODULE</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE MODULE</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE MODULE</refname>
+  <refpurpose>define a new module</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE MODULE module_name [ OWNER <replaceable class="parameter">role_specification</replaceable> ] [ <replaceable class="parameter">module_element</replaceable> [ ... ] ]
+CREATE MODULE IF NOT EXISTS module_name [ OWNER <replaceable class="parameter">role_specification</replaceable> ]
+
+<phrase>where <replaceable class="parameter">role_specification</replaceable> can be:</phrase>
+
+    <replaceable class="parameter">user_name</replaceable>
+  | CURRENT_ROLE
+  | CURRENT_USER
+  | SESSION_USER
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE MODULE</command> adds a new module
+   into the current schema.
+   The module name must be distinct from the name of any existing module
+   in the current schema.
+  </para>
+
+  <para>
+   A module is a grouping:
+   it contains named objects (functions, and procedures)
+   whose names can duplicate those of other objects existing in other
+   modules.  Named objects are accessed by <quote>qualifying</quote>
+   their names with the module name as a prefix.
+  </para>
+
+  <para>
+   Optionally, <command>CREATE MODULE</command> can include subcommands
+   to create objects (functions, and procedures) within the new module.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+    <variablelist>
+     <varlistentry>
+      <term><replaceable class="parameter">module_name</replaceable></term>
+      <listitem>
+       <para>
+        The name of a module to be created.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="parameter">user_name</replaceable></term>
+      <listitem>
+       <para>
+        The role name of the user who will own the new module.  If omitted,
+        defaults to the user executing the command.  To create a schema
+        owned by another role, you must be a direct or indirect member of
+        that role, or be a superuser.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="parameter">module_element</replaceable></term>
+      <listitem>
+       <para>
+        An SQL statement defining an object to be created within the
+        schema. Currently, only <command>CREATE
+        FUNCTION</command> and <command>CREATE PROCEDURE</command> are accepted as clauses
+        within <command>CREATE MODULE</command>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>IF NOT EXISTS</literal></term>
+      <listitem>
+       <para>
+        Do nothing (except issuing a notice) if a module with the same name
+        already exists in the current schema.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   To create a module, the invoking user, if not a superuser, must have the
+   <literal>CREATE</literal> privilege for the current schema.
+   If the invoking user is a superuser, this check is bypassed.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Create a module:
+<programlisting>
+CREATE MODULE mymodule;
+</programlisting>
+  </para>
+
+  <para>
+   Create a module named <literal>test</literal> that will be owned by user
+   <literal>jill</literal>, unless there already is a module named <literal>test</literal>
+   in the current schema.
+   (It does not matter whether <literal>joe</literal> owns the pre-existing module.)
+<programlisting>
+CREATE SCHEMA IF NOT EXISTS test OWNER jill;
+</programlisting>
+  </para>
+
+  <para>
+   Create a module and create a procedure and function within it:
+<programlisting>
+CREATE TABLE cm_test (a int, b text);
+
+CREATE MODULE mtest1
+  CREATE PROCEDURE m2testa(x text)
+     LANGUAGE SQL
+     AS $$
+     INSERT INTO cm_test VALUES (1, x);
+     $$
+  CREATE FUNCTION m1testa() RETURNS text
+     LANGUAGE sql
+     RETURN '1x';
+</programlisting>
+   Notice that the individual subcommands do not end with semicolons.
+  </para>
+
+  <para>
+   The following is an equivalent way of accomplishing the same result:
+<programlisting>
+CREATE MODULE mtest1;
+CREATE PROCEDURE mtest1.m2testa(x text)
+     LANGUAGE SQL
+     AS $$
+     INSERT INTO cm_test VALUES (1, x);
+     $$;
+CREATE FUNCTION mtest1.m1testa() RETURNS text
+     LANGUAGE sql
+     RETURN '1x';
+</programlisting></para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   The <literal>IF NOT EXISTS</literal> option is a
+   <productname>PostgreSQL</productname> extension.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-alterschema"/></member>
+   <member><xref linkend="sql-dropschema"/></member>
+ </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_module.sgml b/doc/src/sgml/ref/drop_module.sgml
new file mode 100644
index 0000000000..69bb7b7641
--- /dev/null
+++ b/doc/src/sgml/ref/drop_module.sgml
@@ -0,0 +1,110 @@
+<!--
+doc/src/sgml/ref/drop_module.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-dropmodule">
+ <indexterm zone="sql-dropmodule">
+  <primary>DROP MODULE</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP MODULE</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP MODULE</refname>
+  <refpurpose>remove a module</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP MODULE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [, ...] [ CASCADE | RESTRICT ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP MODULE</command> removes modules from the database.
+  </para>
+
+  <para>
+   A module can only be dropped by its owner or a superuser.  Note that
+   the owner can drop the module (and thereby all contained objects)
+   even if they do not own some of the objects within the module.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the module does not exist. A notice is issued
+      in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of a module.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>CASCADE</literal></term>
+    <listitem>
+     <para>
+      Automatically drop objects (procedures, functions) that are
+      contained in the module,
+      and in turn all objects that depend on those objects
+      (see <xref linkend="ddl-depend"/>).
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESTRICT</literal></term>
+    <listitem>
+     <para>
+      Refuse to drop the module if it contains any objects.  This is
+      the default.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   To remove module <literal>mymodule</literal> from the database,
+   along with all functions and procedures it contains:
+
+<programlisting>
+DROP MODULE mymoule CASCADE;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-altermodule"/></member>
+   <member><xref linkend="sql-createmodule"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index df5268fbc3..6be6ca1a04 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -35,6 +35,7 @@ OBJS = \
 	pg_enum.o \
 	pg_inherits.o \
 	pg_largeobject.o \
+	pg_module.o \
 	pg_namespace.o \
 	pg_operator.o \
 	pg_proc.o \
@@ -59,7 +60,7 @@ CATALOG_HEADERS := \
 	pg_language.h pg_largeobject_metadata.h pg_largeobject.h pg_aggregate.h \
 	pg_statistic.h pg_statistic_ext.h pg_statistic_ext_data.h \
 	pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
-	pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
+	pg_cast.h pg_enum.h pg_module.h pg_namespace.h pg_conversion.h pg_depend.h \
 	pg_database.h pg_db_role_setting.h pg_tablespace.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 \
@@ -79,7 +80,7 @@ POSTGRES_BKI_SRCS := $(addprefix $(top_srcdir)/src/include/catalog/, $(CATALOG_H
 POSTGRES_BKI_DATA = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	pg_aggregate.dat pg_am.dat pg_amop.dat pg_amproc.dat pg_authid.dat \
 	pg_cast.dat pg_class.dat pg_collation.dat pg_conversion.dat \
-	pg_database.dat pg_language.dat \
+	pg_database.dat pg_language.dat pg_module.dat \
 	pg_namespace.dat pg_opclass.dat pg_operator.dat pg_opfamily.dat \
 	pg_proc.dat pg_range.dat pg_tablespace.dat \
 	pg_ts_config.dat pg_ts_config_map.dat pg_ts_dict.dat pg_ts_parser.dat \
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 1dd03a8e51..1e6e4ef7cb 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -44,6 +44,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_largeobject.h"
 #include "catalog/pg_largeobject_metadata.h"
+#include "catalog/pg_module.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
@@ -109,6 +110,7 @@ static void ExecGrant_ForeignServer(InternalGrant *grantStmt);
 static void ExecGrant_Function(InternalGrant *grantStmt);
 static void ExecGrant_Language(InternalGrant *grantStmt);
 static void ExecGrant_Largeobject(InternalGrant *grantStmt);
+static void ExecGrant_Module(InternalGrant *grantStmt);
 static void ExecGrant_Namespace(InternalGrant *grantStmt);
 static void ExecGrant_Tablespace(InternalGrant *grantStmt);
 static void ExecGrant_Type(InternalGrant *grantStmt);
@@ -118,6 +120,7 @@ static void SetDefaultACL(InternalDefaultACL *iacls);
 
 static List *objectNamesToOids(ObjectType objtype, List *objnames);
 static List *objectsInSchemaToOids(ObjectType objtype, List *nspnames);
+static List *objectsInModuleToOids(ObjectType objtype, List *objnames);
 static List *getRelationsInNamespace(Oid namespaceId, char relkind);
 static void expand_col_privileges(List *colnames, Oid table_oid,
 								  AclMode this_privileges,
@@ -240,6 +243,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
 		case OBJECT_LARGEOBJECT:
 			whole_mask = ACL_ALL_RIGHTS_LARGEOBJECT;
 			break;
+		case OBJECT_MODULE:
+			whole_mask = ACL_ALL_RIGHTS_MODULE;
+			break;
 		case OBJECT_SCHEMA:
 			whole_mask = ACL_ALL_RIGHTS_SCHEMA;
 			break;
@@ -395,6 +401,11 @@ ExecuteGrantStmt(GrantStmt *stmt)
 		case ACL_TARGET_ALL_IN_SCHEMA:
 			istmt.objects = objectsInSchemaToOids(stmt->objtype, stmt->objects);
 			break;
+		case ACL_TARGET_ALL_IN_MODULE:
+			/* For execute privilege on a module. Create privilege on a module
+			   will go to ACL_TARGET_OBJECT case */
+			istmt.objects = objectsInModuleToOids(stmt->objtype, stmt->objects);
+			break;
 			/* ACL_TARGET_DEFAULTS should not be seen here */
 		default:
 			elog(ERROR, "unrecognized GrantStmt.targtype: %d",
@@ -498,6 +509,10 @@ ExecuteGrantStmt(GrantStmt *stmt)
 			all_privileges = ACL_ALL_RIGHTS_FOREIGN_SERVER;
 			errormsg = gettext_noop("invalid privilege type %s for foreign server");
 			break;
+		case OBJECT_MODULE:
+			all_privileges = ACL_ALL_RIGHTS_MODULE;
+			errormsg = gettext_noop("invalid privilege type %s for module");
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) stmt->objtype);
@@ -597,6 +612,9 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 		case OBJECT_SCHEMA:
 			ExecGrant_Namespace(istmt);
 			break;
+		case OBJECT_MODULE:
+			ExecGrant_Module(istmt);
+			break;
 		case OBJECT_TABLESPACE:
 			ExecGrant_Tablespace(istmt);
 			break;
@@ -701,6 +719,19 @@ objectNamesToOids(ObjectType objtype, List *objnames)
 				objects = lappend_oid(objects, lobjOid);
 			}
 			break;
+		case OBJECT_MODULE:
+			foreach(cell, objnames)
+			{
+				Oid        namespaceId;
+				Oid        moduleId;
+				char       *moduleName = NULL;
+				List       *modNameList = (List *) lfirst(cell);
+				namespaceId = QualifiedNameWithModuleGetCreationNamespace(modNameList,
+															  &moduleId, &moduleName);
+				moduleId = get_module_oid_from_name(namespaceId, moduleName, true);
+				objects = lappend_oid(objects, moduleId);
+			}
+			break;
 		case OBJECT_SCHEMA:
 			foreach(cell, objnames)
 			{
@@ -858,6 +889,78 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
 	return objects;
 }
 
+/*
+ * objectsInModuleToOids
+ *
+ * Find all objects of a given type in a specified module, and return a list
+ * of their Oids.
+ */
+static List *
+objectsInModuleToOids(ObjectType objtype, List *moduleNames)
+{
+	List	   *objectOids = NIL;
+	ListCell   *cell;
+
+	if ((objtype != OBJECT_FUNCTION) && (objtype != OBJECT_PROCEDURE) && (objtype != OBJECT_ROUTINE))
+				/* should not happen */
+				elog(ERROR, "unrecognized objtype in GrantStmt on modules: %d",
+					 (int) objtype);
+
+	foreach(cell, moduleNames)
+	{
+		List	   *moduleNameList = (List *) lfirst(cell);
+		char       *moduleName = NULL;
+		Oid     	namespaceId;
+		Oid     	moduleId;
+		ScanKeyData key[3];
+		Relation	rel;
+		TableScanDesc scan;
+		HeapTuple	tuple;
+		int         keycount = 3;
+
+		/* Convert qualified names List of module to a name and namespace */
+		namespaceId = QualifiedNameWithModuleGetCreationNamespace(moduleNameList,
+															  &moduleId, &moduleName);
+		moduleId = get_module_oid_from_name(namespaceId, moduleName, true);
+
+		ScanKeyInit(&key[0],
+					Anum_pg_proc_pronamespace,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(namespaceId));
+
+		ScanKeyInit(&key[1],
+					Anum_pg_proc_promodule,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(moduleId));
+
+		if (objtype == OBJECT_FUNCTION)
+			/* includes aggregates and window functions */
+			ScanKeyInit(&key[2],
+						Anum_pg_proc_prokind,
+						BTEqualStrategyNumber, F_CHARNE,
+						CharGetDatum(PROKIND_PROCEDURE));
+		else if (objtype == OBJECT_PROCEDURE)
+			ScanKeyInit(&key[2],
+						Anum_pg_proc_prokind,
+						BTEqualStrategyNumber, F_CHAREQ,
+						CharGetDatum(PROKIND_PROCEDURE));
+
+		rel = table_open(ProcedureRelationId, AccessShareLock);
+		scan = table_beginscan_catalog(rel, keycount, key);
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			Oid	oid = ((Form_pg_proc) GETSTRUCT(tuple))->oid;
+			objectOids = lappend_oid(objectOids, oid);
+		}
+
+		table_endscan(scan);
+		table_close(rel, AccessShareLock);
+	}
+
+	return objectOids;
+}
+
 /*
  * getRelationsInNamespace
  *
@@ -2844,6 +2947,130 @@ ExecGrant_Largeobject(InternalGrant *istmt)
 	table_close(relation, RowExclusiveLock);
 }
 
+static void
+ExecGrant_Module(InternalGrant *istmt)
+{
+	Relation	relation;
+	ListCell   *cell;
+
+	if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS)
+		istmt->privileges = ACL_ALL_RIGHTS_MODULE;
+
+	relation = table_open(ModuleRelationId, RowExclusiveLock);
+
+	foreach(cell, istmt->objects)
+	{
+		Oid	modid = lfirst_oid(cell);
+		Form_pg_module pg_mod_tuple;
+		Datum aclDatum;
+		bool isNull;
+		AclMode	avail_goptions;
+		AclMode	this_privileges;
+		Acl *old_acl;
+		Acl	*new_acl;
+		Oid	grantorId;
+		Oid	ownerId;
+		HeapTuple tuple;
+		HeapTuple newtuple;
+		Datum values[Natts_pg_module];
+		bool nulls[Natts_pg_module];
+		bool replaces[Natts_pg_module];
+		int	noldmembers;
+		int	nnewmembers;
+		Oid *oldmembers;
+		Oid *newmembers;
+
+		tuple = SearchSysCache1(MODULEOID, ObjectIdGetDatum(modid));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for module %u", modid);
+
+		pg_mod_tuple = (Form_pg_module) GETSTRUCT(tuple);
+
+		/*
+		 * Get owner ID and working copy of existing ACL. If there's no ACL,
+		 * substitute the proper default.
+		 */
+		ownerId = pg_mod_tuple->modowner;
+		aclDatum = SysCacheGetAttr(MODULENAME, tuple,
+								   Anum_pg_module_modacl,
+								   &isNull);
+		if (isNull)
+		{
+			old_acl = acldefault(OBJECT_MODULE, ownerId);
+			/* There are no old member roles according to the catalogs */
+			noldmembers = 0;
+			oldmembers = NULL;
+		}
+		else
+		{
+			old_acl = DatumGetAclPCopy(aclDatum);
+			/* Get the roles mentioned in the existing ACL */
+			noldmembers = aclmembers(old_acl, &oldmembers);
+		}
+
+		/* Determine ID to do the grant as, and available grant options */
+		select_best_grantor(GetUserId(), istmt->privileges,
+							old_acl, ownerId,
+							&grantorId, &avail_goptions);
+
+		/*
+		 * Restrict the privileges to what we can actually grant, and emit the
+		 * standards-mandated warning and error messages.
+		 */
+		this_privileges =
+			restrict_and_check_grant(istmt->is_grant, avail_goptions,
+									 istmt->all_privs, istmt->privileges,
+									 modid, grantorId, OBJECT_MODULE,
+									 NameStr(pg_mod_tuple->modname),
+									 0, NULL);
+
+		/*
+		 * Generate new ACL.
+		 */
+		new_acl = merge_acl_with_grant(old_acl, istmt->is_grant,
+									   istmt->grant_option, istmt->behavior,
+									   istmt->grantees, this_privileges,
+									   grantorId, ownerId);
+
+		/*
+		 * We need the members of both old and new ACLs so we can correct the
+		 * shared dependency information.
+		 */
+		nnewmembers = aclmembers(new_acl, &newmembers);
+
+		/* finished building new ACL value, now insert it */
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, false, sizeof(nulls));
+		MemSet(replaces, false, sizeof(replaces));
+
+		replaces[Anum_pg_module_modacl - 1] = true;
+		values[Anum_pg_module_modacl - 1] = PointerGetDatum(new_acl);
+
+		newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values,
+									 nulls, replaces);
+
+		CatalogTupleUpdate(relation, &newtuple->t_self, newtuple);
+
+		/* Update initial privileges for extensions */
+		recordExtensionInitPriv(modid, ModuleRelationId, 0, new_acl);
+
+		/* Update the shared dependency ACL info */
+		updateAclDependencies(ModuleRelationId, pg_mod_tuple->oid, 0,
+							  ownerId,
+							  noldmembers, oldmembers,
+							  nnewmembers, newmembers);
+
+		ReleaseSysCache(tuple);
+
+		pfree(new_acl);
+
+		/* prevent error when processing duplicate objects */
+		CommandCounterIncrement();
+	}
+
+	table_close(relation, RowExclusiveLock);
+}
+
 static void
 ExecGrant_Namespace(InternalGrant *istmt)
 {
@@ -3367,6 +3594,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_MATVIEW:
 						msg = gettext_noop("permission denied for materialized view %s");
 						break;
+					case OBJECT_MODULE:
+						msg = gettext_noop("permission denied for module %s");
+						break;
 					case OBJECT_OPCLASS:
 						msg = gettext_noop("permission denied for operator class %s");
 						break;
@@ -3496,6 +3726,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_MATVIEW:
 						msg = gettext_noop("must be owner of materialized view %s");
 						break;
+					case OBJECT_MODULE:
+						msg = gettext_noop("must be owner of module %s");
+						break;
 					case OBJECT_OPCLASS:
 						msg = gettext_noop("must be owner of operator class %s");
 						break;
@@ -3655,6 +3888,8 @@ pg_aclmask(ObjectType objtype, Oid table_oid, AttrNumber attnum, Oid roleid,
 												   mask, how, NULL);
 		case OBJECT_SCHEMA:
 			return pg_namespace_aclmask(table_oid, roleid, mask, how);
+		case OBJECT_MODULE:
+			return pg_module_aclmask(table_oid, roleid, mask, how);
 		case OBJECT_STATISTIC_EXT:
 			elog(ERROR, "grantable rights not supported for statistics objects");
 			/* not reached, but keep compiler quiet */
@@ -4282,6 +4517,59 @@ pg_namespace_aclmask(Oid nsp_oid, Oid roleid,
 	return result;
 }
 
+/*
+ * Exported routine for examining a user's privileges for a module
+ */
+AclMode
+pg_module_aclmask(Oid mod_oid, Oid roleid, AclMode mask, AclMaskHow how)
+{
+	AclMode	result;
+	HeapTuple tuple;
+	Datum aclDatum;
+	bool isNull;
+	Acl *acl;
+	Oid ownerId;
+
+	/* Superusers bypass all permission checking. */
+	if (superuser_arg(roleid))
+		return mask;
+
+	/*
+	 * Get the module's ACL from pg_module
+	 */
+	tuple = SearchSysCache1(MODULEOID, ObjectIdGetDatum(mod_oid));
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_MODULE),
+				 errmsg("module with OID %u does not exist", mod_oid)));
+
+	ownerId = ((Form_pg_module) GETSTRUCT(tuple))->modowner;
+
+	aclDatum = SysCacheGetAttr(MODULEOID, tuple, Anum_pg_module_modacl,
+							   &isNull);
+	if (isNull)
+	{
+		/* No ACL, so build default ACL */
+		acl = acldefault(OBJECT_MODULE, ownerId);
+		aclDatum = (Datum) 0;
+	}
+	else
+	{
+		/* detoast ACL if necessary */
+		acl = DatumGetAclP(aclDatum);
+	}
+
+	result = aclmask(acl, roleid, ownerId, mask, how);
+
+	/* if we have a detoasted copy, free it */
+	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+		pfree(acl);
+
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
 /*
  * Exported routine for examining a user's privileges for a tablespace
  */
@@ -4763,6 +5051,18 @@ pg_namespace_aclcheck(Oid nsp_oid, Oid roleid, AclMode mode)
 		return ACLCHECK_NO_PRIV;
 }
 
+/*
+ * Exported routine for checking a user's access privileges to a module
+ */
+AclResult
+pg_module_aclcheck(Oid mod_oid, Oid roleid, AclMode mode)
+{
+	if (pg_module_aclmask(mod_oid, roleid, mode, ACLMASK_ANY) != 0)
+		return ACLCHECK_OK;
+	else
+		return ACLCHECK_NO_PRIV;
+}
+
 /*
  * Exported routine for checking a user's access privileges to a tablespace
  */
@@ -5015,6 +5315,31 @@ pg_namespace_ownercheck(Oid nsp_oid, Oid roleid)
 	return has_privs_of_role(roleid, ownerId);
 }
 
+/*
+ * Ownership check for a module (specified by OID).
+ */
+bool
+pg_module_ownercheck(Oid mod_oid, Oid roleid)
+{
+	HeapTuple tuple;
+	Oid ownerId;
+
+	/* Superusers bypass all permission checking. */
+	if (superuser_arg(roleid))
+		return true;
+
+	tuple = SearchSysCache1(MODULEOID, ObjectIdGetDatum(mod_oid));
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR, (errcode(ERRCODE_UNDEFINED_MODULE),
+						errmsg("module with OID %u does not exist", mod_oid)));
+
+	ownerId = ((Form_pg_module) GETSTRUCT(tuple))->modowner;
+
+	ReleaseSysCache(tuple);
+
+	return has_privs_of_role(roleid, ownerId);
+}
+
 /*
  * Ownership check for a tablespace (specified by OID).
  */
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index ab9e42d7d1..0f3cbd487c 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -42,6 +42,7 @@
 #include "catalog/pg_init_privs.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_largeobject.h"
+#include "catalog/pg_module.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
@@ -163,6 +164,7 @@ static const Oid object_classes[] = {
 	AccessMethodProcedureRelationId,	/* OCLASS_AMPROC */
 	RewriteRelationId,			/* OCLASS_REWRITE */
 	TriggerRelationId,			/* OCLASS_TRIGGER */
+	ModuleRelationId,			/* OCLASS_MODULE */
 	NamespaceRelationId,		/* OCLASS_SCHEMA */
 	StatisticExtRelationId,		/* OCLASS_STATISTIC_EXT */
 	TSParserRelationId,			/* OCLASS_TSPARSER */
@@ -1488,6 +1490,7 @@ doDeletion(const ObjectAddress *object, int flags)
 		case OCLASS_AM:
 		case OCLASS_AMOP:
 		case OCLASS_AMPROC:
+		case OCLASS_MODULE:
 		case OCLASS_SCHEMA:
 		case OCLASS_TSPARSER:
 		case OCLASS_TSDICT:
@@ -2817,6 +2820,9 @@ getObjectClass(const ObjectAddress *object)
 		case TriggerRelationId:
 			return OCLASS_TRIGGER;
 
+		case ModuleRelationId:
+			return OCLASS_MODULE;
+
 		case NamespaceRelationId:
 			return OCLASS_SCHEMA;
 
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
index 2dfcdc5dad..e664d690ed 100644
--- a/src/backend/catalog/genbki.pl
+++ b/src/backend/catalog/genbki.pl
@@ -246,6 +246,13 @@ foreach my $row (@{ $catalog_data{pg_namespace} })
 	$namespaceoids{ $row->{nspname} } = $row->{oid};
 }
 
+# module OID lookup
+my %moduleoids;
+foreach my $row (@{ $catalog_data{pg_module} })
+{
+	$moduleoids{ $row->{modname} } = $row->{oid};
+}
+
 # opclass OID lookup
 my %opcoids;
 foreach my $row (@{ $catalog_data{pg_opclass} })
@@ -393,6 +400,7 @@ my %lookup_kind = (
 	pg_collation   => \%collationoids,
 	pg_language    => \%langoids,
 	pg_namespace   => \%namespaceoids,
+	pg_module      => \%moduleoids,
 	pg_opclass     => \%opcoids,
 	pg_operator    => \%operoids,
 	pg_opfamily    => \%opfoids,
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 5dbac9c437..145431d3ea 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -29,6 +29,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_module.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
@@ -208,7 +209,8 @@ static void NamespaceCallback(Datum arg, int cacheid, uint32 hashvalue);
 static bool MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,
 						   bool include_out_arguments, int pronargs,
 						   int **argnumbers);
-
+static Oid qualifiedNameGetCreationNamespaceHelper(List *names, Oid *moduleId,
+												   char **objname_p, bool check_module);
 
 /*
  * RangeVarGetRelidExtended
@@ -954,23 +956,34 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
 	FuncCandidateList resultList = NULL;
 	bool		any_special = false;
 	char	   *schemaname;
+	char       *modulename;
 	char	   *funcname;
-	Oid			namespaceId;
+	Oid	        namespaceId;
+	Oid         moduleId = InvalidOid;
 	CatCList   *catlist;
 	int			i;
+	bool        discardSchemanameIfNoMatch = false;
 
 	/* check for caller error */
 	Assert(nargs >= 0 || !(expand_variadic | expand_defaults));
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(names, &schemaname, &funcname);
+	DeconstructQualifiedNameWithModule(names, &schemaname, &modulename, &funcname);
 
 	if (schemaname)
 	{
-		/* use exact schema given */
-		namespaceId = LookupExplicitNamespace(schemaname, missing_ok);
-		if (!OidIsValid(namespaceId))
-			return NULL;
+		/* use exact schema given, missing_ok because if there is no schema,
+		   we will still check for a module by that name */
+
+		namespaceId = LookupExplicitNamespace(schemaname, true /* missing_ok */);
+
+		/* if no schema with that name is found, then treat it as a module name */
+		if (!OidIsValid(namespaceId) || namespaceId == 0) {
+			modulename = schemaname;
+			/* we need namespace search to find schema */
+			namespaceId = InvalidOid;
+			recomputeNamespacePath();
+		}
 	}
 	else
 	{
@@ -979,7 +992,8 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
 		recomputeNamespacePath();
 	}
 
-	/* Search syscache by name only */
+	/* Search syscache by name only. Check every result to see if it is in search path and
+	   if it matches with module name, if any */
 	catlist = SearchSysCacheList1(PROCNAMEARGSNSP, CStringGetDatum(funcname));
 
 	for (i = 0; i < catlist->n_members; i++)
@@ -994,33 +1008,70 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
 		bool		use_defaults;
 		Oid			va_elem_type;
 		int		   *argnumbers = NULL;
+		Oid        schemaOid;
 		FuncCandidateList newResult;
 
 		if (OidIsValid(namespaceId))
 		{
-			/* Consider only procs in specified namespace */
+			/* Consider only procs in specified schema */
 			if (procform->pronamespace != namespaceId)
 				continue;
 		}
 		else
 		{
 			/*
-			 * Consider only procs that are in the search path and are not in
-			 * the temp namespace.
+			 * The qualified name did not contain a schema name, so we will try to match with a schema
+			 * in the search path.
+			 * Consider only procs that are in the search path and are not in the temp namespace.
 			 */
 			ListCell   *nsp;
-
 			foreach(nsp, activeSearchPath)
 			{
 				if (procform->pronamespace == lfirst_oid(nsp) &&
 					procform->pronamespace != myTempNamespace)
+				{
+					schemaOid = lfirst_oid(nsp);
+					schemaname = get_namespace_name(schemaOid);
+					/* schema not originally specified, it was picked up from search path
+					   so may have to discard if this is not the correct proc*/
+					discardSchemanameIfNoMatch = true;
 					break;
+				}
 				pathpos++;
 			}
 			if (nsp == NULL)
 				continue;		/* proc is not in search path */
 		}
 
+		/* At this point a non-null schemaname has been set.
+		   If there is a module name to match against, then we will do that now */
+		if (modulename)
+		{
+			Oid nspoid = get_namespace_oid(schemaname, false);
+			moduleId = get_module_oid_from_name(nspoid, modulename, true);
+
+			/* Consider only if module oid is valid and the found proc is in specified module */
+			if (!OidIsValid(moduleId) || procform->promodule != moduleId)
+			{
+				if (discardSchemanameIfNoMatch)
+				{
+					/* if the schema name was picked up from the search path, discard it */
+					schemaname = NULL;
+					discardSchemanameIfNoMatch = false;
+				}
+				continue;
+			}
+		}
+		else
+		{
+			/* Searching for a proc that is not in any module,
+			   but the proc we found is in a module */
+			if (OidIsValid(procform->promodule) && (procform->promodule != 0))
+			{
+				continue;
+			}
+		}
+
 		/*
 		 * If we are asked to match to OUT arguments, then use the
 		 * proallargtypes array (which includes those); otherwise use
@@ -1494,7 +1545,6 @@ FunctionIsVisible(Oid funcid)
 
 		clist = FuncnameGetCandidates(list_make1(makeString(proname)),
 									  nargs, NIL, false, false, false, false);
-
 		for (; clist; clist = clist->next)
 		{
 			if (memcmp(clist->args, procform->proargtypes.values,
@@ -1533,7 +1583,7 @@ OpernameGetOprid(List *names, Oid oprleft, Oid oprright)
 	ListCell   *l;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(names, &schemaname, &opername);
+	DeconstructQualifiedNameNoModule(names, &schemaname, &opername);
 
 	if (schemaname)
 	{
@@ -1640,7 +1690,7 @@ OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok)
 	int			i;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(names, &schemaname, &opername);
+	DeconstructQualifiedNameNoModule(names, &schemaname, &opername);
 
 	if (schemaname)
 	{
@@ -2229,7 +2279,7 @@ get_statistics_object_oid(List *names, bool missing_ok)
 	ListCell   *l;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(names, &schemaname, &stats_name);
+	DeconstructQualifiedNameNoModule(names, &schemaname, &stats_name);
 
 	if (schemaname)
 	{
@@ -2351,7 +2401,7 @@ get_ts_parser_oid(List *names, bool missing_ok)
 	ListCell   *l;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(names, &schemaname, &parser_name);
+	DeconstructQualifiedNameNoModule(names, &schemaname, &parser_name);
 
 	if (schemaname)
 	{
@@ -2477,7 +2527,7 @@ get_ts_dict_oid(List *names, bool missing_ok)
 	ListCell   *l;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(names, &schemaname, &dict_name);
+	DeconstructQualifiedNameNoModule(names, &schemaname, &dict_name);
 
 	if (schemaname)
 	{
@@ -2604,7 +2654,7 @@ get_ts_template_oid(List *names, bool missing_ok)
 	ListCell   *l;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(names, &schemaname, &template_name);
+	DeconstructQualifiedNameNoModule(names, &schemaname, &template_name);
 
 	if (schemaname)
 	{
@@ -2730,7 +2780,7 @@ get_ts_config_oid(List *names, bool missing_ok)
 	ListCell   *l;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(names, &schemaname, &config_name);
+	DeconstructQualifiedNameNoModule(names, &schemaname, &config_name);
 
 	if (schemaname)
 	{
@@ -2842,7 +2892,6 @@ TSConfigIsVisible(Oid cfgid)
 	return visible;
 }
 
-
 /*
  * DeconstructQualifiedName
  *		Given a possibly-qualified name expressed as a list of String nodes,
@@ -2851,17 +2900,18 @@ TSConfigIsVisible(Oid cfgid)
  * *nspname_p is set to NULL if there is no explicit schema name.
  */
 void
-DeconstructQualifiedName(List *names,
+DeconstructQualifiedNameNoModule(List *names,
 						 char **nspname_p,
 						 char **objname_p)
 {
 	char	   *catalogname;
-	char	   *schemaname = NULL;
-	char	   *objname = NULL;
+	char	   *schemaname;
+	char	   *objname;
 
 	switch (list_length(names))
 	{
 		case 1:
+			schemaname = NULL;
 			objname = strVal(linitial(names));
 			break;
 		case 2:
@@ -2872,7 +2922,6 @@ DeconstructQualifiedName(List *names,
 			catalogname = strVal(linitial(names));
 			schemaname = strVal(lsecond(names));
 			objname = strVal(lthird(names));
-
 			/*
 			 * We check the catalog name and then ignore it.
 			 */
@@ -2894,6 +2943,111 @@ DeconstructQualifiedName(List *names,
 	*objname_p = objname;
 }
 
+/*
+ * DeconstructQualifiedNameWithModule
+ *		Given a possibly-qualified name expressed as a list of String nodes,
+ *		extract the schema name, module name and object name.
+ *
+ * *nspname_p is set to NULL if there is no explicit schema name.
+ * *modname_p is set to NULL if there is no explicit module name.
+ */
+void
+DeconstructQualifiedNameWithModule(List *names,
+						 char **nspname_p,
+						 char **modname_p,
+						 char **objname_p)
+{
+	char	   *catalogname;
+	char	   *nspname = NULL;
+	char	   *modulename = NULL;
+	char	   *objname = NULL;
+
+	switch (list_length(names))
+	{
+		case 1:
+			objname = strVal(linitial(names));
+			break;
+		case 2:
+			if ((strcmp(strVal(linitial(names)), "pg_temp") == 0) || SearchSysCacheExists1(NAMESPACENAME, PointerGetDatum(strVal(linitial(names)))))
+			{
+				nspname = strVal(linitial(names));
+				/* If the object to create is itself a module, then objname is the modulename */
+				objname = strVal(lsecond(names));
+			}
+			else
+			{
+				modulename = strVal(linitial(names));
+				/* If the object to create is itself a module, then objname is the modulename */
+				objname = strVal(lsecond(names));
+			}
+			break;
+		case 3:
+				/*
+				 * Since we don't allow cross-database references, check if the
+				 * first element is the current catalog and if is different assume
+				 * the first element is a schema
+				 */
+				if (strcmp(strVal(linitial(names)), get_database_name(MyDatabaseId)) != 0)
+				{
+					if ((strcmp(strVal(linitial(names)), "pg_temp") == 0) || SearchSysCacheExists1(NAMESPACENAME, PointerGetDatum(strVal(linitial(names)))))
+					{
+						nspname = strVal(linitial(names));
+						modulename = strVal(lsecond(names));
+					}
+					else
+					{
+						/*
+						 * The first element is neither the current catalog nor a schema
+						 */
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cross-database references are not implemented: %s",
+										NameListToString(names))));
+					}
+				}
+				else
+				{
+					catalogname = strVal(linitial(names));
+					if ((strcmp(strVal(lsecond(names)), "pg_temp") == 0) || SearchSysCacheExists1(NAMESPACENAME, PointerGetDatum(strVal(lsecond(names)))))
+					{
+						nspname = strVal(lsecond(names));
+					}
+					else
+					{
+						modulename = strVal(lsecond(names));
+					}
+				}
+
+				objname = strVal(lthird(names));
+			break;
+		case 4:
+				catalogname = strVal(linitial(names));
+				nspname = strVal(lsecond(names));
+				modulename = strVal(lthird(names));
+				objname = strVal(lfourth(names));
+
+				/*
+				 * We check the catalog name and then ignore it.
+				 */
+				if (strcmp(catalogname, get_database_name(MyDatabaseId)) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("cross-database references are not implemented: %s",
+									NameListToString(names))));
+			break;
+		default:
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("improper qualified name (too many dotted names): %s",
+							NameListToString(names))));
+			break;
+	}
+
+	/* nspname_p and/or modname_p can be set to NULL here if no nspname and/or modulename was parsed. */
+	*nspname_p = nspname;
+	*modname_p = modulename;
+	*objname_p = objname;
+}
 /*
  * LookupNamespaceNoError
  *		Look up a schema name.
@@ -2945,7 +3099,6 @@ LookupExplicitNamespace(const char *nspname, bool missing_ok)
 	{
 		if (OidIsValid(myTempNamespace))
 			return myTempNamespace;
-
 		/*
 		 * Since this is used only for looking up existing objects, there is
 		 * no point in trying to initialize the temp namespace here; and doing
@@ -2954,6 +3107,7 @@ LookupExplicitNamespace(const char *nspname, bool missing_ok)
 	}
 
 	namespaceId = get_namespace_oid(nspname, missing_ok);
+
 	if (missing_ok && !OidIsValid(namespaceId))
 		return InvalidOid;
 
@@ -2961,6 +3115,7 @@ LookupExplicitNamespace(const char *nspname, bool missing_ok)
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, OBJECT_SCHEMA,
 					   nspname);
+
 	/* Schema search hook for this lookup */
 	InvokeNamespaceSearchHook(namespaceId, true);
 
@@ -3024,42 +3179,70 @@ CheckSetNamespace(Oid oldNspOid, Oid nspOid)
 				 errmsg("cannot move objects into or out of TOAST schema")));
 }
 
-/*
- * QualifiedNameGetCreationNamespace
- *		Given a possibly-qualified name for an object (in List-of-Strings
- *		format), determine what namespace the object should be created in.
- *		Also extract and return the object name (last component of list).
- *
- * Note: this does not apply any permissions check.  Callers must check
- * for CREATE rights on the selected namespace when appropriate.
- *
- * Note: calling this may result in a CommandCounterIncrement operation,
- * if we have to create or clean out the temp namespace.
- */
+
 Oid
-QualifiedNameGetCreationNamespace(List *names, char **objname_p)
+get_module_oid_from_name(Oid nspoid, const char *modname, bool missing_ok)
 {
-	char	   *schemaname;
+	Oid			oid;
+
+	oid = GetSysCacheOid2(MODULENAME, Anum_pg_module_oid,
+						  CStringGetDatum(modname),  ObjectIdGetDatum(nspoid));
+
+	if (!OidIsValid(oid) && !missing_ok)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_MODULE),
+				 errmsg("module \"%s\" in namespace \"%d\" does not exist", modname, nspoid)));
+	return oid;
+}
+
+Oid
+qualifiedNameGetCreationNamespaceHelper(List *names, Oid *moduleId, char **objname_p, bool check_module)
+{
+	char	   *nspname = NULL;
 	Oid			namespaceId;
+	char       *modulename = NULL;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(names, &schemaname, objname_p);
+	if (check_module)
+		DeconstructQualifiedNameWithModule(names, &nspname, &modulename, objname_p);
+	else
+	{
+		DeconstructQualifiedNameNoModule(names, &nspname, objname_p);
+	}
 
-	if (schemaname)
+	if (nspname && modulename)
 	{
 		/* check for pg_temp alias */
-		if (strcmp(schemaname, "pg_temp") == 0)
+		if (strcmp(nspname, "pg_temp") == 0)
+		{
+			/* Initialize temp namespace */
+			AccessTempTableNamespace(false);
+			namespaceId = myTempNamespace;
+		}
+		else
+		{
+			namespaceId = get_namespace_oid(nspname, false);
+		}
+		*moduleId = get_module_oid_from_name(namespaceId, modulename, true);
+	}
+	else if (nspname)
+	{
+		*moduleId = InvalidOid;
+		/* check for pg_temp alias */
+		if (strcmp(nspname, "pg_temp") == 0)
 		{
 			/* Initialize temp namespace */
 			AccessTempTableNamespace(false);
 			return myTempNamespace;
 		}
 		/* use exact schema given */
-		namespaceId = get_namespace_oid(schemaname, false);
+		namespaceId = get_namespace_oid(nspname, false);
+
 		/* we do not check for USAGE rights here! */
 	}
 	else
 	{
+		*moduleId = InvalidOid;
 		/* use the default creation namespace */
 		recomputeNamespacePath();
 		if (activeTempCreationPending)
@@ -3072,12 +3255,50 @@ QualifiedNameGetCreationNamespace(List *names, char **objname_p)
 		if (!OidIsValid(namespaceId))
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_SCHEMA),
-					 errmsg("no schema has been selected to create in")));
+					 errmsg("no schema has been selected to create the module in")));
 	}
 
 	return namespaceId;
 }
 
+/*
+ * QualifiedNameGetCreationNamespace
+ *		Given a possibly-qualified name for an object (in List-of-Strings
+ *		format), determine what namespace the object should be created in.
+ *		Also extract and return the object name (last component of list).
+ *
+ * Note: this does not apply any permissions check.  Callers must check
+ * for CREATE rights on the selected namespace when appropriate.
+ *
+ * Note: calling this may result in a CommandCounterIncrement operation,
+ * if we have to create or clean out the temp namespace.
+ */
+Oid
+QualifiedNameGetCreationNamespace(List *names, char **objname_p)
+{
+	Oid moduleIdPlaceholder;
+	return qualifiedNameGetCreationNamespaceHelper(names, &moduleIdPlaceholder, objname_p, false);
+}
+
+/*
+ * QualifiedNameGetCreationNamespaceWithModule
+ *		Given a possibly-qualified name for an object (in List-of-Strings
+ *		format), that is allowed in a module, determine what namespace the
+ *      object should be created in.
+ *		Also extract and return the module oid and object name (last component of list).
+ *
+ * Note: this does not apply any permissions check.  Callers must check
+ * for CREATE rights on the selected namespace when appropriate.
+ *
+ * Note: calling this may result in a CommandCounterIncrement operation,
+ * if we have to create or clean out the temp namespace.
+ */
+Oid
+QualifiedNameWithModuleGetCreationNamespace(List *names, Oid *moduleId, char **objname_p)
+{
+	return qualifiedNameGetCreationNamespaceHelper(names, moduleId, objname_p, true);
+}
+
 /*
  * get_namespace_oid - given a namespace name, look up the OID
  *
@@ -3099,6 +3320,82 @@ get_namespace_oid(const char *nspname, bool missing_ok)
 	return oid;
 }
 
+/*
+ * get_module_oid - given a (possibly fully qualified) module name, look up the OID
+ *
+ * If missing_ok is false, throw an error if namespace or module name not found.  If
+ * true, just return InvalidOid.
+ */
+Oid
+get_module_oid(List *names, bool missing_ok)
+{
+	char	   *schemaname = NULL;
+	char	   *modulename = NULL;
+	char       *objname = NULL;
+	Oid			moduleId;
+	ListCell   *l;
+
+	/* Deconstruct the name list. Treat the module as an object */
+	DeconstructQualifiedNameWithModule(names, &schemaname, &modulename, &objname);
+
+	if (objname)
+		/* Deconstructing the name returned the object name which is the name of the module object */
+		modulename = objname;
+	else
+		/* Deconstruct did not find an object name. This should never happen.*/
+		ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("Trying to get module oid for a corrupt path name %s",  NameListToString(names))));
+
+	if (schemaname)
+	{
+		Oid nspoid = get_namespace_oid(schemaname, false);
+		moduleId = get_module_oid_from_name(nspoid, modulename, true);
+	}
+	else
+	{
+		/* Schema name was not specified in the module name
+		 * so search for it in search path
+		 */
+		recomputeNamespacePath();
+		foreach(l, activeSearchPath)
+		{
+			Oid namespaceId;
+			namespaceId = lfirst_oid(l);
+			schemaname = get_namespace_name(namespaceId);
+			schemaname = NULL;
+		}
+
+		moduleId = InvalidOid;
+		foreach(l, activeSearchPath)
+		{
+			Oid namespaceId;
+			namespaceId = lfirst_oid(l);
+			if (namespaceId == PG_CATALOG_NAMESPACE)
+				continue;		/* do not look in pg_catalog */
+			if (namespaceId == myTempNamespace)
+				continue;		/* do not look in temp namespace */
+
+			schemaname = get_namespace_name(namespaceId);
+			if (!schemaname)
+				continue;		/* no such namespace? */
+
+			moduleId = get_module_oid_from_name(namespaceId, modulename, true);
+
+			if (OidIsValid(moduleId))
+			{
+				break;          /* Found a module with that name */
+			}
+		}
+	}
+
+	if (!OidIsValid(moduleId) && !missing_ok)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("module \"%s\" does not exist",
+						NameListToString(names))));
+
+	return moduleId;
+}
+
 /*
  * makeRangeVarFromNameList
  *		Utility routine to convert a qualified-name list into RangeVar form.
@@ -3653,7 +3950,7 @@ get_collation_oid(List *name, bool missing_ok)
 	ListCell   *l;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(name, &schemaname, &collation_name);
+	DeconstructQualifiedNameNoModule(name, &schemaname, &collation_name);
 
 	if (schemaname)
 	{
@@ -3706,7 +4003,7 @@ get_conversion_oid(List *name, bool missing_ok)
 	ListCell   *l;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(name, &schemaname, &conversion_name);
+	DeconstructQualifiedNameNoModule(name, &schemaname, &conversion_name);
 
 	if (schemaname)
 	{
@@ -4044,8 +4341,7 @@ InitTempTableNamespace(void)
 		 * temp tables.  This works because the places that access the temp
 		 * namespace for my own backend skip permissions checks on it.
 		 */
-		namespaceId = NamespaceCreate(namespaceName, BOOTSTRAP_SUPERUSERID,
-									  true);
+		namespaceId = NamespaceCreate(namespaceName, BOOTSTRAP_SUPERUSERID, true);
 		/* Advance command counter to make namespace visible */
 		CommandCounterIncrement();
 	}
@@ -4069,8 +4365,7 @@ InitTempTableNamespace(void)
 	toastspaceId = get_namespace_oid(namespaceName, true);
 	if (!OidIsValid(toastspaceId))
 	{
-		toastspaceId = NamespaceCreate(namespaceName, BOOTSTRAP_SUPERUSERID,
-									   true);
+		toastspaceId = NamespaceCreate(namespaceName, BOOTSTRAP_SUPERUSERID, true);
 		/* Advance command counter to make namespace visible */
 		CommandCounterIncrement();
 	}
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index f30c742d48..d1f94e1fc6 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -41,6 +41,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_largeobject.h"
 #include "catalog/pg_largeobject_metadata.h"
+#include "catalog/pg_module.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
@@ -413,6 +414,20 @@ static const ObjectPropertyType ObjectProperty[] =
 		OBJECT_SCHEMA,
 		true
 	},
+	{
+		"module",
+		ModuleRelationId,
+		ModuleOidIndexId,
+		MODULEOID,
+		MODULENAME,
+		Anum_pg_module_oid,
+		Anum_pg_module_modname,
+		Anum_pg_module_nspoid,
+		Anum_pg_module_modowner,
+		Anum_pg_module_modacl,
+		OBJECT_MODULE,
+		true
+	},
 	{
 		"relation",
 		RelationRelationId,
@@ -766,6 +781,10 @@ static const struct object_type_map
 	{
 		"schema", OBJECT_SCHEMA
 	},
+	/* OCLASS_MODULE */
+	{
+		"module", OBJECT_MODULE
+	},
 	/* OCLASS_TSPARSER */
 	{
 		"text search parser", OBJECT_TSPARSER
@@ -943,7 +962,6 @@ get_object_address(ObjectType objtype, Node *object,
 
 	/* Some kind of lock must be taken. */
 	Assert(lockmode != NoLock);
-
 	for (;;)
 	{
 		/*
@@ -1139,6 +1157,11 @@ get_object_address(ObjectType objtype, Node *object,
 															 missing_ok);
 				address.objectSubId = 0;
 				break;
+			case OBJECT_MODULE:
+				address.classId = ModuleRelationId;
+				address.objectId = get_module_oid(castNode(List, object), missing_ok);
+				address.objectSubId = 0;
+				break;
 			default:
 				elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 				/* placate compiler, in case it thinks elog might return */
@@ -2322,6 +2345,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
 		case OBJECT_TABCONSTRAINT:
 		case OBJECT_OPCLASS:
 		case OBJECT_OPFAMILY:
+		case OBJECT_MODULE:
 			objnode = (Node *) name;
 			break;
 		case OBJECT_ACCESS_METHOD:
@@ -2491,6 +2515,11 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 				aclcheck_error(ACLCHECK_NOT_OWNER, objtype,
 							   strVal(object));
 			break;
+		case OBJECT_MODULE:
+			if (!pg_module_ownercheck(address.objectId, roleid))
+				aclcheck_error(ACLCHECK_NOT_OWNER, objtype,
+							   strVal(object));
+			break;
 		case OBJECT_COLLATION:
 			if (!pg_collation_ownercheck(address.objectId, roleid))
 				aclcheck_error(ACLCHECK_NOT_OWNER, objtype,
@@ -2968,6 +2997,34 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 
 	switch (getObjectClass(object))
 	{
+		case OCLASS_MODULE:
+			{
+				Relation	modRel;
+				HeapTuple	tup;
+				Form_pg_module modForm;
+
+				modRel = table_open(ModuleRelationId, AccessShareLock);
+
+				tup = get_catalog_object_by_oid(modRel, Anum_pg_module_oid,
+												object->objectId);
+
+				if (!HeapTupleIsValid(tup))
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for module %u",
+							 object->objectId);
+
+					table_close(modRel, AccessShareLock);
+					break;
+				}
+
+				modForm = (Form_pg_module) GETSTRUCT(tup);
+
+				appendStringInfo(&buffer, _("module %s"), (modForm->modname).data);
+
+				table_close(modRel, AccessShareLock);
+				break;
+			}
 		case OCLASS_CLASS:
 			if (object->objectSubId == 0)
 				getRelationDescription(&buffer, object->objectId, missing_ok);
@@ -4530,6 +4587,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 			appendStringInfoString(&buffer, "schema");
 			break;
 
+		case OCLASS_MODULE:
+			appendStringInfoString(&buffer, "module");
+			break;
+
 		case OCLASS_STATISTIC_EXT:
 			appendStringInfoString(&buffer, "statistics object");
 			break;
@@ -4850,6 +4911,35 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 
+		case OCLASS_MODULE:
+			{
+				Relation	modRel;
+				HeapTuple	tup;
+				Form_pg_module modForm;
+
+				modRel = table_open(ModuleRelationId, AccessShareLock);
+
+				tup = get_catalog_object_by_oid(modRel, Anum_pg_module_oid,
+												object->objectId);
+
+				if (!HeapTupleIsValid(tup))
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for module %u",
+							 object->objectId);
+
+					table_close(modRel, AccessShareLock);
+					break;
+				}
+
+				modForm = (Form_pg_module) GETSTRUCT(tup);
+
+				appendStringInfo(&buffer, "module %s", (modForm->modname).data);
+
+				table_close(modRel, AccessShareLock);
+				break;
+			}
+
 		case OCLASS_TYPE:
 			{
 				bits16		flags = FORMAT_TYPE_INVALID_AS_NULL | FORMAT_TYPE_FORCE_QUALIFY;
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 0d0daa69b3..b033286fb7 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -614,6 +614,7 @@ AggregateCreate(const char *aggName,
 
 	myself = ProcedureCreate(aggName,
 							 aggNamespace,
+							 InvalidOid, /* not in a module */
 							 replace,	/* maybe replacement */
 							 false, /* doesn't return a set */
 							 finaltype, /* returnType */
diff --git a/src/backend/catalog/pg_module.c b/src/backend/catalog/pg_module.c
new file mode 100644
index 0000000000..3312e334c7
--- /dev/null
+++ b/src/backend/catalog/pg_module.c
@@ -0,0 +1,118 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_module.c
+ *	  routines to support manipulation of the pg_module relation
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/pg_module.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/table.h"
+#include "catalog/catalog.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_module.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+
+/* ----------------
+ * ModuleCreate
+ *
+ * Create a module with the given name and owner OID.
+ *
+ * ---------------
+ */
+Oid
+ModuleCreate(const char *modName, const char *nspName, Oid ownerId)
+{
+	Relation	modDesc;
+	HeapTuple	tup;
+	Oid     	modOid;
+	Oid     	nspOid;
+	bool		nulls[Natts_pg_module];
+	Datum		values[Natts_pg_module];
+	NameData	nname;
+	TupleDesc	tupDesc;
+	ObjectAddress myself;
+	int     	i;
+	Acl        *modAcl;
+
+	/* sanity checks */
+	if (!modName)
+		elog(ERROR, "no module name supplied");
+
+	if (!nspName)
+		elog(ERROR, "no parent namespace name supplied");
+
+	nspOid = get_namespace_oid(nspName, false);
+
+	if (SearchSysCacheExists2(MODULENAME, PointerGetDatum(modName), ObjectIdGetDatum(nspOid)))
+		ereport(ERROR, (errcode(ERRCODE_DUPLICATE_MODULE),
+						errmsg("module \"%s\" already exists in schema \"%s\"", modName, nspName)));
+
+	modAcl = get_user_default_acl(OBJECT_MODULE, ownerId,
+								  InvalidOid);
+
+	modDesc = table_open(ModuleRelationId, RowExclusiveLock);
+	tupDesc = modDesc->rd_att;
+
+	/* initialize nulls and values */
+	for (i = 0; i < Natts_pg_module; i++)
+	{
+		nulls[i] = false;
+		values[i] = (Datum) NULL;
+	}
+
+	modOid = GetNewOidWithIndex(modDesc, ModuleOidIndexId,
+								Anum_pg_module_oid);
+	values[Anum_pg_module_oid - 1] = ObjectIdGetDatum(modOid);
+
+	namestrcpy(&nname, modName);
+	//namestrcpy(&schemaname, nspName);
+
+	values[Anum_pg_module_modname - 1] = NameGetDatum(&nname);
+	values[Anum_pg_module_nspoid - 1] = ObjectIdGetDatum(nspOid);
+	values[Anum_pg_module_modowner - 1] = ObjectIdGetDatum(ownerId);
+	if (modAcl != NULL)
+		values[Anum_pg_module_modacl - 1] = PointerGetDatum(modAcl);
+	else
+		nulls[Anum_pg_module_modacl - 1] = true;
+
+	tup = heap_form_tuple(tupDesc, values, nulls);
+
+	CatalogTupleInsert(modDesc, tup);
+	Assert(OidIsValid(modOid));
+
+	table_close(modDesc, RowExclusiveLock);
+
+	/* Record dependencies */
+	myself.classId = ModuleRelationId;
+	myself.objectId = modOid;
+	myself.objectSubId = 0;
+
+	/* dependency on owner */
+	recordDependencyOnOwner(ModuleRelationId, modOid, ownerId);
+
+	/* dependencies on roles mentioned in default ACL */
+	recordDependencyOnNewAcl(ModuleRelationId, modOid, 0, ownerId, modAcl);
+
+	/* dependency on extension */
+	recordDependencyOnCurrentExtension(&myself, false);
+
+	/* Post creation hook for new schema */
+	InvokeObjectPostCreateHook(ModuleRelationId, modOid, 0);
+
+	return modOid;
+}
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 12521c77c3..928d1f790a 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -22,6 +22,7 @@
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_language.h"
+#include "catalog/pg_module.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_transform.h"
@@ -70,6 +71,7 @@ static bool match_prosrc_to_literal(const char *prosrc, const char *literal,
 ObjectAddress
 ProcedureCreate(const char *procedureName,
 				Oid procNamespace,
+				Oid procModule,
 				bool replace,
 				bool returnsSet,
 				Oid returnType,
@@ -300,6 +302,7 @@ ProcedureCreate(const char *procedureName,
 	namestrcpy(&procname, procedureName);
 	values[Anum_pg_proc_proname - 1] = NameGetDatum(&procname);
 	values[Anum_pg_proc_pronamespace - 1] = ObjectIdGetDatum(procNamespace);
+	values[Anum_pg_proc_promodule - 1] = ObjectIdGetDatum(procModule);
 	values[Anum_pg_proc_proowner - 1] = ObjectIdGetDatum(proowner);
 	values[Anum_pg_proc_prolang - 1] = ObjectIdGetDatum(languageObjectId);
 	values[Anum_pg_proc_procost - 1] = Float4GetDatum(procost);
@@ -356,10 +359,11 @@ ProcedureCreate(const char *procedureName,
 	tupDesc = RelationGetDescr(rel);
 
 	/* Check for pre-existing definition */
-	oldtup = SearchSysCache3(PROCNAMEARGSNSP,
+	oldtup = SearchSysCache4(PROCNAMEARGSNSP,
 							 PointerGetDatum(procedureName),
 							 PointerGetDatum(parameterTypes),
-							 ObjectIdGetDatum(procNamespace));
+							 ObjectIdGetDatum(procNamespace),
+							 ObjectIdGetDatum(procModule));
 
 	if (HeapTupleIsValid(oldtup))
 	{
@@ -374,6 +378,7 @@ ProcedureCreate(const char *procedureName,
 					(errcode(ERRCODE_DUPLICATE_FUNCTION),
 					 errmsg("function \"%s\" already exists with same argument types",
 							procedureName)));
+
 		if (!pg_proc_ownercheck(oldproc->oid, proowner))
 			aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION,
 						   procedureName);
@@ -604,6 +609,10 @@ ProcedureCreate(const char *procedureName,
 	ObjectAddressSet(referenced, NamespaceRelationId, procNamespace);
 	add_exact_object_address(&referenced, addrs);
 
+	/* dependency on module */
+	ObjectAddressSet(referenced, ModuleRelationId, procModule);
+	add_exact_object_address(&referenced, addrs);
+
 	/* dependency on implementation language */
 	ObjectAddressSet(referenced, LanguageRelationId, languageObjectId);
 	add_exact_object_address(&referenced, addrs);
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 48f7348f91..a1fdb50be1 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -40,6 +40,7 @@ OBJS = \
 	indexcmds.o \
 	lockcmds.o \
 	matview.o \
+	modulecmds.o \
 	opclasscmds.o \
 	operatorcmds.o \
 	policy.o \
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 1f64c8aa51..bbec90c64a 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -47,6 +47,7 @@
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
 #include "commands/extension.h"
+#include "commands/modulecmds.h"
 #include "commands/policy.h"
 #include "commands/proclang.h"
 #include "commands/publicationcmds.h"
@@ -384,6 +385,7 @@ ExecRenameStmt(RenameStmt *stmt)
 		case OBJECT_OPCLASS:
 		case OBJECT_OPFAMILY:
 		case OBJECT_LANGUAGE:
+		case OBJECT_MODULE:
 		case OBJECT_PROCEDURE:
 		case OBJECT_ROUTINE:
 		case OBJECT_STATISTIC_EXT:
@@ -664,6 +666,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
 		case OCLASS_PUBLICATION_REL:
 		case OCLASS_SUBSCRIPTION:
 		case OCLASS_TRANSFORM:
+		case OCLASS_MODULE:
 			/* ignore object types that don't have schema-qualified names */
 			break;
 
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index c9b5732448..bb55b2d3b8 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -104,6 +104,7 @@ RemoveObjects(DropStmt *stmt)
 
 		/* Check permissions. */
 		namespaceId = get_object_namespace(&address);
+
 		if (!OidIsValid(namespaceId) ||
 			!pg_namespace_ownercheck(namespaceId, GetUserId()))
 			check_object_ownership(GetUserId(), stmt->removeType, address,
@@ -283,6 +284,10 @@ does_not_exist_skipping(ObjectType objtype, Node *object)
 				name = NameListToString(castNode(List, object));
 			}
 			break;
+		case OBJECT_MODULE:
+			msg = gettext_noop("module \"%s\" does not exist, skipping");
+			name = strVal(object);
+			break;
 		case OBJECT_SCHEMA:
 			msg = gettext_noop("schema \"%s\" does not exist, skipping");
 			name = strVal(object);
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 93c2099735..a8b03a1573 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -967,6 +967,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_LANGUAGE:
 		case OBJECT_LARGEOBJECT:
 		case OBJECT_MATVIEW:
+		case OBJECT_MODULE:
 		case OBJECT_OPCLASS:
 		case OBJECT_OPERATOR:
 		case OBJECT_OPFAMILY:
@@ -1039,6 +1040,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_REWRITE:
 		case OCLASS_TRIGGER:
 		case OCLASS_SCHEMA:
+		case OCLASS_MODULE:
 		case OCLASS_STATISTIC_EXT:
 		case OCLASS_TSPARSER:
 		case OCLASS_TSDICT:
@@ -2096,6 +2098,8 @@ stringify_grant_objtype(ObjectType objtype)
 			return "LANGUAGE";
 		case OBJECT_LARGEOBJECT:
 			return "LARGE OBJECT";
+		case OBJECT_MODULE:
+			return "MODULE";
 		case OBJECT_SCHEMA:
 			return "SCHEMA";
 		case OBJECT_PROCEDURE:
@@ -2179,6 +2183,8 @@ stringify_adefprivs_objtype(ObjectType objtype)
 			return "LANGUAGES";
 		case OBJECT_LARGEOBJECT:
 			return "LARGE OBJECTS";
+		case OBJECT_MODULE:
+			return "MODULES";
 		case OBJECT_SCHEMA:
 			return "SCHEMAS";
 		case OBJECT_PROCEDURE:
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 25b75375a8..db2e488c6e 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -149,8 +149,7 @@ compute_return_type(TypeName *returnType, Oid languageOid,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("type \"%s\" is not yet defined", typnam),
 				 errdetail("Creating a shell type definition.")));
-		namespaceId = QualifiedNameGetCreationNamespace(returnType->names,
-														&typname);
+		namespaceId = QualifiedNameGetCreationNamespace(returnType->names, &typname);
 		aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(),
 										  ACL_CREATE);
 		if (aclresult != ACLCHECK_OK)
@@ -1011,6 +1010,9 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
 	}
 }
 
+ObjectAddress
+CreateFunctionHelper(ParseState *pstate, CreateFunctionStmt *stmt, Oid namespaceId,
+					 Oid moduleId, char *funcname);
 
 /*
  * CreateFunction
@@ -1018,6 +1020,25 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
  */
 ObjectAddress
 CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
+{
+	char	   *funcname;
+	Oid			namespaceId;
+
+	/* Convert list of names to a name and namespace */
+	namespaceId = QualifiedNameGetCreationNamespace(stmt->funcname, &funcname);
+
+	return CreateFunctionHelper(pstate, stmt, namespaceId,
+								InvalidOid /* not in a  module */, funcname);
+}
+
+/*
+ * CreateFunctionHelper
+ *	 All the work of executing a CREATE FUNCTION (or CREATE PROCEDURE) utility statement.
+ *   This is common to functions/procedures in a module as well as those not in a module.
+ */
+ObjectAddress
+CreateFunctionHelper(ParseState *pstate, CreateFunctionStmt *stmt, Oid namespaceId,
+					 Oid moduleId, char *funcname)
 {
 	char	   *probin_str;
 	char	   *prosrc_str;
@@ -1028,8 +1049,6 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
 	Oid			languageOid;
 	Oid			languageValidator;
 	Node	   *transformDefElem = NULL;
-	char	   *funcname;
-	Oid			namespaceId;
 	AclResult	aclresult;
 	oidvector  *parameterTypes;
 	List	   *parameterTypes_list = NIL;
@@ -1056,10 +1075,6 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
 	List	   *as_clause;
 	char		parallel;
 
-	/* Convert list of names to a name and namespace */
-	namespaceId = QualifiedNameGetCreationNamespace(stmt->funcname,
-													&funcname);
-
 	/* Check we have creation rights in target namespace */
 	aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_CREATE);
 	if (aclresult != ACLCHECK_OK)
@@ -1268,6 +1283,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
 	 */
 	return ProcedureCreate(funcname,
 						   namespaceId,
+						   moduleId,
 						   stmt->replace,
 						   returnsSet,
 						   prorettype,
@@ -1295,6 +1311,27 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
 						   prorows);
 }
 
+/*
+ * CreateFunction
+ *	 Execute a CREATE FUNCTION (or CREATE PROCEDURE) utility statement in a module
+ */
+ObjectAddress
+CreateFunctionInModule(ParseState *pstate, CreateFunctionStmt *stmt, Oid namespaceId, Oid moduleId)
+{
+	char* funcname;
+	AclResult	aclresult;
+
+	/* Convert list of names to a name */
+	QualifiedNameGetCreationNamespace(stmt->funcname, &funcname);
+
+	/* Permission check: must have create permission on module */
+	aclresult = pg_module_aclcheck(moduleId, GetUserId(), ACL_CREATE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_MODULE, get_module_name(moduleId));
+
+	return CreateFunctionHelper(pstate, stmt, namespaceId, moduleId, funcname);
+}
+
 /*
  * Guts of function deletion.
  *
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 42aacc8f0a..a713c6b778 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2029,7 +2029,7 @@ ResolveOpClass(List *opclass, Oid attrType,
 	 */
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(opclass, &schemaname, &opcname);
+	DeconstructQualifiedNameNoModule(opclass, &schemaname, &opcname);
 
 	if (schemaname)
 	{
diff --git a/src/backend/commands/modulecmds.c b/src/backend/commands/modulecmds.c
new file mode 100644
index 0000000000..d775916200
--- /dev/null
+++ b/src/backend/commands/modulecmds.c
@@ -0,0 +1,513 @@
+/*-------------------------------------------------------------------------
+ *
+ * modulecmds.c
+ *	  module creation/manipulation commands
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/modulecmds.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/table.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_authid.h"
+#include "catalog/pg_module.h"
+#include "catalog/pg_namespace.h"
+#include "commands/dbcommands.h"
+#include "commands/defrem.h"
+#include "commands/event_trigger.h"
+#include "commands/modulecmds.h"
+#include "miscadmin.h"
+#include "parser/parse_utilcmd.h"
+#include "tcop/utility.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+static void AlterModuleOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId);
+
+/*
+ * CREATE MODULE
+ *
+ * Note: caller should pass in location information for the whole
+ * CREATE MODULE statement, which in turn we pass down as the location
+ * of the component commands.  This comports with our general plan of
+ * reporting location/len for the whole command even when executing
+ * a subquery.
+ */
+ObjectAddress
+CreateModuleCommand(CreateModuleStmt *stmt, const char *queryString,
+					int stmt_location, int stmt_len)
+{
+	char	   *modulename;
+	char	   *schemaname;
+	Oid		    moduleId;
+	Oid		    namespaceId;
+	OverrideSearchPath *overridePath;
+	List	   *parsetree_list;
+	ListCell   *parsetree_item;
+	Oid		    owner_uid;
+	Oid		    saved_uid;
+	int		    save_sec_context;
+	AclResult	aclresult;
+	ObjectAddress myself,
+				referenced;
+	ObjectAddresses *addrs;
+	Oid 	    dummyOid;
+
+	GetUserIdAndSecContext(&saved_uid, &save_sec_context);
+
+	if (stmt->authrole)
+		owner_uid = get_rolespec_oid(stmt->authrole, false);
+	else
+		owner_uid = saved_uid;
+
+	/* Convert list of names to a name and namespace */
+	namespaceId = QualifiedNameWithModuleGetCreationNamespace(stmt->modulename,
+															  &dummyOid, &modulename);
+	schemaname = get_namespace_name(namespaceId);
+
+	/* Check we have creation rights in target namespace */
+	aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_CREATE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_SCHEMA, schemaname);
+
+	/*
+	 * If if_not_exists was given and the module already exists, bail out.
+	 * (Note: we needn't check this when not if_not_exists, because
+	 * ModuleCreate will complain anyway.)  We could do this before making
+	 * the permissions checks, but since CREATE TABLE IF NOT EXISTS makes its
+	 * creation-permission check first, we do likewise.
+	 */
+
+	if (stmt->if_not_exists &&
+		SearchSysCacheExists2(MODULENAME, PointerGetDatum(modulename),
+							  ObjectIdGetDatum(namespaceId)))
+	{
+		ereport(NOTICE,
+				(errcode(ERRCODE_DUPLICATE_MODULE),
+				 errmsg("module \"%s\" already exists in schema \"%s\", skipping",
+						modulename, schemaname)));
+		return InvalidObjectAddress;
+	}
+
+	/*
+	 * If the requested authorization is different from the current user,
+	 * temporarily set the current user so that the object(s) will be created
+	 * with the correct ownership.
+	 *
+	 * (The setting will be restored at the end of this routine, or in case of
+	 * error, transaction abort will clean things up.)
+	 */
+
+	if (saved_uid != owner_uid)
+		SetUserIdAndSecContext(owner_uid,
+							   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+
+	/* Create the module's entry in catalog in pg_module */
+	moduleId = ModuleCreate(modulename, schemaname, owner_uid);
+
+	/* Advance cmd counter to make the namespace visible */
+	CommandCounterIncrement();
+
+	/*
+	 * Temporarily make the new namespace be the front of the search path, as
+	 * well as the default creation target namespace.  This will be undone at
+	 * the end of this routine, or upon error.
+	 */
+	overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+	overridePath->schemas = lcons_oid(moduleId, overridePath->schemas);
+	/* TODO should we clear overridePath->useTemp? */
+	PushOverrideSearchPath(overridePath);
+
+	/*
+	 * Report the new module to possibly interested event triggers.  Note we
+	 * must do this here and not in ProcessUtilitySlow because otherwise the
+	 * objects created below are reported before the module, which would be
+	 * wrong.
+	 */
+	ObjectAddressSet(myself, ModuleRelationId, moduleId);
+	EventTriggerCollectSimpleCommand(myself, InvalidObjectAddress, (Node *) stmt);
+
+	/*
+	 * Examine the list of commands embedded in the CREATE MODULE command, and
+	 * reorganize them into a sequentially executable order with no forward
+	 * references.  Note that the result is still a list of raw parsetrees ---
+	 * we cannot, in general, run parse analysis on one statement until we
+	 * have actually executed the prior ones.
+	 */
+	parsetree_list = transformCreateModuleStmt(stmt);
+
+	/*
+	 * Execute each command contained in the CREATE MODULE.  Since the grammar
+	 * allows only utility commands in CREATE MODULE, there is no need to pass
+	 * them through parse_analyze() or the rewriter; we can just hand them
+	 * straight to ProcessUtility.
+	 */
+	foreach(parsetree_item, parsetree_list)
+	{
+		Node	   *stmt = (Node *) lfirst(parsetree_item);
+		PlannedStmt *wrapper;
+
+		wrapper = makeNode(PlannedStmt);
+		wrapper->commandType = CMD_UTILITY;
+		wrapper->canSetTag = false;
+		wrapper->utilityStmt = stmt;
+		wrapper->stmt_location = stmt_location;
+		wrapper->stmt_len = stmt_len;
+
+		ProcessUtilityUsingModule(wrapper,
+								  queryString,
+								  false,
+								  PROCESS_UTILITY_SUBCOMMAND,
+								  NULL,
+								  NULL,
+								  None_Receiver,
+								  NULL,
+								  namespaceId,
+								  moduleId);
+
+		CommandCounterIncrement();
+	}
+
+	/* Reset search path to normal state */
+	PopOverrideSearchPath();
+
+	/* Reset current user and security context */
+	SetUserIdAndSecContext(saved_uid, save_sec_context);
+
+	addrs = new_object_addresses();
+
+	/* dependency on namespace */
+	ObjectAddressSet(referenced, ModuleRelationId, namespaceId);
+	add_exact_object_address(&referenced, addrs);
+
+	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
+	free_object_addresses(addrs);
+
+	recordDependencyOnOwner(ModuleRelationId, moduleId, owner_uid);
+
+	return myself;
+}
+
+/*
+ * Alter module ... create/replace function
+ */
+ObjectAddress
+AlterModuleCreateReplaceFunction(AlterModuleCreateReplaceFuncStmt *stmt, const char *queryString,
+								 int stmt_location, int stmt_len)
+{
+	Oid namespaceOid;
+	Oid moduleOid;
+	Oid dummyOid;
+	char *moduleName;
+	ObjectAddress address;
+	Relation rel;
+	HeapTuple tup;
+	CreateFunctionStmt *fstmt;
+	Node *element = stmt->createreplacefunction;
+	Form_pg_module modForm;
+	AclResult	aclresult;
+
+	rel = table_open(ModuleRelationId, RowExclusiveLock);
+
+	/* Convert qualified names List to a name and namespace */
+	namespaceOid = QualifiedNameWithModuleGetCreationNamespace
+		(stmt->modulename, &dummyOid, &moduleName);
+
+	/* Permission check: must have create permission on namespace */
+	aclresult = pg_namespace_aclcheck(namespaceOid, GetUserId(), ACL_CREATE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_SCHEMA, get_namespace_name(namespaceOid));
+
+	/* Permission check: must have create permission on module */
+	moduleOid = get_module_oid_from_name(namespaceOid, NameListToString(stmt->modulename), true);
+	aclresult = pg_module_aclcheck(moduleOid, GetUserId(), ACL_CREATE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_MODULE, NameListToString(stmt->modulename));
+
+	tup = SearchSysCacheCopy2(MODULENAME, PointerGetDatum(moduleName),
+							  ObjectIdGetDatum(namespaceOid));
+	if (!HeapTupleIsValid(tup)) /* should not happen */
+		elog(ERROR, "cache lookup failed for module %s", NameListToString(stmt->modulename));
+
+	modForm = (Form_pg_module) GETSTRUCT(tup);
+	moduleOid = modForm->oid;
+
+	ObjectAddressSet(address, ModuleRelationId, moduleOid);
+
+	switch (nodeTag(element))
+	{
+		case T_CreateFunctionStmt:
+		{
+			fstmt = (CreateFunctionStmt *) element;
+
+			if (list_length(fstmt->funcname) > 1)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_MODULE_DEFINITION),
+						 errmsg("CREATE/REPLACE FUNCTION (%s) specifies a "
+								"namespace inside of ALTER MODULE (%s)",
+								NameListToString(fstmt->funcname),
+								NameListToString(stmt->modulename))));
+
+			break;
+		}
+
+		default:
+			elog(ERROR, "unrecognized node type: %d",
+				 (int) nodeTag(element));
+	}
+
+	PlannedStmt *wrapper;
+
+	wrapper = makeNode(PlannedStmt);
+	wrapper->commandType = CMD_UTILITY;
+	wrapper->canSetTag = false;
+	wrapper->utilityStmt = element;
+	wrapper->stmt_location = stmt_location;
+	wrapper->stmt_len = stmt_len;
+
+	ProcessUtilityUsingModule(wrapper,
+							  queryString,
+							  false,
+							  PROCESS_UTILITY_SUBCOMMAND,
+							  NULL,
+							  NULL,
+							  None_Receiver,
+							  NULL,
+							  namespaceOid,
+							  moduleOid);
+
+	CommandCounterIncrement();
+
+	table_close(rel, NoLock);
+	heap_freetuple(tup);
+
+	return address;
+}
+
+/*
+ * Implements the ALTER MODULE utility command (except for the
+ * RENAME and OWNER clauses, which are handled as part of the generic
+ * ALTER framework).
+ */
+ObjectAddress
+AlterModuleAlterFunction(ParseState *pstate, AlterModuleAlterFuncStmt *stmt)
+{
+	char	   *moduleName;
+	Oid			namespaceId;
+	Oid			moduleOid;
+	Oid			dummyOid;
+	Relation	rel;
+	HeapTuple	tup;
+	ObjectAddress address;
+	AlterFunctionStmt *alterfuncstmt;
+	Form_pg_module modForm;
+
+	alterfuncstmt = stmt->alterfuncstmt;
+
+	// TODO Do we need a lock? To prevent concurrent updates creating an inconsistent state for the module
+	rel = table_open(ModuleRelationId, RowExclusiveLock);
+
+	/* Convert qualified names List to a name and namespace */
+	namespaceId = QualifiedNameWithModuleGetCreationNamespace(stmt->modulename,
+															  &dummyOid, &moduleName);
+	tup = SearchSysCacheCopy2(MODULENAME, PointerGetDatum(moduleName),
+							  ObjectIdGetDatum(namespaceId));
+	if (!HeapTupleIsValid(tup)) /* should not happen */
+		elog(ERROR, "cache lookup failed for module %s", NameListToString(stmt->modulename));
+
+	modForm = (Form_pg_module) GETSTRUCT(tup);
+	moduleOid = modForm->oid;
+
+	/* Permission check: must have create permission on module */
+	if (!pg_module_ownercheck(moduleOid, GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, alterfuncstmt->objtype,
+					   NameListToString(alterfuncstmt->func->objname));
+
+	AlterFunction(pstate, alterfuncstmt);
+
+	CommandCounterIncrement();
+
+	table_close(rel, NoLock);
+	heap_freetuple(tup);
+
+	return address;
+}
+
+/*
+ * Rename module
+ */
+ObjectAddress
+AlterModuleRename(AlterModuleRenameStmt *stmt)
+{
+	Oid			namespaceId;
+	Oid			modOid;
+	Oid			dummyOid;
+	List	   *oldName;
+	char	   *newName;
+	char	   *moduleName;
+	HeapTuple	tup;
+	Relation	rel;
+	ObjectAddress address;
+	Form_pg_module modForm;
+
+	oldName = stmt->modulename;
+	newName = stmt->newname;
+
+	/* Convert qualified names List to a name and namespace */
+	namespaceId = QualifiedNameWithModuleGetCreationNamespace(oldName, &dummyOid, &moduleName);
+
+	rel = table_open(ModuleRelationId, RowExclusiveLock);
+
+	tup = SearchSysCacheCopy2(MODULENAME, PointerGetDatum(newName),
+							  PointerGetDatum(namespaceId));
+	if (HeapTupleIsValid(tup)) /* can't rename to a module name that already exists */
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_MODULE),
+				 errmsg("module \"%s\" already exists in schema \"%d\"", newName, namespaceId)));
+
+	tup = SearchSysCacheCopy2(MODULENAME, PointerGetDatum(moduleName),
+							  ObjectIdGetDatum(namespaceId));
+	if (!HeapTupleIsValid(tup)) /* should not happen */
+		elog(ERROR, "module \"%s\" does not exist", NameListToString(oldName));
+
+	modForm = (Form_pg_module) GETSTRUCT(tup);
+	modOid = modForm->oid;
+
+	/* must be owner */
+	if (!superuser() && !pg_module_ownercheck(modOid, GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_MODULE, moduleName);
+
+	/* rename */
+	namestrcpy(&modForm->modname, newName);
+
+	CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+	InvokeObjectPostAlterHook(ModuleRelationId, modForm->oid, 0);
+
+	heap_freetuple(tup);
+
+	table_close(rel, RowExclusiveLock);
+
+	ObjectAddressSet(address, ModuleRelationId, modOid);
+
+	return address;
+}
+
+/*
+ * Change module owner
+ */
+ObjectAddress
+AlterModuleOwner(AlterModuleOwnerStmt *stmt)
+{
+	Oid			namespaceId;
+	Oid			modOid;
+	Oid			dummyOid;
+	List	   *name;
+	Oid			newOwnerId;
+	char	   *moduleName;
+
+	HeapTuple	tup;
+	Relation	rel;
+	ObjectAddress address;
+	Form_pg_module modForm;
+
+	name = stmt->modulename;
+	newOwnerId = get_rolespec_oid(stmt->newowner, false);
+
+	/* Convert qualified names List to a name and namespace */
+	namespaceId = QualifiedNameWithModuleGetCreationNamespace(name, &dummyOid, &moduleName);
+
+	rel = table_open(ModuleRelationId, RowExclusiveLock);
+	tup = SearchSysCacheCopy2(MODULENAME, PointerGetDatum(name), ObjectIdGetDatum(namespaceId));
+	if (!HeapTupleIsValid(tup)) /* should not happen */
+		elog(ERROR, "module %s does not exist", NameListToString(name));
+
+	modForm = (Form_pg_module) GETSTRUCT(tup);
+	modOid = modForm->oid;
+
+	AlterModuleOwner_internal(tup, rel, newOwnerId);
+
+	ObjectAddressSet(address, ModuleRelationId, modOid);
+
+	ReleaseSysCache(tup);
+
+	table_close(rel, RowExclusiveLock);
+
+	return address;
+}
+
+static void
+AlterModuleOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId)
+{
+	Form_pg_module modForm;
+	modForm = (Form_pg_module) GETSTRUCT(tup);
+
+	/*
+	 * If the new owner is the same as the existing owner, consider the
+	 * command to have succeeded.  This is for dump restoration purposes.
+	 */
+	if (modForm->modowner != newOwnerId)
+	{
+		Datum		repl_val[Natts_pg_module];
+		bool		repl_null[Natts_pg_module];
+		bool		repl_repl[Natts_pg_module];
+		Acl		   *newAcl;
+		Datum		aclDatum;
+		bool		isNull;
+		HeapTuple	newtuple;
+
+		/* Superusers can always do it. Otherwise, must be owner of the existing object */
+		if (!superuser() && !pg_module_ownercheck(modForm->oid, GetUserId()))
+			aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_MODULE,
+						   NameStr(modForm->modname));
+
+		/* Must be able to become new owner */
+		check_is_member_of_role(GetUserId(), newOwnerId);
+
+		memset(repl_null, false, sizeof(repl_null));
+		memset(repl_repl, false, sizeof(repl_repl));
+
+		repl_repl[Anum_pg_module_modowner - 1] = true;
+		repl_val[Anum_pg_module_modowner - 1] = ObjectIdGetDatum(newOwnerId);
+
+		/*
+		 * Determine the modified ACL for the new owner.  This is only
+		 * necessary when the ACL is non-null.
+		 */
+		aclDatum = SysCacheGetAttr(MODULENAME, tup, Anum_pg_module_modacl, &isNull);
+		if (!isNull)
+		{
+			newAcl = aclnewowner(DatumGetAclP(aclDatum),
+								 modForm->modowner, newOwnerId);
+			repl_repl[Anum_pg_module_modacl - 1] = true;
+			repl_val[Anum_pg_module_modacl - 1] = PointerGetDatum(newAcl);
+		}
+
+		newtuple = heap_modify_tuple(tup, RelationGetDescr(rel), repl_val, repl_null, repl_repl);
+
+		heap_freetuple(newtuple);
+
+		/* Update owner dependency reference */
+		changeDependencyOnOwner(ModuleRelationId, modForm->oid,
+								newOwnerId);
+	}
+
+	InvokeObjectPostAlterHook(ModuleRelationId,
+							  modForm->oid, 0);
+}
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index 66987966b4..1374b807b6 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -85,7 +85,7 @@ OpFamilyCacheLookup(Oid amID, List *opfamilyname, bool missing_ok)
 	HeapTuple	htup;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(opfamilyname, &schemaname, &opfname);
+	DeconstructQualifiedNameNoModule(opfamilyname, &schemaname, &opfname);
 
 	if (schemaname)
 	{
@@ -166,7 +166,7 @@ OpClassCacheLookup(Oid amID, List *opclassname, bool missing_ok)
 	HeapTuple	htup;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(opclassname, &schemaname, &opcname);
+	DeconstructQualifiedNameNoModule(opclassname, &schemaname, &opcname);
 
 	if (schemaname)
 	{
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 7a62d547e2..9d0ceafd18 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -48,6 +48,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
 		case OBJECT_LANGUAGE:
 		case OBJECT_LARGEOBJECT:
 		case OBJECT_MATVIEW:
+		case OBJECT_MODULE:
 		case OBJECT_PROCEDURE:
 		case OBJECT_PUBLICATION:
 		case OBJECT_ROLE:
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 54a190722d..f4c53ae234 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -639,7 +639,7 @@ AlterStatistics(AlterStatsStmt *stmt)
 
 		Assert(stmt->missing_ok);
 
-		DeconstructQualifiedName(stmt->defnames, &schemaname, &statname);
+		DeconstructQualifiedNameNoModule(stmt->defnames, &schemaname, &statname);
 
 		if (schemaname)
 			ereport(NOTICE,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3e83f375b5..171ae61a62 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12648,6 +12648,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 			case OCLASS_PUBLICATION_REL:
 			case OCLASS_SUBSCRIPTION:
 			case OCLASS_TRANSFORM:
+			case OCLASS_MODULE:
 
 				/*
 				 * We don't expect any of these sorts of objects to depend on
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 053d154753..bbde7f663c 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1751,6 +1751,7 @@ makeRangeConstructors(const char *name, Oid namespace,
 
 		myself = ProcedureCreate(name,	/* name: same as range type */
 								 namespace, /* namespace */
+								 InvalidOid, /* no module oid */
 								 false, /* replace */
 								 false, /* returns set */
 								 rangeOid,	/* return type */
@@ -1816,6 +1817,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
 	argtypes = buildoidvector(NULL, 0);
 	myself = ProcedureCreate(name,	/* name: same as multirange type */
 							 namespace,
+							 InvalidOid, /* no module oid */
 							 false, /* replace */
 							 false, /* returns set */
 							 multirangeOid, /* return type */
@@ -1860,6 +1862,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
 	argtypes = buildoidvector(&rangeOid, 1);
 	myself = ProcedureCreate(name,	/* name: same as multirange type */
 							 namespace,
+							 InvalidOid, /* no module oid */
 							 false, /* replace */
 							 false, /* returns set */
 							 multirangeOid, /* return type */
@@ -1901,6 +1904,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
 									 1, true, 'c');
 	myself = ProcedureCreate(name,	/* name: same as multirange type */
 							 namespace,
+							 InvalidOid, /* no module oid */
 							 false, /* replace */
 							 false, /* returns set */
 							 multirangeOid, /* return type */
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index f843f861ef..4807cdc0aa 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -578,21 +578,17 @@ list_concat_copy(const List *list1, const List *list2)
 {
 	List	   *result;
 	int			new_len;
-
 	if (list1 == NIL)
 		return list_copy(list2);
 	if (list2 == NIL)
 		return list_copy(list1);
-
 	Assert(list1->type == list2->type);
-
 	new_len = list1->length + list2->length;
 	result = new_list(list1->type, new_len);
 	memcpy(result->elements, list1->elements,
 		   list1->length * sizeof(ListCell));
 	memcpy(result->elements + list1->length, list2->elements,
 		   list2->length * sizeof(ListCell));
-
 	check_list_invariants(result);
 	return result;
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index b5966712ce..909341e211 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -111,7 +111,7 @@
 #define YYMALLOC palloc
 #define YYFREE   pfree
 
-/* Private struct for the result of privilege_target production */
+/* Private struct for the result of privilege_target and module_privilege_target production */
 typedef struct PrivTarget
 {
 	GrantTargetType targtype;
@@ -282,10 +282,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct KeyAction	*keyaction;
 }
 
-%type <node>	stmt toplevel_stmt schema_stmt routine_body_stmt
+%type <node>	stmt toplevel_stmt schema_stmt routine_body_stmt module_stmt
 		AlterEventTrigStmt AlterCollationStmt
 		AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
-		AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
+		AlterFdwStmt AlterForeignServerStmt AlterGroupStmt AlterModuleAlterFuncStmt
+		AlterModuleCreateReplaceFuncStmt
 		AlterObjectDependsStmt AlterObjectSchemaStmt AlterOwnerStmt
 		AlterOperatorStmt AlterTypeStmt AlterSeqStmt AlterSystemStmt AlterTableStmt
 		AlterTblSpcStmt AlterExtensionStmt AlterExtensionContentsStmt
@@ -321,6 +322,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		CreateMatViewStmt RefreshMatViewStmt CreateAmStmt
 		CreatePublicationStmt AlterPublicationStmt
 		CreateSubscriptionStmt AlterSubscriptionStmt DropSubscriptionStmt
+		CreateModuleStmt
 
 %type <node>	select_no_parens select_with_parens select_clause
 				simple_select values_clause
@@ -364,7 +366,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_in_database
 
 %type <str>		OptSchemaName
-%type <list>	OptSchemaEltList
+%type <list>	OptSchemaEltList OptModuleEltList
 
 %type <chr>		am_type
 
@@ -388,7 +390,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
 				opt_class opt_inline_handler opt_validator validator_clause
-				opt_collate
+				opt_collate module_name
 
 %type <range>	qualified_name insert_target OptConstrFromTable
 
@@ -404,7 +406,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <list>	grantee_list
 %type <accesspriv> privilege
 %type <list>	privileges privilege_list
-%type <privtarget> privilege_target
+ //%type <accesspriv> module_privilege
+ //%type <list>	module_privileges
+ //%type <list>    module_privilege_list
+%type <privtarget> privilege_target module_privilege_target privilege_target_proc
 %type <objwithargs> function_with_argtypes aggregate_with_argtypes operator_with_argtypes
 %type <list>	function_with_argtypes_list aggregate_with_argtypes_list operator_with_argtypes_list
 %type <ival>	defacl_privilege_target
@@ -704,7 +709,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
 	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
 
-	MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
+	MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MODULE
+	MONTH_P MOVE
 
 	NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NFC NFD NFKC NFKD NO NONE
 	NORMALIZE NORMALIZED
@@ -937,6 +943,8 @@ stmt:
 			| AlterForeignServerStmt
 			| AlterFunctionStmt
 			| AlterGroupStmt
+			| AlterModuleAlterFuncStmt
+			| AlterModuleCreateReplaceFuncStmt
 			| AlterObjectDependsStmt
 			| AlterObjectSchemaStmt
 			| AlterOwnerStmt
@@ -977,6 +985,7 @@ stmt:
 			| CreateFunctionStmt
 			| CreateGroupStmt
 			| CreateMatViewStmt
+			| CreateModuleStmt
 			| CreateOpClassStmt
 			| CreateOpFamilyStmt
 			| CreatePublicationStmt
@@ -1489,6 +1498,143 @@ schema_stmt:
 			| ViewStmt
 		;
 
+/*****************************************************************************
+ *
+ * Manipulate a module
+ *
+ *****************************************************************************/
+
+CreateModuleStmt:
+			CREATE MODULE module_name OptModuleEltList
+				{
+					CreateModuleStmt *n = makeNode(CreateModuleStmt);
+					n->modulename = $3;
+					n->authrole = NULL;
+					n->moduleElts = $4;
+					n->if_not_exists = false;
+					$$ = (Node *)n;
+				}
+			| CREATE MODULE module_name OWNER RoleSpec OptModuleEltList
+				{
+					CreateModuleStmt *n = makeNode(CreateModuleStmt);
+					n->modulename = $3;
+					n->authrole = $5;
+					n->moduleElts = $6;
+					n->if_not_exists = false;
+					$$ = (Node *)n;
+				}
+			| CREATE MODULE IF_P NOT EXISTS module_name OptModuleEltList
+				{
+					CreateModuleStmt *n = makeNode(CreateModuleStmt);
+					n->modulename = $6;
+					n->authrole = NULL;
+					if ($7 != NIL)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("CREATE MODULE IF NOT EXISTS cannot include module elements"),
+								 parser_errposition(@7)));
+					n->moduleElts = $7;
+					n->if_not_exists = true;
+					$$ = (Node *)n;
+				}
+			| CREATE MODULE IF_P NOT EXISTS module_name OWNER RoleSpec OptModuleEltList
+				{
+					CreateModuleStmt *n = makeNode(CreateModuleStmt);
+					n->modulename = $6;
+					n->authrole = $8;
+					if ($9 != NIL)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("CREATE MODULE IF NOT EXISTS cannot include module elements"),
+								 parser_errposition(@9)));
+					n->moduleElts = $9;
+					n->if_not_exists = true;
+					$$ = (Node *)n;
+				}
+		;
+
+module_name:
+			name						{ $$ = list_make1(makeString($1)); }
+			| name attrs				{ $$ = lcons(makeString($1), $2); }
+		;
+
+OptModuleEltList:
+			OptModuleEltList module_stmt
+				{
+					if (@$ < 0)			/* see comments for YYLLOC_DEFAULT */
+						@$ = @2;
+					$$ = lappend($1, $2);
+				}
+			| /* EMPTY */
+				{ $$ = NIL; }
+		;
+
+/*
+ *	module_stmt are the ones that can show up inside a CREATE MODULE statement.
+ */
+module_stmt:
+			CreateFunctionStmt
+			;
+
+/*****************************************************************************
+ *
+ * ALTER MODULE name CREATE/REPLACE FUNCTION/PROCEDURE/ROUTINE
+ *
+ *****************************************************************************/
+AlterModuleCreateReplaceFuncStmt:
+			ALTER MODULE module_name module_stmt
+				{
+					AlterModuleCreateReplaceFuncStmt *m;
+					//CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
+					//n = $4;
+					//n->objtype = OBJECT_FUNCTION;
+					m = makeNode(AlterModuleCreateReplaceFuncStmt);
+					m->modulename = $3;
+					m->createreplacefunction = $4;
+					//n->func->objname = list_concat_copy(m->modulename, n->func->objname);
+					$$ = (Node *) m;
+				}
+		;
+
+
+/*****************************************************************************
+ *
+ * ALTER MODULE name ALTER FUNCTION/PROCEDURE/ROUTINE
+ *
+ *****************************************************************************/
+AlterModuleAlterFuncStmt:
+			ALTER MODULE module_name ALTER FUNCTION function_with_argtypes alterfunc_opt_list opt_restrict
+				{
+					AlterModuleAlterFuncStmt *m;
+					AlterFunctionStmt *n = makeNode(AlterFunctionStmt);
+					n->objtype = OBJECT_FUNCTION;
+					n->func = $6;
+					n->actions = $7;
+					m = makeNode(AlterModuleAlterFuncStmt);
+					m->objtype = OBJECT_MODULE;
+					m->modulename = $3;
+					/* add m->modulename to front of  n->func->objname to make the qualified name for the function */
+					n->func->objname = list_concat_copy(m->modulename, n->func->objname);
+					m->alterfuncstmt = n;
+					$$ = (Node *) m;
+				}
+			| ALTER MODULE module_name ALTER PROCEDURE function_with_argtypes alterfunc_opt_list opt_restrict
+				{
+					AlterModuleAlterFuncStmt *m;
+					AlterFunctionStmt *n = makeNode(AlterFunctionStmt);
+					n->objtype = OBJECT_PROCEDURE;
+					n->func = $6;
+					n->actions = $7;
+					m = makeNode(AlterModuleAlterFuncStmt);
+					m->objtype = OBJECT_MODULE;
+					m->modulename = $3;
+					/* add m->modulename to front of n->func->objname to make the qualified name for the function */
+					n->func->objname = list_concat(m->modulename, n->func->objname);
+					m->alterfuncstmt = n;
+					$$ = (Node *) m;
+				}
+		;
+
 
 /*****************************************************************************
  *
@@ -6413,6 +6559,26 @@ DropStmt:	DROP object_type_any_name IF_P EXISTS any_name_list opt_drop_behavior
 					n->concurrent = true;
 					$$ = (Node *)n;
 				}
+			| DROP MODULE any_name_list opt_drop_behavior
+				{
+					DropStmt *n = makeNode(DropStmt);
+					n->removeType = OBJECT_MODULE;
+					n->objects = $3;
+					n->behavior = $4;
+					n->missing_ok = false;
+					n->concurrent = false;
+					$$ = (Node *)n;
+				}
+			| DROP MODULE IF_P EXISTS any_name_list opt_drop_behavior
+				{
+					DropStmt *n = makeNode(DropStmt);
+					n->removeType = OBJECT_MODULE;
+					n->objects = $5;
+					n->behavior = $6;
+					n->missing_ok = true;
+					n->concurrent = false;
+					$$ = (Node *)n;
+				}
 		;
 
 /* object types taking any_name/any_name_list */
@@ -6958,7 +7124,7 @@ opt_from_in:	from_in
  *
  *****************************************************************************/
 
-GrantStmt:	GRANT privileges ON privilege_target TO grantee_list
+GrantStmt:  GRANT privileges ON privilege_target TO grantee_list
 			opt_grant_grant_option opt_granted_by
 				{
 					GrantStmt *n = makeNode(GrantStmt);
@@ -6972,6 +7138,121 @@ GrantStmt:	GRANT privileges ON privilege_target TO grantee_list
 					n->grantor = $8;
 					$$ = (Node*)n;
 				}
+			| GRANT ON MODULE module_name REFERENCES ON module_privilege_target TO grantee_list
+			opt_grant_grant_option opt_granted_by
+				{
+					GrantStmt *n;
+					List *moduleName;
+					ListCell *cell;
+					ObjectWithArgs *func;
+					n = makeNode(GrantStmt);
+					moduleName = $4;
+					n->is_grant = true;
+
+					AccessPriv *p = makeNode(AccessPriv);
+					p->priv_name = "execute";
+					n->privileges = list_make1(p);
+
+					n->targtype = ($7)->targtype;
+					n->objtype = ($7)->objtype;
+					n->objects =  ($7)->objs;
+					foreach(cell, n->objects)
+					{
+						func = (ObjectWithArgs *) lfirst(cell);
+
+						/* func is a pointer, so the following modifies the list element itself and not a copy of it
+						 * add moduleName to the front of func->objname to make the qualified name for the function */
+						func->objname = list_concat_copy(moduleName, func->objname);
+					}
+					n->grantees = $9;
+					n->grant_option = $10;
+					n->grantor = $11;
+					$$ = (Node*)n;
+				}
+			| GRANT ON MODULE module_name CREATE TO grantee_list opt_grant_grant_option opt_granted_by
+				{
+					GrantStmt *n;
+					List *moduleName;
+
+					n = makeNode(GrantStmt);
+					moduleName = $4;
+					n->is_grant = true;
+
+					AccessPriv *p = makeNode(AccessPriv);
+					p->priv_name = "create";
+					n->privileges = list_make1(p);
+
+					//n->targtype = ACL_TARGET_ALL_IN_MODULE;
+					n->objtype = OBJECT_MODULE;
+					n->objects = list_make1(moduleName);
+					n->grantees = $7;
+					n->grant_option = $8;
+					n->grantor = $9;
+					$$ = (Node*)n;
+				}
+			| GRANT ON MODULE module_name REFERENCES ON ALL FUNCTIONS TO grantee_list opt_grant_grant_option opt_granted_by
+				{
+					GrantStmt *n;
+					List *moduleName;
+
+					n = makeNode(GrantStmt);
+					moduleName = $4;
+					n->is_grant = true;
+
+					AccessPriv *p = makeNode(AccessPriv);
+					p->priv_name = "execute";
+					n->privileges = list_make1(p);
+
+					n->targtype = ACL_TARGET_ALL_IN_MODULE;
+					n->objtype = OBJECT_FUNCTION; // OBJECT_PROCEDURE  OBJECT_ROUTINE
+					n->objects = list_make1(moduleName);
+					n->grantees = $10;
+					n->grant_option = $11;
+					n->grantor = $12;
+					$$ = (Node*)n;
+				}
+			| GRANT ON MODULE module_name REFERENCES ON ALL PROCEDURES TO grantee_list opt_grant_grant_option opt_granted_by
+				{
+					GrantStmt *n;
+					List *moduleName;
+
+					n = makeNode(GrantStmt);
+					moduleName = $4;
+					n->is_grant = true;
+
+					AccessPriv *p = makeNode(AccessPriv);
+					p->priv_name = "execute";
+					n->privileges = list_make1(p);
+
+					n->targtype = ACL_TARGET_ALL_IN_MODULE;
+					n->objtype = OBJECT_PROCEDURE;
+					n->objects = list_make1(moduleName);
+					n->grantees = $10;
+					n->grant_option = $11;
+					n->grantor = $12;
+					$$ = (Node*)n;
+				}
+			| GRANT ON MODULE module_name REFERENCES ON ALL ROUTINES TO grantee_list opt_grant_grant_option opt_granted_by
+				{
+					GrantStmt *n;
+					List *moduleName;
+
+					n = makeNode(GrantStmt);
+					moduleName = $4;
+					n->is_grant = true;
+
+					AccessPriv *p = makeNode(AccessPriv);
+					p->priv_name = "execute";
+					n->privileges = list_make1(p);
+
+					n->targtype = ACL_TARGET_ALL_IN_MODULE;
+					n->objtype = OBJECT_ROUTINE;
+					n->objects = list_make1(moduleName);
+					n->grantees = $10;
+					n->grant_option = $11;
+					n->grantor = $12;
+					$$ = (Node*)n;
+				}
 		;
 
 RevokeStmt:
@@ -7005,8 +7286,153 @@ RevokeStmt:
 					n->behavior = $11;
 					$$ = (Node *)n;
 				}
-		;
+			| REVOKE ON MODULE module_name CREATE FROM grantee_list opt_grant_grant_option opt_granted_by
+				{
+					GrantStmt *n;
+					List *moduleName;
 
+					n = makeNode(GrantStmt);
+					moduleName = $4;
+					n->is_grant = false;
+
+					AccessPriv *p = makeNode(AccessPriv);
+					p->priv_name = "create";
+					n->privileges = list_make1(p);
+
+					//n->targtype = ACL_TARGET_ALL_IN_MODULE;
+					n->objtype = OBJECT_MODULE;
+					n->objects = list_make1(moduleName);
+					n->grantees = $7;
+					n->grant_option = $8;
+					n->grantor = $9;
+					$$ = (Node*)n;
+				}
+			| REVOKE ON MODULE module_name REFERENCES ON module_privilege_target
+			FROM grantee_list opt_granted_by opt_drop_behavior
+				{
+					GrantStmt *n;
+					List *moduleName;
+					ListCell *cell;
+					ObjectWithArgs *func;
+					n = makeNode(GrantStmt);
+					moduleName = $4;
+					n->is_grant = false;
+					n->grant_option = false;
+
+					AccessPriv *p = makeNode(AccessPriv);
+					p->priv_name = "execute";
+					n->privileges = list_make1(p);
+
+					n->targtype = ($7)->targtype;
+					n->objtype = ($7)->objtype;
+					n->objects = ($7)->objs;
+					foreach(cell, n->objects)
+					{
+						func = (ObjectWithArgs *) lfirst(cell);
+						/* func is a pointer, so the following modifies the list element itself and not a copy of it
+						 * add moduleName to the front of func->objname to make the qualified name for the function */
+						func->objname = list_concat_copy(moduleName, func->objname);
+					}
+					n->grantees = $9;
+					n->grantor = $10;
+					n->behavior = $11;
+					$$ = (Node *)n;
+				}
+			| REVOKE ON MODULE module_name GRANT OPTION FOR REFERENCES ON module_privilege_target
+			FROM grantee_list opt_granted_by opt_drop_behavior
+				{
+					GrantStmt *n;
+					List *moduleName;
+					ListCell *cell;
+					ObjectWithArgs *func;
+					n = makeNode(GrantStmt);
+					moduleName = $4;
+					n->is_grant = false;
+					n->grant_option = true;
+
+					AccessPriv *p = makeNode(AccessPriv);
+					p->priv_name = "execute";
+					n->privileges = list_make1(p);
+
+					n->targtype = ($10)->targtype;
+					n->objtype = ($10)->objtype;
+					n->objects = ($10)->objs;
+					foreach(cell, n->objects)
+					{
+						func = (ObjectWithArgs *) lfirst(cell);
+						/* func is a pointer, so the following modifies the list element itself and not a copy of it
+						 * add moduleName to the front of func->objname to make the qualified name for the function */
+						func->objname = list_concat_copy(moduleName, func->objname);
+					}
+					n->grantees = $12;
+					n->grantor = $13;
+					n->behavior = $14;
+					$$ = (Node *)n;
+				}
+			| REVOKE ON MODULE module_name REFERENCES ON ALL FUNCTIONS FROM grantee_list opt_grant_grant_option opt_granted_by
+				{
+					GrantStmt *n;
+					List *moduleName;
+
+					n = makeNode(GrantStmt);
+					moduleName = $4;
+					n->is_grant = false;
+
+					AccessPriv *p = makeNode(AccessPriv);
+					p->priv_name = "execute";
+					n->privileges = list_make1(p);
+
+					n->targtype = ACL_TARGET_ALL_IN_MODULE;
+					n->objtype = OBJECT_FUNCTION;
+					n->objects = list_make1(moduleName);
+					n->grantees = $10;
+					n->grant_option = $11;
+					n->grantor = $12;
+					$$ = (Node*)n;
+				}
+			| REVOKE ON MODULE module_name REFERENCES ON ALL PROCEDURES FROM grantee_list opt_grant_grant_option opt_granted_by
+				{
+					GrantStmt *n;
+					List *moduleName;
+
+					n = makeNode(GrantStmt);
+					moduleName = $4;
+					n->is_grant = false;
+
+					AccessPriv *p = makeNode(AccessPriv);
+					p->priv_name = "execute";
+					n->privileges = list_make1(p);
+
+					n->targtype = ACL_TARGET_ALL_IN_MODULE;
+					n->objtype = OBJECT_PROCEDURE;
+					n->objects = list_make1(moduleName);
+					n->grantees = $10;
+					n->grant_option = $11;
+					n->grantor = $12;
+					$$ = (Node*)n;
+				}
+			| REVOKE ON MODULE module_name REFERENCES ON ALL ROUTINES FROM grantee_list opt_grant_grant_option opt_granted_by
+				{
+					GrantStmt *n;
+					List *moduleName;
+
+					n = makeNode(GrantStmt);
+					moduleName = $4;
+					n->is_grant = false;
+
+					AccessPriv *p = makeNode(AccessPriv);
+					p->priv_name = "execute";
+					n->privileges = list_make1(p);
+
+					n->targtype = ACL_TARGET_ALL_IN_MODULE;
+					n->objtype = OBJECT_ROUTINE;
+					n->objects = list_make1(moduleName);
+					n->grantees = $10;
+					n->grant_option = $11;
+					n->grantor = $12;
+					$$ = (Node*)n;
+				}
+		;
 
 /*
  * Privilege names are represented as strings; the validity of the privilege
@@ -7073,7 +7499,6 @@ privilege:	SELECT opt_column_list
 			}
 		;
 
-
 /* Don't bother trying to fold the first two rules into one using
  * opt_table.  You're going to get conflicts.
  */
@@ -7118,30 +7543,7 @@ privilege_target:
 					n->objs = $3;
 					$$ = n;
 				}
-			| FUNCTION function_with_argtypes_list
-				{
-					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
-					n->targtype = ACL_TARGET_OBJECT;
-					n->objtype = OBJECT_FUNCTION;
-					n->objs = $2;
-					$$ = n;
-				}
-			| PROCEDURE function_with_argtypes_list
-				{
-					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
-					n->targtype = ACL_TARGET_OBJECT;
-					n->objtype = OBJECT_PROCEDURE;
-					n->objs = $2;
-					$$ = n;
-				}
-			| ROUTINE function_with_argtypes_list
-				{
-					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
-					n->targtype = ACL_TARGET_OBJECT;
-					n->objtype = OBJECT_ROUTINE;
-					n->objs = $2;
-					$$ = n;
-				}
+			| privilege_target_proc
 			| DATABASE name_list
 				{
 					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
@@ -7238,8 +7640,61 @@ privilege_target:
 					n->objs = $5;
 					$$ = n;
 				}
+			| ALL FUNCTIONS IN_P MODULE name_list
+				{
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+					n->targtype = ACL_TARGET_ALL_IN_MODULE;
+					n->objtype = OBJECT_FUNCTION;
+					n->objs = $5;
+					$$ = n;
+				}
+			| ALL PROCEDURES IN_P MODULE name_list
+				{
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+					n->targtype = ACL_TARGET_ALL_IN_MODULE;
+					n->objtype = OBJECT_PROCEDURE;
+					n->objs = $5;
+					$$ = n;
+				}
+			| ALL ROUTINES IN_P MODULE name_list
+				{
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+					n->targtype = ACL_TARGET_ALL_IN_MODULE;
+					n->objtype = OBJECT_ROUTINE;
+					n->objs = $5;
+					$$ = n;
+				}
+		;
+
+module_privilege_target: privilege_target_proc
 		;
 
+privilege_target_proc:
+			FUNCTION function_with_argtypes_list
+				{
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+					n->targtype = ACL_TARGET_OBJECT;
+					n->objtype = OBJECT_FUNCTION;
+					n->objs = $2;
+					$$ = n;
+				}
+			| PROCEDURE function_with_argtypes_list
+				{
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+					n->targtype = ACL_TARGET_OBJECT;
+					n->objtype = OBJECT_PROCEDURE;
+					n->objs = $2;
+					$$ = n;
+				}
+			| ROUTINE function_with_argtypes_list
+				{
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+					n->targtype = ACL_TARGET_OBJECT;
+					n->objtype = OBJECT_ROUTINE;
+					n->objs = $2;
+					$$ = n;
+				}
+		;
 
 grantee_list:
 			grantee									{ $$ = list_make1($1); }
@@ -8650,6 +9105,14 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ALTER MODULE module_name RENAME TO name
+				{
+					AlterModuleRenameStmt *n = makeNode(AlterModuleRenameStmt);
+					n->modulename = $3;
+					n->newname = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			| ALTER opt_procedural LANGUAGE name RENAME TO name
 				{
 					RenameStmt *n = makeNode(RenameStmt);
@@ -9553,6 +10016,13 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
 					n->newowner = $7;
 					$$ = (Node *)n;
 				}
+			| ALTER MODULE module_name OWNER TO RoleSpec
+				{
+					AlterModuleOwnerStmt *n = makeNode(AlterModuleOwnerStmt);
+					n->modulename = $3;
+					n->newowner = $6;
+					$$ = (Node *)n;
+				}
 			| ALTER OPERATOR operator_with_argtypes OWNER TO RoleSpec
 				{
 					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
@@ -15785,6 +16255,7 @@ unreserved_keyword:
 			| MINUTE_P
 			| MINVALUE
 			| MODE
+			| MODULE
 			| MONTH_P
 			| MOVE
 			| NAME_P
@@ -16351,6 +16822,7 @@ bare_label_keyword:
 			| METHOD
 			| MINVALUE
 			| MODE
+			| MODULE
 			| MOVE
 			| NAME_P
 			| NAMES
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index d91951e1f6..a939f45c48 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -18,8 +18,10 @@
 #include "catalog/pg_aggregate.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "commands/dbcommands.h"
 #include "funcapi.h"
 #include "lib/stringinfo.h"
+#include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parse_agg.h"
@@ -1422,8 +1424,8 @@ func_get_detail(List *funcname,
 	/* Get list of possible candidates from namespace search */
 	raw_candidates = FuncnameGetCandidates(funcname, nargs, fargnames,
 										   expand_variadic, expand_defaults,
-										   include_out_arguments, false);
 
+										   include_out_arguments, false);
 	/*
 	 * Quickly check if there is an exact match to the input datatypes (there
 	 * can be only one)
@@ -1551,7 +1553,6 @@ func_get_detail(List *funcname,
 			/* one match only? then run with it... */
 			if (ncandidates == 1)
 				best_candidate = current_candidates;
-
 			/*
 			 * multiple candidates? then better decide or throw an error...
 			 */
@@ -1884,6 +1885,20 @@ FuncNameAsType(List *funcname)
 	Oid			result;
 	Type		typtup;
 
+	/*
+	 * check if this may be in a module. If it could be, don't check if it
+	 * may be a type since they can not be in a module.
+	 */
+	char	   *dummySchemaName = NULL;
+	char       *dummyModuleName = NULL;
+	char       *dummyObjName = NULL;
+	/* deconstruct the name list */
+	DeconstructQualifiedNameWithModule(funcname, &dummySchemaName, &dummyModuleName, &dummyObjName);
+	if (dummyModuleName)
+	{
+		return InvalidOid;
+	}
+
 	/*
 	 * temp_ok=false protects the <refsect1 id="sql-createfunction-security">
 	 * contract for writing SECURITY DEFINER functions safely.
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index cf64afbd85..c35de34b9b 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -955,7 +955,7 @@ make_oper_cache_key(ParseState *pstate, OprCacheKey *key, List *opname,
 	char	   *opername;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(opname, &schemaname, &opername);
+	DeconstructQualifiedNameNoModule(opname, &schemaname, &opername);
 
 	/* ensure zero-fill for stable hashing */
 	MemSet(key, 0, sizeof(OprCacheKey));
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 307114a30d..514ece6a5e 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -166,7 +166,7 @@ LookupTypeNameExtended(ParseState *pstate,
 		char	   *typname;
 
 		/* deconstruct the name list */
-		DeconstructQualifiedName(typeName->names, &schemaname, &typname);
+		DeconstructQualifiedNameNoModule(typeName->names, &schemaname, &typname);
 
 		if (schemaname)
 		{
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0eea214dd8..d024639217 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -103,12 +103,21 @@ typedef struct
 	RoleSpec   *authrole;		/* owner of schema */
 	List	   *sequences;		/* CREATE SEQUENCE items */
 	List	   *tables;			/* CREATE TABLE items */
+	List	   *modules;		/* CREATE MODULE items */
 	List	   *views;			/* CREATE VIEW items */
 	List	   *indexes;		/* CREATE INDEX items */
 	List	   *triggers;		/* CREATE TRIGGER items */
 	List	   *grants;			/* GRANT items */
 } CreateSchemaStmtContext;
 
+/* State shared by transformCreateModuleStmt and its subroutines */
+typedef struct
+{
+	const char *stmtType;		/* "CREATE MODULE" or "ALTER MODULE" */
+	List	   *modulename;		/* name of module */
+	RoleSpec   *authrole;		/* owner of module */
+	List	   *routines;		/* CREATE FUNCTION or CREATE PROCEDURE items */
+} CreateModuleStmtContext;
 
 static void transformColumnDefinition(CreateStmtContext *cxt,
 									  ColumnDef *column);
@@ -3822,6 +3831,7 @@ transformCreateSchemaStmt(CreateSchemaStmt *stmt)
 	cxt.authrole = (RoleSpec *) stmt->authrole;
 	cxt.sequences = NIL;
 	cxt.tables = NIL;
+	cxt.modules = NIL;
 	cxt.views = NIL;
 	cxt.indexes = NIL;
 	cxt.triggers = NIL;
@@ -3859,6 +3869,10 @@ transformCreateSchemaStmt(CreateSchemaStmt *stmt)
 				}
 				break;
 
+			case T_CreateModuleStmt:
+				cxt.modules = lappend(cxt.modules, element);
+				break;
+
 			case T_ViewStmt:
 				{
 					ViewStmt   *elp = (ViewStmt *) element;
@@ -3903,6 +3917,7 @@ transformCreateSchemaStmt(CreateSchemaStmt *stmt)
 	result = NIL;
 	result = list_concat(result, cxt.sequences);
 	result = list_concat(result, cxt.tables);
+	result = list_concat(result, cxt.modules);
 	result = list_concat(result, cxt.views);
 	result = list_concat(result, cxt.indexes);
 	result = list_concat(result, cxt.triggers);
@@ -3911,6 +3926,70 @@ transformCreateSchemaStmt(CreateSchemaStmt *stmt)
 	return result;
 }
 
+/*
+ * transformCreateModuleStmt -
+ *	  analyzes the CREATE MODULE statement
+ *
+ * Split the module element list into individual commands and place
+ * them in the result list in an order such that there are no forward
+ * references. Currently there are only functions allowed in modules
+ * but the spec allows variables and temp tables so this provides a
+ * way to have them created before the functions that could use them.
+ *
+ * The functions are also checked to make sure there is not an explicit
+ * namespace attempted to be used.
+ */
+List *
+transformCreateModuleStmt(CreateModuleStmt *stmt)
+{
+	CreateModuleStmtContext cxt;
+	List	   *result;
+	ListCell   *elements;
+
+
+	cxt.stmtType = "CREATE MODULE";
+	cxt.modulename = stmt->modulename;
+	cxt.authrole = (RoleSpec *) stmt->authrole;
+	cxt.routines = NIL;
+
+	/*
+	 * Run through each module element in the module element list. Separate
+	 * statements by type, and do preliminary analysis.
+	 */
+	foreach(elements, stmt->moduleElts)
+	{
+		Node	   *element = lfirst(elements);
+
+		switch (nodeTag(element))
+		{
+			case T_CreateFunctionStmt:
+				{
+					CreateFunctionStmt *elp = (CreateFunctionStmt *) element;
+
+					if (list_length(elp->funcname) > 1)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_MODULE_DEFINITION),
+								 errmsg("CREATE FUNCTION (%s) specifies a "
+										"namespace inside of CREATE MODULE (%s)",
+										NameListToString(elp->funcname),
+										NameListToString(cxt.modulename))));
+
+					cxt.routines = lappend(cxt.routines, element);
+				}
+				break;
+
+			default:
+				elog(ERROR, "unrecognized node type: %d",
+					 (int) nodeTag(element));
+		}
+	}
+
+	result = NIL;
+	result = list_concat(result, cxt.routines);
+
+	return result;
+}
+
 /*
  * setSchemaName
  *		Set or check schema name in an element of a CREATE SCHEMA command
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 83e4e37c78..fb7b8b4a6f 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -43,6 +43,7 @@
 #include "commands/extension.h"
 #include "commands/lockcmds.h"
 #include "commands/matview.h"
+#include "commands/modulecmds.h"
 #include "commands/policy.h"
 #include "commands/portalcmds.h"
 #include "commands/prepare.h"
@@ -85,7 +86,9 @@ static void ProcessUtilitySlow(ParseState *pstate,
 							   ParamListInfo params,
 							   QueryEnvironment *queryEnv,
 							   DestReceiver *dest,
-							   QueryCompletion *qc);
+							   QueryCompletion *qc,
+							   Oid namespaceIdWhenCreatingInModule,
+							   Oid moduleIdWhenCreatingInModule);
 static void ExecDropStmt(DropStmt *stmt, bool isTopLevel);
 
 /*
@@ -147,6 +150,8 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_AlterFdwStmt:
 		case T_AlterForeignServerStmt:
 		case T_AlterFunctionStmt:
+		case T_AlterModuleAlterFuncStmt:
+		case T_AlterModuleCreateReplaceFuncStmt:
 		case T_AlterObjectDependsStmt:
 		case T_AlterObjectSchemaStmt:
 		case T_AlterOpFamilyStmt:
@@ -179,6 +184,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_CreateForeignServerStmt:
 		case T_CreateForeignTableStmt:
 		case T_CreateFunctionStmt:
+		case T_CreateModuleStmt:
 		case T_CreateOpClassStmt:
 		case T_CreateOpFamilyStmt:
 		case T_CreatePLangStmt:
@@ -212,6 +218,8 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_ReassignOwnedStmt:
 		case T_RefreshMatViewStmt:
 		case T_RenameStmt:
+		case T_AlterModuleRenameStmt:
+		case T_AlterModuleOwnerStmt:
 		case T_RuleStmt:
 		case T_SecLabelStmt:
 		case T_TruncateStmt:
@@ -525,9 +533,76 @@ ProcessUtility(PlannedStmt *pstmt,
 								context, params, queryEnv,
 								dest, qc);
 	else
+	{
 		standard_ProcessUtility(pstmt, queryString, readOnlyTree,
+								context, params, queryEnv,
+								dest, qc, InvalidOid, InvalidOid /* not in module */);
+	}
+}
+
+/*
+ * ProcessUtilityUsingModule
+ *		similar to ProcessUtility but for when using modules
+ *
+ *	pstmt: PlannedStmt wrapper for the utility statement
+ *	queryString: original source text of command
+ *	readOnlyTree: if true, pstmt's node tree must not be modified
+ *	context: identifies source of statement (toplevel client command,
+ *		non-toplevel client command, subcommand of a larger utility command)
+ *	params: parameters to use during execution
+ *	queryEnv: environment for parse through execution (e.g., ephemeral named
+ *		tables like trigger transition tables).  May be NULL.
+ *	dest: where to send results
+ *	qc: where to store command completion status data.  May be NULL,
+ *		but if not, then caller must have initialized it.
+ *	namespaceId: oid of the schema that the enclosing module for the stmt is in.
+ *	moduleId: oid of the module the stmt is being called in.
+ *
+ * Caller MUST supply a queryString; it is not allowed to pass NULL.
+ * If you really don't have source text, you can pass a constant string,
+ * perhaps "(query not available)".
+ *
+ * Note for users of ProcessUtility_hook: the same queryString may be passed
+ * to multiple invocations of ProcessUtility when processing a query string
+ * containing multiple semicolon-separated statements.  One should use
+ * pstmt->stmt_location and pstmt->stmt_len to identify the substring
+ * containing the current statement.  Keep in mind also that some utility
+ * statements that CREATE MODULE will recurse to ProcessUtility to process
+ * sub-statements to define functions and procedures passing down the same
+ * queryString, stmt_location, and stmt_len that were given for the whole statement.
+ */
+void
+ProcessUtilityUsingModule(PlannedStmt *pstmt,
+						  const char *queryString,
+						  bool readOnlyTree,
+						  ProcessUtilityContext context,
+						  ParamListInfo params,
+						  QueryEnvironment *queryEnv,
+						  DestReceiver *dest,
+						  QueryCompletion *qc,
+						  Oid namespaceId,
+						  Oid moduleId)
+{
+	Assert(IsA(pstmt, PlannedStmt));
+	Assert(pstmt->commandType == CMD_UTILITY);
+	Assert(queryString != NULL);	/* required as of 8.4 */
+	Assert(qc == NULL || qc->commandTag == CMDTAG_UNKNOWN);
+
+	/*
+	 * We provide a function hook variable that lets loadable plugins get
+	 * control when ProcessUtility is called.  Such a plugin would normally
+	 * call standard_ProcessUtility().
+	 */
+	if (ProcessUtility_hook)
+		(*ProcessUtility_hook) (pstmt, queryString, readOnlyTree,
 								context, params, queryEnv,
 								dest, qc);
+	else
+	{
+		standard_ProcessUtility(pstmt, queryString, readOnlyTree,
+										   context, params, queryEnv,
+										   dest, qc, namespaceId, moduleId);
+	}
 }
 
 /*
@@ -549,7 +624,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 						ParamListInfo params,
 						QueryEnvironment *queryEnv,
 						DestReceiver *dest,
-						QueryCompletion *qc)
+						QueryCompletion *qc,
+						Oid namespaceIdWhenCreatingInModule,
+						Oid moduleIdWhenCreatingInModule)
 {
 	Node	   *parsetree;
 	bool		isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
@@ -965,7 +1042,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				if (EventTriggerSupportsObjectType(stmt->objtype))
 					ProcessUtilitySlow(pstate, pstmt, queryString,
 									   context, params, queryEnv,
-									   dest, qc);
+									   dest, qc,
+									   namespaceIdWhenCreatingInModule,
+									   moduleIdWhenCreatingInModule);
 				else
 					ExecuteGrantStmt(stmt);
 			}
@@ -978,7 +1057,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				if (EventTriggerSupportsObjectType(stmt->removeType))
 					ProcessUtilitySlow(pstate, pstmt, queryString,
 									   context, params, queryEnv,
-									   dest, qc);
+									   dest, qc,
+									   namespaceIdWhenCreatingInModule,
+									   moduleIdWhenCreatingInModule);
 				else
 					ExecDropStmt(stmt, isTopLevel);
 			}
@@ -991,7 +1072,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				if (EventTriggerSupportsObjectType(stmt->renameType))
 					ProcessUtilitySlow(pstate, pstmt, queryString,
 									   context, params, queryEnv,
-									   dest, qc);
+									   dest, qc,
+									   namespaceIdWhenCreatingInModule,
+									   moduleIdWhenCreatingInModule);
 				else
 					ExecRenameStmt(stmt);
 			}
@@ -1004,7 +1087,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				if (EventTriggerSupportsObjectType(stmt->objectType))
 					ProcessUtilitySlow(pstate, pstmt, queryString,
 									   context, params, queryEnv,
-									   dest, qc);
+									   dest, qc,
+									   namespaceIdWhenCreatingInModule,
+									   moduleIdWhenCreatingInModule);
 				else
 					ExecAlterObjectDependsStmt(stmt, NULL);
 			}
@@ -1017,7 +1102,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				if (EventTriggerSupportsObjectType(stmt->objectType))
 					ProcessUtilitySlow(pstate, pstmt, queryString,
 									   context, params, queryEnv,
-									   dest, qc);
+									   dest, qc,
+									   namespaceIdWhenCreatingInModule,
+									   moduleIdWhenCreatingInModule);
 				else
 					ExecAlterObjectSchemaStmt(stmt, NULL);
 			}
@@ -1030,7 +1117,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				if (EventTriggerSupportsObjectType(stmt->objectType))
 					ProcessUtilitySlow(pstate, pstmt, queryString,
 									   context, params, queryEnv,
-									   dest, qc);
+									   dest, qc,
+									   namespaceIdWhenCreatingInModule,
+									   moduleIdWhenCreatingInModule);
 				else
 					ExecAlterOwnerStmt(stmt);
 			}
@@ -1043,7 +1132,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				if (EventTriggerSupportsObjectType(stmt->objtype))
 					ProcessUtilitySlow(pstate, pstmt, queryString,
 									   context, params, queryEnv,
-									   dest, qc);
+									   dest, qc,
+									   namespaceIdWhenCreatingInModule,
+									   moduleIdWhenCreatingInModule);
 				else
 					CommentObject(stmt);
 				break;
@@ -1056,7 +1147,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				if (EventTriggerSupportsObjectType(stmt->objtype))
 					ProcessUtilitySlow(pstate, pstmt, queryString,
 									   context, params, queryEnv,
-									   dest, qc);
+									   dest, qc,
+									   namespaceIdWhenCreatingInModule,
+									   moduleIdWhenCreatingInModule);
 				else
 					ExecSecLabelStmt(stmt);
 				break;
@@ -1066,7 +1159,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
 							   context, params, queryEnv,
-							   dest, qc);
+							   dest, qc,
+							   namespaceIdWhenCreatingInModule,
+							   moduleIdWhenCreatingInModule);
 			break;
 	}
 
@@ -1093,7 +1188,9 @@ ProcessUtilitySlow(ParseState *pstate,
 				   ParamListInfo params,
 				   QueryEnvironment *queryEnv,
 				   DestReceiver *dest,
-				   QueryCompletion *qc)
+				   QueryCompletion *qc,
+				   Oid namespaceIdWhenCreatingInModule,
+				   Oid moduleIdWhenCreatingInModule)
 {
 	Node	   *parsetree = pstmt->utilityStmt;
 	bool		isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
@@ -1130,6 +1227,18 @@ ProcessUtilitySlow(ParseState *pstate,
 				commandCollected = true;
 				break;
 
+			case T_CreateModuleStmt:	/* CREATE Module */
+				CreateModuleCommand((CreateModuleStmt *) parsetree,
+									queryString,
+									pstmt->stmt_location,
+									pstmt->stmt_len);
+				/*
+				 * EventTriggerCollectSimpleCommand called by
+				 * CreateModuleCommand
+				 */
+				commandCollected = true;
+				break;
+
 			case T_CreateStmt:
 			case T_CreateForeignTableStmt:
 				{
@@ -1265,6 +1374,14 @@ ProcessUtilitySlow(ParseState *pstate,
 				}
 				break;
 
+			case T_AlterModuleCreateReplaceFuncStmt:
+				AlterModuleCreateReplaceFunction((AlterModuleCreateReplaceFuncStmt *) parsetree,
+									queryString,
+									pstmt->stmt_location,
+									pstmt->stmt_len);
+				break;
+
+
 			case T_AlterTableStmt:
 				{
 					AlterTableStmt *atstmt = (AlterTableStmt *) parsetree;
@@ -1638,13 +1755,22 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateFunctionStmt:	/* CREATE FUNCTION */
-				address = CreateFunction(pstate, (CreateFunctionStmt *) parsetree);
+				if (OidIsValid(moduleIdWhenCreatingInModule))
+					address = CreateFunctionInModule(pstate, (CreateFunctionStmt *) parsetree,
+													 namespaceIdWhenCreatingInModule,
+													 moduleIdWhenCreatingInModule);
+				else
+					address = CreateFunction(pstate, (CreateFunctionStmt *) parsetree);
 				break;
 
 			case T_AlterFunctionStmt:	/* ALTER FUNCTION */
 				address = AlterFunction(pstate, (AlterFunctionStmt *) parsetree);
 				break;
 
+			case T_AlterModuleAlterFuncStmt: /* ALTER MODULE CONTENTS */
+				address = AlterModuleAlterFunction(pstate, (AlterModuleAlterFuncStmt *) parsetree);
+				break;
+
 			case T_RuleStmt:	/* CREATE RULE */
 				address = DefineRule((RuleStmt *) parsetree, queryString);
 				break;
@@ -1757,6 +1883,10 @@ ProcessUtilitySlow(ParseState *pstate,
 				address = ExecRenameStmt((RenameStmt *) parsetree);
 				break;
 
+			case T_AlterModuleRenameStmt:
+				address = AlterModuleRename((AlterModuleRenameStmt *) parsetree);
+				break;
+
 			case T_AlterObjectDependsStmt:
 				address =
 					ExecAlterObjectDependsStmt((AlterObjectDependsStmt *) parsetree,
@@ -1773,6 +1903,10 @@ ProcessUtilitySlow(ParseState *pstate,
 				address = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
 				break;
 
+			case T_AlterModuleOwnerStmt:
+				address = AlterModuleOwner((AlterModuleOwnerStmt *) parsetree);
+				break;
+
 			case T_AlterOperatorStmt:
 				address = AlterOperator((AlterOperatorStmt *) parsetree);
 				break;
@@ -2621,6 +2755,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_STATISTIC_EXT:
 					tag = CMDTAG_DROP_STATISTICS;
 					break;
+				case OBJECT_MODULE:
+					tag = CMDTAG_DROP_MODULE;
+					break;
 				default:
 					tag = CMDTAG_UNKNOWN;
 			}
@@ -2642,6 +2779,22 @@ CreateCommandTag(Node *parsetree)
 			tag = CMDTAG_COPY;
 			break;
 
+		case T_AlterModuleRenameStmt:
+			tag = CMDTAG_ALTER_MODULE_RENAME;
+			break;
+
+		case T_AlterModuleOwnerStmt:
+			tag = CMDTAG_ALTER_MODULE_OWNER;
+			break;
+
+		case T_AlterModuleCreateReplaceFuncStmt:
+			tag = CMDTAG_ALTER_MODULE_CREATE_REPLACE_FUNC;
+			break;
+
+		case T_AlterModuleAlterFuncStmt:
+			tag = CMDTAG_ALTER_MODULE_ALTER_FUNCTION;
+			break;
+
 		case T_RenameStmt:
 
 			/*
@@ -2776,6 +2929,10 @@ CreateCommandTag(Node *parsetree)
 				tag = CMDTAG_CREATE_FUNCTION;
 			break;
 
+		case T_CreateModuleStmt:
+			tag = CMDTAG_CREATE_MODULE;
+			break;
+
 		case T_IndexStmt:
 			tag = CMDTAG_CREATE_INDEX;
 			break;
@@ -3419,6 +3576,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_CreateModuleStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 		case T_IndexStmt:
 			lev = LOGSTMT_DDL;
 			break;
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 0a16f8156c..05d9e023d6 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -773,6 +773,10 @@ acldefault(ObjectType objtype, Oid ownerId)
 			world_default = ACL_NO_RIGHTS;
 			owner_default = ACL_ALL_RIGHTS_LARGEOBJECT;
 			break;
+		case OBJECT_MODULE:
+			world_default = ACL_NO_RIGHTS;
+			owner_default = ACL_ALL_RIGHTS_MODULE;
+			break;
 		case OBJECT_SCHEMA:
 			world_default = ACL_NO_RIGHTS;
 			owner_default = ACL_ALL_RIGHTS_SCHEMA;
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index feef999863..b46e064a44 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_language.h"
+#include "catalog/pg_module.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
@@ -3304,6 +3305,31 @@ free_attstatsslot(AttStatsSlot *sslot)
 		pfree(sslot->numbers_arr);
 }
 
+/*				---------- PG_MODULE CACHE ----------				 */
+
+/*
+ * get_module_name
+ *		Returns the name of a given module or NULL if no such module
+ */
+char *
+get_module_name(Oid modid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(MODULEOID, ObjectIdGetDatum(modid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_module modtup = (Form_pg_module) GETSTRUCT(tp);
+		char	   *result;
+
+		result = pstrdup(NameStr(modtup->modname));
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return NULL;
+}
+
 /*				---------- PG_NAMESPACE CACHE ----------				 */
 
 /*
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index f4e7819f1e..bf1db809af 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -43,6 +43,7 @@
 #include "catalog/pg_foreign_server.h"
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_language.h"
+#include "catalog/pg_module.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
@@ -508,6 +509,28 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		4
 	},
+	{ModuleRelationId,		/* MODULENAME */
+		ModuleNameIndexId,
+		2,
+		{
+			Anum_pg_module_modname,
+			Anum_pg_module_nspoid,
+			0,
+			0
+		},
+		16
+	},
+	{ModuleRelationId,		/* MODULEOID */
+		ModuleOidIndexId,
+		1,
+		{
+			Anum_pg_module_oid,
+			0,
+			0,
+			0
+		},
+		16
+	},
 	{NamespaceRelationId,		/* NAMESPACENAME */
 		NamespaceNameIndexId,
 		1,
@@ -586,13 +609,13 @@ static const struct cachedesc cacheinfo[] = {
 		32
 	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
-		ProcedureNameArgsNspIndexId,
-		3,
+		ProcedureNameArgsNspModIndexId,
+		4,
 		{
 			Anum_pg_proc_proname,
 			Anum_pg_proc_proargtypes,
 			Anum_pg_proc_pronamespace,
-			0
+			Anum_pg_proc_promodule
 		},
 		128
 	},
@@ -1268,13 +1291,18 @@ GetSysCacheOid(int cacheId,
 	Oid			result;
 
 	tuple = SearchSysCache(cacheId, key1, key2, key3, key4);
+
 	if (!HeapTupleIsValid(tuple))
 		return InvalidOid;
+
 	result = heap_getattr(tuple, oidcol,
 						  SysCache[cacheId]->cc_tupdesc,
 						  &isNull);
+
 	Assert(!isNull);			/* columns used as oids should never be NULL */
+
 	ReleaseSysCache(tuple);
+
 	return result;
 }
 
diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index a4e1a46644..524db891cb 100644
--- a/src/backend/utils/errcodes.txt
+++ b/src/backend/utils/errcodes.txt
@@ -363,6 +363,7 @@ Section: Class 42 - Syntax Error or Access Rule Violation
 42883    E    ERRCODE_UNDEFINED_FUNCTION                                     undefined_function
 26000    E    ERRCODE_UNDEFINED_PSTATEMENT
 3F000    E    ERRCODE_UNDEFINED_SCHEMA
+42P00    E    ERRCODE_UNDEFINED_MODULE                                       undefined_module
 42P01    E    ERRCODE_UNDEFINED_TABLE                                        undefined_table
 42P02    E    ERRCODE_UNDEFINED_PARAMETER                                    undefined_parameter
 42704    E    ERRCODE_UNDEFINED_OBJECT                                       undefined_object
@@ -373,6 +374,7 @@ Section: Class 42 - Syntax Error or Access Rule Violation
 42P05    E    ERRCODE_DUPLICATE_PSTATEMENT                                   duplicate_prepared_statement
 42P06    E    ERRCODE_DUPLICATE_SCHEMA                                       duplicate_schema
 42P07    E    ERRCODE_DUPLICATE_TABLE                                        duplicate_table
+42P08    E    ERRCODE_DUPLICATE_MODULE                                       duplicate_module
 42712    E    ERRCODE_DUPLICATE_ALIAS                                        duplicate_alias
 42710    E    ERRCODE_DUPLICATE_OBJECT                                       duplicate_object
 42702    E    ERRCODE_AMBIGUOUS_COLUMN                                       ambiguous_column
@@ -388,6 +390,7 @@ Section: Class 42 - Syntax Error or Access Rule Violation
 42P15    E    ERRCODE_INVALID_SCHEMA_DEFINITION                              invalid_schema_definition
 42P16    E    ERRCODE_INVALID_TABLE_DEFINITION                               invalid_table_definition
 42P17    E    ERRCODE_INVALID_OBJECT_DEFINITION                              invalid_object_definition
+42P18    E    ERRCODE_INVALID_MODULE_DEFINITION                              invalid_module_definition
 
 Section: Class 44 - WITH CHECK OPTION Violation
 
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index b9a25442f5..83fef4108e 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -21,6 +21,7 @@
 #include "catalog/pg_collation_d.h"
 #include "catalog/pg_extension_d.h"
 #include "catalog/pg_namespace_d.h"
+#include "catalog/pg_module_d.h"
 #include "catalog/pg_operator_d.h"
 #include "catalog/pg_proc_d.h"
 #include "catalog/pg_publication_d.h"
@@ -105,6 +106,7 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	int			numOperators;
 	int			numCollations;
 	int			numNamespaces;
+	int			numModules;
 	int			numExtensions;
 	int			numPublications;
 	int			numAggregates;
@@ -151,6 +153,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 
 	getOwnedSeqs(fout, tblinfo, numTables);
 
+	pg_log_info("reading modules");
+	(void) getModules(fout, &numModules);
+
 	pg_log_info("reading user-defined functions");
 	(void) getFuncs(fout, &numFuncs);
 
@@ -885,6 +890,24 @@ findNamespaceByOid(Oid oid)
 	return (NamespaceInfo *) dobj;
 }
 
+/*
+ * findModuleByOid
+ *	  finds the DumpableObject for the module with the given oid
+ *	  returns NULL if not found
+ */
+ModuleInfo *
+findModuleByOid(Oid oid)
+{
+	CatalogId	catId;
+	DumpableObject *dobj;
+
+	catId.tableoid = ModuleRelationId;
+	catId.oid = oid;
+	dobj = findObjectByCatalogId(catId);
+	Assert(dobj == NULL || dobj->objType == DO_MODULE);
+	return (ModuleInfo *) dobj;
+}
+
 /*
  * findExtensionByOid
  *	  finds the DumpableObject for the extension with the given oid
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e3ddf19959..381297376c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -202,6 +202,7 @@ static int	findSecLabels(Oid classoid, Oid objoid, SecLabelItem **items);
 static void collectSecLabels(Archive *fout);
 static void dumpDumpableObject(Archive *fout, DumpableObject *dobj);
 static void dumpNamespace(Archive *fout, const NamespaceInfo *nspinfo);
+static void dumpModule(Archive *fout, const ModuleInfo *modinfo);
 static void dumpExtension(Archive *fout, const ExtensionInfo *extinfo);
 static void dumpType(Archive *fout, const TypeInfo *tyinfo);
 static void dumpBaseType(Archive *fout, const TypeInfo *tyinfo);
@@ -4907,6 +4908,85 @@ getNamespaces(Archive *fout, int *numNamespaces)
 	return nsinfo;
 }
 
+/*
+ * getModules:
+ *	read all modulees in the system catalogs and return them in the
+ * ModuleInfo* structure
+ *
+ *	numModules is set to the number of moduless read in
+ */
+ModuleInfo *
+getModules(Archive *fout, int *numModules)
+{
+	PGresult *res;
+	int ntups;
+	int	i;
+	PQExpBuffer query;
+	ModuleInfo *modinfo;
+	int i_oid;
+	int i_modname;
+	int i_nspoid;
+	int i_modowner;
+	int i_modacl;
+	int i_acldefault;
+
+	query = createPQExpBuffer();
+
+	/*
+	 * we fetch all modules in pg_module
+	 */
+	appendPQExpBuffer(query, "SELECT n.oid, n.modname, "
+					  "n.nspoid, "
+					  "n.modowner, "
+					  "n.modacl, "
+					  "acldefault('n', n.modowner) AS acldefault "
+					  "FROM pg_module n");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	modinfo = (ModuleInfo *) pg_malloc(ntups * sizeof(ModuleInfo));
+
+	i_oid = PQfnumber(res, "oid");
+	i_modname = PQfnumber(res, "modname");
+	i_nspoid = PQfnumber(res, "nspoid");
+	i_modowner = PQfnumber(res, "modowner");
+	i_modacl = PQfnumber(res, "modacl");
+	i_acldefault = PQfnumber(res, "acldefault");
+
+	for (i = 0; i < ntups; i++)
+	{
+		const char *modowner;
+		const char *nspoid;
+
+		modinfo[i].dobj.objType = DO_MODULE;
+		modinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+		AssignDumpId(&modinfo[i].dobj);
+		modinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_modname));
+		modinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_modacl));
+		modinfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault));
+		modinfo[i].dacl.privtype = 0;
+		modinfo[i].dacl.initprivs = NULL;
+		nspoid = PQgetvalue(res, i, i_nspoid);
+		modinfo[i].nspoid = atooid(nspoid);
+		modowner = PQgetvalue(res, i, i_modowner);
+		modinfo[i].modowner = atooid(modowner);
+		modinfo[i].rolname = getRoleName(modowner);
+
+		/* Mark whether module has an ACL */
+		if (!PQgetisnull(res, i, i_modacl))
+			modinfo[i].dobj.components |= DUMP_COMPONENT_ACL;
+	}
+
+	PQclear(res);
+	destroyPQExpBuffer(query);
+
+	*numModules = ntups;
+
+	return modinfo;
+}
+
 /*
  * findNamespace:
  *		given a namespace OID, look up the info read by getNamespaces
@@ -4922,6 +5002,21 @@ findNamespace(Oid nsoid)
 	return nsinfo;
 }
 
+/*
+ * findModule:
+ *		given a module OID, look up the info read by getModules
+ */
+static ModuleInfo *
+findModule(Oid modoid)
+{
+	ModuleInfo *modinfo;
+
+	modinfo = findModuleByOid(modoid);
+	if (modinfo == NULL)
+		fatal("module with OID %u does not exist", modoid);
+	return modinfo;
+}
+
 /*
  * getExtensions:
  *	  read all extensions in the system catalogs and return them in the
@@ -5748,6 +5843,7 @@ getFuncs(Archive *fout, int *numFuncs)
 	int			i_oid;
 	int			i_proname;
 	int			i_pronamespace;
+	int			i_promodule;
 	int			i_proowner;
 	int			i_prolang;
 	int			i_pronargs;
@@ -5790,6 +5886,7 @@ getFuncs(Archive *fout, int *numFuncs)
 						  "p.proacl, "
 						  "acldefault('f', p.proowner) AS acldefault, "
 						  "p.pronamespace, "
+						  "p.pronmodule, "
 						  "p.proowner "
 						  "FROM pg_proc p "
 						  "LEFT JOIN pg_init_privs pip ON "
@@ -5832,6 +5929,7 @@ getFuncs(Archive *fout, int *numFuncs)
 						  "pronargs, proargtypes, prorettype, proacl, "
 						  "acldefault('f', proowner) AS acldefault, "
 						  "pronamespace, "
+						  "promodule, "
 						  "proowner "
 						  "FROM pg_proc p "
 						  "WHERE NOT proisagg"
@@ -5877,6 +5975,7 @@ getFuncs(Archive *fout, int *numFuncs)
 	i_oid = PQfnumber(res, "oid");
 	i_proname = PQfnumber(res, "proname");
 	i_pronamespace = PQfnumber(res, "pronamespace");
+	i_promodule = PQfnumber(res, "promodule");
 	i_proowner = PQfnumber(res, "proowner");
 	i_prolang = PQfnumber(res, "prolang");
 	i_pronargs = PQfnumber(res, "pronargs");
@@ -5894,6 +5993,8 @@ getFuncs(Archive *fout, int *numFuncs)
 		finfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_proname));
 		finfo[i].dobj.namespace =
 			findNamespace(atooid(PQgetvalue(res, i, i_pronamespace)));
+		finfo[i].dobj.module =
+			findModule(atooid(PQgetvalue(res, i, i_promodule)));
 		finfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_proacl));
 		finfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault));
 		finfo[i].dacl.privtype = 0;
@@ -9615,6 +9716,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_NAMESPACE:
 			dumpNamespace(fout, (const NamespaceInfo *) dobj);
 			break;
+		case DO_MODULE:
+			dumpModule(fout, (const ModuleInfo *) dobj);
+			break;
 		case DO_EXTENSION:
 			dumpExtension(fout, (const ExtensionInfo *) dobj);
 			break;
@@ -9853,6 +9957,44 @@ dumpNamespace(Archive *fout, const NamespaceInfo *nspinfo)
 	destroyPQExpBuffer(delq);
 }
 
+/*
+ * dumpModule
+ *	  writes out to fout the queries to recreate a user-defined module
+ *	  Functions and procedures will be added to the module when those
+ *	  functions and procedures are created, with ALTER MODULE.
+ */
+static void
+dumpModule(Archive *fout, const ModuleInfo *modinfo)
+{
+	DumpOptions *dopt = fout->dopt;
+	PQExpBuffer q;
+	PQExpBuffer delq;
+	char	   *qmodname;
+
+	/* Do nothing in data-only dump */
+	if (dopt->dataOnly)
+		return;
+
+	q = createPQExpBuffer();
+	delq = createPQExpBuffer();
+
+	qmodname = pg_strdup(fmtId(modinfo->dobj.name));
+
+	appendPQExpBuffer(delq, "DROP IF EXISTS MODULE %s;\n", qmodname);
+	appendPQExpBuffer(q, "CREATE MODULE %s;\n", qmodname);
+
+
+	if (modinfo->dobj.dump & DUMP_COMPONENT_ACL)
+		dumpACL(fout, modinfo->dobj.dumpId, InvalidDumpId, "MODULE",
+				qmodname, NULL, NULL,
+				modinfo->rolname, &modinfo->dacl);
+
+	free(qmodname);
+
+	destroyPQExpBuffer(q);
+	destroyPQExpBuffer(delq);
+}
+
 /*
  * dumpExtension
  *	  writes out to fout the queries to recreate an extension
@@ -11539,11 +11681,23 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
 	appendPQExpBuffer(delqry, "DROP %s %s;\n",
 					  keyword, qual_funcsig);
 
-	appendPQExpBuffer(q, "CREATE %s %s.%s",
-					  keyword,
-					  fmtId(finfo->dobj.namespace->dobj.name),
-					  funcfullsig ? funcfullsig :
-					  funcsig);
+	if (finfo->dobj.module == NULL)
+	{
+		appendPQExpBuffer(q, "CREATE %s %s.%s",
+						  keyword,
+						  fmtId(finfo->dobj.namespace->dobj.name),
+						  funcfullsig ? funcfullsig :
+						  funcsig);
+	}
+	else
+	{
+		appendPQExpBuffer(q, "ALTER MODULE %s CREATE %s %s.%s",
+						  fmtId(finfo->dobj.module->dobj.name),
+						  keyword,
+						  fmtId(finfo->dobj.namespace->dobj.name),
+						  funcfullsig ? funcfullsig :
+						  funcsig);
+	}
 
 	if (prokind[0] == PROKIND_PROCEDURE)
 		 /* no result type to output */ ;
@@ -17647,6 +17801,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 		switch (dobj->objType)
 		{
 			case DO_NAMESPACE:
+			case DO_MODULE:
 			case DO_EXTENSION:
 			case DO_TYPE:
 			case DO_SHELL_TYPE:
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 066a129ee5..4516a11a5a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -82,7 +82,8 @@ typedef enum
 	DO_PUBLICATION,
 	DO_PUBLICATION_REL,
 	DO_PUBLICATION_TABLE_IN_SCHEMA,
-	DO_SUBSCRIPTION
+	DO_SUBSCRIPTION,
+	DO_MODULE
 } DumpableObjectType;
 
 /*
@@ -136,6 +137,7 @@ typedef struct _dumpableObject
 	DumpId		dumpId;			/* assigned by AssignDumpId() */
 	char	   *name;			/* object name (should never be NULL) */
 	struct _namespaceInfo *namespace;	/* containing namespace, or NULL */
+	struct _moduleInfo *module;	/* containing module, or NULL */
 	DumpComponents dump;		/* bitmask of components requested to dump */
 	DumpComponents dump_contains;	/* as above, but for contained objects */
 	DumpComponents components;	/* bitmask of components available to dump */
@@ -175,6 +177,16 @@ typedef struct _namespaceInfo
 	const char *rolname;		/* name of owner */
 } NamespaceInfo;
 
+typedef struct _moduleInfo
+{
+	DumpableObject dobj;
+	DumpableAcl dacl;
+	bool		create;			/* CREATE MODULE, or just set owner? */
+	Oid			nspoid; 		/* OID of schema */
+	Oid			modowner;		/* OID of owner */
+	const char *rolname;		/* name of owner */
+} ModuleInfo;
+
 typedef struct _extensionInfo
 {
 	DumpableObject dobj;
@@ -681,6 +693,7 @@ extern FuncInfo *findFuncByOid(Oid oid);
 extern OprInfo *findOprByOid(Oid oid);
 extern CollInfo *findCollationByOid(Oid oid);
 extern NamespaceInfo *findNamespaceByOid(Oid oid);
+extern ModuleInfo *findModuleByOid(Oid oid);
 extern ExtensionInfo *findExtensionByOid(Oid oid);
 extern PublicationInfo *findPublicationByOid(Oid oid);
 
@@ -697,6 +710,7 @@ extern void sortDumpableObjectsByTypeName(DumpableObject **objs, int numObjs);
  * version specific routines
  */
 extern NamespaceInfo *getNamespaces(Archive *fout, int *numNamespaces);
+extern ModuleInfo *getModules(Archive *fout, int *numModules);
 extern ExtensionInfo *getExtensions(Archive *fout, int *numExtensions);
 extern TypeInfo *getTypes(Archive *fout, int *numTypes);
 extern FuncInfo *getFuncs(Archive *fout, int *numFuncs);
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index d979f93b3d..0a10a6a18b 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -1277,6 +1277,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
 					 "SCHEMA %s  (ID %d OID %u)",
 					 obj->name, obj->dumpId, obj->catId.oid);
 			return;
+		case DO_MODULE:
+			snprintf(buf, bufsize,
+					 "MODULE %s  (ID %d OID %u)",
+					 obj->name, obj->dumpId, obj->catId.oid);
+			return;
 		case DO_EXTENSION:
 			snprintf(buf, bufsize,
 					 "EXTENSION %s  (ID %d OID %u)",
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 6487bf9c0a..576ab7d802 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,7 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	202201311
+
+#define CATALOG_VERSION_NO	202202021
 
 #endif
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 344482ec87..f47fd681bc 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -105,6 +105,7 @@ typedef enum ObjectClass
 	OCLASS_AMPROC,				/* pg_amproc */
 	OCLASS_REWRITE,				/* pg_rewrite */
 	OCLASS_TRIGGER,				/* pg_trigger */
+	OCLASS_MODULE,				/* pg_module */
 	OCLASS_SCHEMA,				/* pg_namespace */
 	OCLASS_STATISTIC_EXT,		/* pg_statistic_ext */
 	OCLASS_TSPARSER,			/* pg_ts_parser */
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index f963d82797..a405247fb6 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -136,16 +136,23 @@ extern bool TSTemplateIsVisible(Oid tmplId);
 extern Oid	get_ts_config_oid(List *names, bool missing_ok);
 extern bool TSConfigIsVisible(Oid cfgid);
 
-extern void DeconstructQualifiedName(List *names,
-									 char **nspname_p,
-									 char **objname_p);
+extern void DeconstructQualifiedNameNoModule(List *names,
+											char **nspname_p,
+											char **objname_p);
+extern void DeconstructQualifiedNameWithModule(List *names,
+											char **nspname_p,
+											char **modname_p,
+											char **objname_p);
 extern Oid	LookupNamespaceNoError(const char *nspname);
 extern Oid	LookupExplicitNamespace(const char *nspname, bool missing_ok);
 extern Oid	get_namespace_oid(const char *nspname, bool missing_ok);
+extern Oid	get_module_oid(List *names, bool missing_ok);
+extern Oid	get_module_oid_from_name(Oid nspoid, const char *modname, bool missing_ok);
 
 extern Oid	LookupCreationNamespace(const char *nspname);
 extern void CheckSetNamespace(Oid oldNspOid, Oid nspOid);
 extern Oid	QualifiedNameGetCreationNamespace(List *names, char **objname_p);
+extern Oid	QualifiedNameWithModuleGetCreationNamespace(List *names, Oid *moduleId, char **objname_p);
 extern RangeVar *makeRangeVarFromNameList(List *names);
 extern char *NameListToString(List *names);
 extern char *NameListToQuotedString(List *names);
diff --git a/src/include/catalog/pg_module.dat b/src/include/catalog/pg_module.dat
new file mode 100644
index 0000000000..414cfdf9e3
--- /dev/null
+++ b/src/include/catalog/pg_module.dat
@@ -0,0 +1,14 @@
+#----------------------------------------------------------------------
+#
+# pg_module.dat
+#    Initial contents of the pg_module system catalog.
+#
+# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/include/catalog/pg_module.dat
+#
+#----------------------------------------------------------------------
+
+[
+]
diff --git a/src/include/catalog/pg_module.h b/src/include/catalog/pg_module.h
new file mode 100644
index 0000000000..4947a4ec8f
--- /dev/null
+++ b/src/include/catalog/pg_module.h
@@ -0,0 +1,64 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_module.h
+ *	  definition of the "module" system catalog (pg_module)
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_module.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives module
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_MODULE_H
+#define PG_MODULE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_module_d.h"
+#include "utils/acl.h"
+
+/* ----------------------------------------------------------------
+ *		pg_module definition.
+ *
+ *		cpp turns this into typedef struct FormData_pg_module
+ *
+ *	modname				name of the module
+ *	nspoid				oid of the schema the module is in
+ *	modowner			oid of owner (creator) of the module
+ *	modacl				access privilege list
+ * ----------------------------------------------------------------
+ */
+CATALOG(pg_module,9829,ModuleRelationId)
+{
+	Oid			oid;			/* oid */
+
+	NameData	modname;
+	Oid			nspoid;
+	Oid			modowner BKI_DEFAULT(POSTGRES) BKI_LOOKUP(pg_authid);
+
+#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+	aclitem		modacl[1];
+#endif
+} FormData_pg_module;
+
+/* ----------------
+ *		Form_pg_module corresponds to a pointer to a tuple with
+ *		the format of pg_module relation.
+ * ----------------
+ */
+typedef FormData_pg_module *Form_pg_module;
+
+DECLARE_UNIQUE_INDEX(pg_module_modname_index, 9830, ModuleNameIndexId, on pg_module using btree(modname name_ops, nspoid oid_ops));
+DECLARE_UNIQUE_INDEX_PKEY(pg_module_oid_index, 9831, ModuleOidIndexId, on pg_module using btree(oid oid_ops));
+
+/*
+ * prototypes for functions in pg_module.c
+ */
+extern Oid	ModuleCreate(const char *modName, const char *nspName, Oid ownerId);
+
+#endif							/* PG_MODULE_H */
diff --git a/src/include/catalog/pg_namespace.dat b/src/include/catalog/pg_namespace.dat
index 9f1d27cd2a..67645f0060 100644
--- a/src/include/catalog/pg_namespace.dat
+++ b/src/include/catalog/pg_namespace.dat
@@ -18,7 +18,7 @@
 { oid => '99', oid_symbol => 'PG_TOAST_NAMESPACE',
   descr => 'reserved schema for TOAST tables',
   nspname => 'pg_toast', nspacl => '_null_' },
-# update dumpNamespace() if changing this descr
+ # update dumpNamespace() if changing this descr
 { oid => '2200', oid_symbol => 'PG_PUBLIC_NAMESPACE',
   descr => 'standard public schema',
   nspname => 'public', nspowner => 'pg_database_owner', nspacl => '_null_' },
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 76310d4cc9..d73f06f417 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -37,6 +37,9 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,Proce
 	/* OID of namespace containing this proc */
 	Oid			pronamespace BKI_DEFAULT(pg_catalog) BKI_LOOKUP(pg_namespace);
 
+	/* OID of module containing this proc, or 0 if none */
+	Oid			promodule BKI_DEFAULT(0);
+
 	/* procedure owner */
 	Oid			proowner BKI_DEFAULT(POSTGRES) BKI_LOOKUP(pg_authid);
 
@@ -138,7 +141,7 @@ typedef FormData_pg_proc *Form_pg_proc;
 DECLARE_TOAST(pg_proc, 2836, 2837);
 
 DECLARE_UNIQUE_INDEX_PKEY(pg_proc_oid_index, 2690, ProcedureOidIndexId, on pg_proc using btree(oid oid_ops));
-DECLARE_UNIQUE_INDEX(pg_proc_proname_args_nsp_index, 2691, ProcedureNameArgsNspIndexId, on pg_proc using btree(proname name_ops, proargtypes oidvector_ops, pronamespace oid_ops));
+DECLARE_UNIQUE_INDEX(pg_proc_proname_args_nsp_index, 2691, ProcedureNameArgsNspModIndexId, on pg_proc using btree(proname name_ops, proargtypes oidvector_ops, pronamespace oid_ops, promodule oid_ops));
 
 #ifdef EXPOSE_TO_CLIENT_CODE
 
@@ -187,6 +190,7 @@ DECLARE_UNIQUE_INDEX(pg_proc_proname_args_nsp_index, 2691, ProcedureNameArgsNspI
 
 extern ObjectAddress ProcedureCreate(const char *procedureName,
 									 Oid procNamespace,
+									 Oid procModule,
 									 bool replace,
 									 bool returnsSet,
 									 Oid returnType,
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 56d2bb6616..97362954fc 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -50,6 +50,8 @@ extern Oid	ResolveOpClass(List *opclass, Oid attrType,
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
+extern ObjectAddress CreateFunctionInModule(ParseState *pstate, CreateFunctionStmt *stmt,
+									Oid namespaceId, Oid moduleId);
 extern void RemoveFunctionById(Oid funcOid);
 extern ObjectAddress AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt);
 extern ObjectAddress CreateCast(CreateCastStmt *stmt);
@@ -74,6 +76,9 @@ extern void interpret_function_parameter_list(ParseState *pstate,
 											  Oid *variadicArgType,
 											  Oid *requiredResultType);
 
+/* commands/modulecmds.c */
+extern ObjectAddress AlterModuleAlterFunction(ParseState *pstate, AlterModuleAlterFuncStmt *stmt);
+
 /* commands/operatorcmds.c */
 extern ObjectAddress DefineOperator(List *names, List *parameters);
 extern void RemoveOperatorById(Oid operOid);
diff --git a/src/include/commands/modulecmds.h b/src/include/commands/modulecmds.h
new file mode 100644
index 0000000000..48d800e50c
--- /dev/null
+++ b/src/include/commands/modulecmds.h
@@ -0,0 +1,29 @@
+/*-------------------------------------------------------------------------
+ *
+ * modulecmds.h
+ *	  prototypes for modulecmds.c.
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/modulecmds.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef MODULECMDS_H
+#define MODULECMDS_H
+
+#include "catalog/objectaddress.h"
+#include "nodes/parsenodes.h"
+
+extern ObjectAddress	CreateModuleCommand(CreateModuleStmt *parsetree,
+											const char *queryString,
+											int stmt_location, int stmt_len);
+extern ObjectAddress AlterModuleRename(AlterModuleRenameStmt *stmt);
+extern ObjectAddress AlterModuleOwner(AlterModuleOwnerStmt *stmt);
+extern ObjectAddress AlterModuleCreateReplaceFunction(AlterModuleCreateReplaceFuncStmt *smt,
+											 const char *queryString,
+											 int stmt_location, int stmt_len);
+#endif							/* MODULECMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index da35f2c272..7f0e64f5df 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -383,6 +383,8 @@ typedef enum NodeTag
 	T_DeclareCursorStmt,
 	T_CreateTableSpaceStmt,
 	T_DropTableSpaceStmt,
+	T_AlterModuleOwnerStmt,
+	T_AlterModuleRenameStmt,
 	T_AlterObjectDependsStmt,
 	T_AlterObjectSchemaStmt,
 	T_AlterOwnerStmt,
@@ -429,6 +431,9 @@ typedef enum NodeTag
 	T_AlterCollationStmt,
 	T_CallStmt,
 	T_AlterStatsStmt,
+	T_CreateModuleStmt,
+	T_AlterModuleAlterFuncStmt,
+	T_AlterModuleCreateReplaceFuncStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3e9bdc781f..72c3df2d16 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1811,6 +1811,7 @@ typedef enum ObjectType
 	OBJECT_LANGUAGE,
 	OBJECT_LARGEOBJECT,
 	OBJECT_MATVIEW,
+	OBJECT_MODULE,
 	OBJECT_OPCLASS,
 	OBJECT_OPERATOR,
 	OBJECT_OPFAMILY,
@@ -2019,6 +2020,7 @@ typedef enum GrantTargetType
 {
 	ACL_TARGET_OBJECT,			/* grant on specific named object(s) */
 	ACL_TARGET_ALL_IN_SCHEMA,	/* grant on all objects in given schema(s) */
+	ACL_TARGET_ALL_IN_MODULE,	/* grant on all objects in given module */
 	ACL_TARGET_DEFAULTS			/* ALTER DEFAULT PRIVILEGES */
 } GrantTargetType;
 
@@ -3004,6 +3006,40 @@ typedef struct AlterFunctionStmt
 	List	   *actions;		/* list of DefElem */
 } AlterFunctionStmt;
 
+typedef enum AlterModuleAlterFuncMode
+{
+	ALTER_MODULE_ALTER_FUNCTION,
+	ALTER_MODULE_ALTER_PROCEDURE,
+	ALTER_MODULE_ALTER_ROUTINE
+} AlterModuleAlterFuncMode;
+
+typedef struct AlterModuleAlterFuncStmt
+{
+	NodeTag		type;
+	ObjectType	objtype;
+	List *modulename;
+	AlterModuleAlterFuncMode altermodulemode;
+	AlterFunctionStmt *alterfuncstmt;
+} AlterModuleAlterFuncStmt;
+
+/* ----------------------
+ *		Create Module Statement
+ *
+ * NOTE: the moduleElts list contains raw parsetrees for component statements
+ * of the module, such as CREATE FUNCTION, CREATE PROCEDURE, etc.  These are
+ * analyzed and executed after the module itself is created.
+ * ----------------------
+ */
+typedef struct CreateModuleStmt
+{
+	Node	   Tagtype;
+	List	   *modulename;		/* the name of the module to create */
+	RoleSpec   *authrole;		/* the owner of the created module */
+	List	   *moduleElts;		/* module components (list of parsenodes) */
+	bool	   if_not_exists;	/* just do nothing if module already exists? */
+} CreateModuleStmt;
+
+
 /* ----------------------
  *		DO Statement
  *
@@ -3049,7 +3085,7 @@ typedef struct CallContext
 } CallContext;
 
 /* ----------------------
- *		Alter Object Rename Statement
+ *		ALTER MODULE RENAME Statement
  * ----------------------
  */
 typedef struct RenameStmt
@@ -3066,6 +3102,30 @@ typedef struct RenameStmt
 	bool		missing_ok;		/* skip error if missing? */
 } RenameStmt;
 
+/* ----------------------
+ *		ALTER MODULE RENAME Statement
+ * ----------------------
+ */
+typedef struct AlterModuleRenameStmt
+{
+	NodeTag	type;
+	List *modulename;
+	char *newname;
+	bool missing_ok;	/* skip error if missing */
+} AlterModuleRenameStmt;
+
+/* ----------------------
+ *		ALTER MODULE CREATE [OR REPLACE] FUNCTION|PROCEDURE Statement
+ * ----------------------
+ */
+typedef struct AlterModuleCreateReplaceFuncStmt
+{
+	NodeTag	type;
+	List *modulename;
+	Node *createreplacefunction;
+	bool missing_ok;	/* skip error if missing */
+} AlterModuleCreateReplaceFuncStmt;
+
 /* ----------------------
  * ALTER object DEPENDS ON EXTENSION extname
  * ----------------------
@@ -3107,6 +3167,17 @@ typedef struct AlterOwnerStmt
 	RoleSpec   *newowner;		/* the new owner */
 } AlterOwnerStmt;
 
+/* ----------------------
+ *		Alter Module Owner Statement
+ * ----------------------
+ */
+typedef struct AlterModuleOwnerStmt
+{
+	NodeTag	type;
+	List *modulename;
+	RoleSpec *newowner;		/* the new owner */
+} AlterModuleOwnerStmt;
+
 /* ----------------------
  *		Alter Operator Set ( this-n-that )
  * ----------------------
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index bcef7eed2f..dd26ef9b02 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -257,6 +257,7 @@ PG_KEYWORD("method", METHOD, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("mode", MODE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("module", MODULE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("month", MONTH_P, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("move", MOVE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("name", NAME_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4ab07bbac4..a84085fb84 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -31,6 +31,7 @@ extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
 extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
+extern List *transformCreateModuleStmt(CreateModuleStmt *stmt);
 extern PartitionBoundSpec *transformPartitionBound(ParseState *pstate, Relation parent,
 												   PartitionBoundSpec *spec);
 extern List *expandTableLikeClause(RangeVar *heapRel,
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index 4bc7ddf410..226e8fed53 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -43,6 +43,10 @@ PG_CMDTAG(CMDTAG_ALTER_INDEX, "ALTER INDEX", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_LANGUAGE, "ALTER LANGUAGE", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_LARGE_OBJECT, "ALTER LARGE OBJECT", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_MATERIALIZED_VIEW, "ALTER MATERIALIZED VIEW", true, false, false)
+PG_CMDTAG(CMDTAG_ALTER_MODULE_RENAME, "ALTER MODULE RENAME", true, false, false)
+PG_CMDTAG(CMDTAG_ALTER_MODULE_OWNER, "ALTER MODULE OWNER", true, false, false)
+PG_CMDTAG(CMDTAG_ALTER_MODULE_CREATE_REPLACE_FUNC, "ALTER MODULE CREATE REPLACE FUNCTION", true, false, false)
+PG_CMDTAG(CMDTAG_ALTER_MODULE_ALTER_FUNCTION, "ALTER MODULE ALTER FUNCTION", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_OPERATOR, "ALTER OPERATOR", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_OPERATOR_CLASS, "ALTER OPERATOR CLASS", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_OPERATOR_FAMILY, "ALTER OPERATOR FAMILY", true, false, false)
@@ -98,6 +102,7 @@ PG_CMDTAG(CMDTAG_CREATE_FUNCTION, "CREATE FUNCTION", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_INDEX, "CREATE INDEX", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_LANGUAGE, "CREATE LANGUAGE", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_MATERIALIZED_VIEW, "CREATE MATERIALIZED VIEW", true, false, false)
+PG_CMDTAG(CMDTAG_CREATE_MODULE, "CREATE MODULE", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_OPERATOR, "CREATE OPERATOR", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_OPERATOR_CLASS, "CREATE OPERATOR CLASS", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_OPERATOR_FAMILY, "CREATE OPERATOR FAMILY", true, false, false)
@@ -150,6 +155,7 @@ PG_CMDTAG(CMDTAG_DROP_FUNCTION, "DROP FUNCTION", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_INDEX, "DROP INDEX", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_LANGUAGE, "DROP LANGUAGE", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_MATERIALIZED_VIEW, "DROP MATERIALIZED VIEW", true, false, false)
+PG_CMDTAG(CMDTAG_DROP_MODULE, "DROP MODULE", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_OPERATOR, "DROP OPERATOR", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_OPERATOR_CLASS, "DROP OPERATOR CLASS", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_OPERATOR_FAMILY, "DROP OPERATOR FAMILY", true, false, false)
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index f9daf5b744..ed15864c34 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -82,11 +82,18 @@ extern void ProcessUtility(PlannedStmt *pstmt, const char *queryString,
 						   ProcessUtilityContext context, ParamListInfo params,
 						   QueryEnvironment *queryEnv,
 						   DestReceiver *dest, QueryCompletion *qc);
+extern void ProcessUtilityUsingModule(PlannedStmt *pstmt, const char *queryString,
+									  bool readOnlyTree,
+									  ProcessUtilityContext context, ParamListInfo params,
+									  QueryEnvironment *queryEnv,
+									  DestReceiver *dest, QueryCompletion *qc,
+									  Oid namespaceId, Oid moduleId);
 extern void standard_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
 									bool readOnlyTree,
 									ProcessUtilityContext context, ParamListInfo params,
 									QueryEnvironment *queryEnv,
-									DestReceiver *dest, QueryCompletion *qc);
+									DestReceiver *dest, QueryCompletion *qc,
+									Oid namespaceId, Oid moduleId);
 
 extern void ProcessUtilityForAlterTable(Node *stmt,
 										AlterTableUtilityContext *context);
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 1ce4c5556e..9e3f248f98 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -162,6 +162,7 @@ typedef struct ArrayType Acl;
 #define ACL_ALL_RIGHTS_FUNCTION		(ACL_EXECUTE)
 #define ACL_ALL_RIGHTS_LANGUAGE		(ACL_USAGE)
 #define ACL_ALL_RIGHTS_LARGEOBJECT	(ACL_SELECT|ACL_UPDATE)
+#define ACL_ALL_RIGHTS_MODULE		(ACL_EXECUTE|ACL_CREATE)
 #define ACL_ALL_RIGHTS_SCHEMA		(ACL_USAGE|ACL_CREATE)
 #define ACL_ALL_RIGHTS_TABLESPACE	(ACL_CREATE)
 #define ACL_ALL_RIGHTS_TYPE			(ACL_USAGE)
@@ -251,6 +252,8 @@ extern AclMode pg_largeobject_aclmask_snapshot(Oid lobj_oid, Oid roleid,
 											   AclMode mask, AclMaskHow how, Snapshot snapshot);
 extern AclMode pg_namespace_aclmask(Oid nsp_oid, Oid roleid,
 									AclMode mask, AclMaskHow how);
+extern AclMode pg_module_aclmask(Oid mod_oid, Oid roleid,
+									AclMode mask, AclMaskHow how);
 extern AclMode pg_tablespace_aclmask(Oid spc_oid, Oid roleid,
 									 AclMode mask, AclMaskHow how);
 extern AclMode pg_foreign_data_wrapper_aclmask(Oid fdw_oid, Oid roleid,
@@ -276,6 +279,7 @@ extern AclResult pg_language_aclcheck(Oid lang_oid, Oid roleid, AclMode mode);
 extern AclResult pg_largeobject_aclcheck_snapshot(Oid lang_oid, Oid roleid,
 												  AclMode mode, Snapshot snapshot);
 extern AclResult pg_namespace_aclcheck(Oid nsp_oid, Oid roleid, AclMode mode);
+extern AclResult pg_module_aclcheck(Oid mod_oid, Oid roleid, AclMode mode);
 extern AclResult pg_tablespace_aclcheck(Oid spc_oid, Oid roleid, AclMode mode);
 extern AclResult pg_foreign_data_wrapper_aclcheck(Oid fdw_oid, Oid roleid, AclMode mode);
 extern AclResult pg_foreign_server_aclcheck(Oid srv_oid, Oid roleid, AclMode mode);
@@ -301,6 +305,7 @@ extern bool pg_proc_ownercheck(Oid proc_oid, Oid roleid);
 extern bool pg_language_ownercheck(Oid lan_oid, Oid roleid);
 extern bool pg_largeobject_ownercheck(Oid lobj_oid, Oid roleid);
 extern bool pg_namespace_ownercheck(Oid nsp_oid, Oid roleid);
+extern bool pg_module_ownercheck(Oid mod_oid, Oid roleid);
 extern bool pg_tablespace_ownercheck(Oid spc_oid, Oid roleid);
 extern bool pg_opclass_ownercheck(Oid opc_oid, Oid roleid);
 extern bool pg_opfamily_ownercheck(Oid opf_oid, Oid roleid);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index b8dd27d4a9..47b746225c 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -188,6 +188,7 @@ extern int32 get_attavgwidth(Oid relid, AttrNumber attnum);
 extern bool get_attstatsslot(AttStatsSlot *sslot, HeapTuple statstuple,
 							 int reqkind, Oid reqop, int flags);
 extern void free_attstatsslot(AttStatsSlot *sslot);
+extern char *get_module_name(Oid modid);
 extern char *get_namespace_name(Oid nspid);
 extern char *get_namespace_name_or_temp(Oid nspid);
 extern Oid	get_range_subtype(Oid rangeOid);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 9c1a76e8bb..04f72dcd45 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -66,6 +66,8 @@ enum SysCacheIdentifier
 	INDEXRELID,
 	LANGNAME,
 	LANGOID,
+	MODULENAME,
+	MODULEOID,
 	NAMESPACENAME,
 	NAMESPACEOID,
 	OPERNAMENSP,
diff --git a/src/test/regress/expected/create_module.out b/src/test/regress/expected/create_module.out
new file mode 100644
index 0000000000..981210c0df
--- /dev/null
+++ b/src/test/regress/expected/create_module.out
@@ -0,0 +1,202 @@
+CREATE TABLE cm_test (a int, b text);
+CREATE MODULE mtest1
+  CREATE FUNCTION m1testa() RETURNS text
+     LANGUAGE sql
+     RETURN '1x'
+  CREATE FUNCTION m1testb() RETURNS text
+     LANGUAGE sql
+     RETURN '1y';
+CREATE SCHEMA temp_mod_test;
+GRANT ALL ON SCHEMA temp_mod_test TO public;
+CREATE MODULE temp_mod_test.mtest2
+  CREATE PROCEDURE m2testa(x text)
+  LANGUAGE SQL
+  AS $$
+  INSERT INTO cm_test VALUES (1, x);
+  $$
+  CREATE FUNCTION m2testb() RETURNS text
+     LANGUAGE sql
+     RETURN '2y';
+CREATE MODULE mtest3
+  CREATE FUNCTION m3testa() RETURNS text
+     LANGUAGE sql
+     RETURN '3x';
+SELECT mtest1.m1testa();
+ m1testa 
+---------
+ 1x
+(1 row)
+
+SELECT mtest1.m1testb();
+ m1testb 
+---------
+ 1y
+(1 row)
+
+SELECT public.mtest1.m1testa();
+ m1testa 
+---------
+ 1x
+(1 row)
+
+SELECT public.mtest1.m1testb();
+ m1testb 
+---------
+ 1y
+(1 row)
+
+SELECT temp_mod_test.mtest2.m2testb();
+ m2testb 
+---------
+ 2y
+(1 row)
+
+SELECT temp_mod_test.mtest2.m2testa('x');  -- error
+ERROR:  temp_mod_test.mtest2.m2testa(unknown) is a procedure
+LINE 1: SELECT temp_mod_test.mtest2.m2testa('x');
+               ^
+HINT:  To call a procedure, use CALL.
+CALL temp_mod_test.mtest2.m2testa('a');  -- ok
+CALL temp_mod_test.mtest2.m2testa('xy' || 'zzy');  -- ok, constant-folded arg
+ALTER MODULE mtest1 CREATE PROCEDURE m1testc(x text)
+  LANGUAGE SQL
+  AS $$
+  INSERT INTO cm_test VALUES (2, x);
+  $$;
+CALL mtest1.m1testc('a');  -- ok
+-- create and modify functions in modules
+ALTER MODULE mtest1 CREATE FUNCTION m1testd() RETURNS text
+     LANGUAGE sql
+     RETURN 'm1testd';
+SELECT mtest1.m1testd();
+ m1testd 
+---------
+ m1testd
+(1 row)
+
+ALTER MODULE mtest1 CREATE OR REPLACE FUNCTION m1testd() RETURNS text
+     LANGUAGE sql
+     RETURN 'm1testd replaced';
+SELECT mtest1.m1testd();
+     m1testd      
+------------------
+ m1testd replaced
+(1 row)
+
+-- grant and revoke
+DROP ROLE IF EXISTS regress_priv_user1;
+NOTICE:  role "regress_priv_user1" does not exist, skipping
+CREATE USER regress_priv_user1;
+ALTER ROLE regress_priv_user1 NOINHERIT;
+GRANT CREATE ON SCHEMA public TO regress_priv_user1;
+REVOKE ON MODULE mtest1 REFERENCES ON FUNCTION m1testa() FROM public;
+REVOKE ON MODULE mtest1 REFERENCES ON FUNCTION m1testb() FROM public;
+GRANT ON MODULE mtest1 REFERENCES ON FUNCTION m1testa() TO regress_priv_user1;
+REVOKE ON MODULE mtest1 REFERENCES ON FUNCTION m1testb() FROM regress_priv_user1;
+SET SESSION AUTHORIZATION regress_priv_user1;
+SELECT mtest1.m1testa();  -- ok
+ m1testa 
+---------
+ 1x
+(1 row)
+
+SELECT mtest1.m1testb();  -- error
+ERROR:  permission denied for function m1testb
+RESET SESSION AUTHORIZATION;
+REVOKE ON MODULE mtest1 REFERENCES ON FUNCTION m1testa() FROM regress_priv_user1;
+GRANT ON MODULE mtest1 REFERENCES ON FUNCTION m1testb() TO regress_priv_user1;
+SET SESSION AUTHORIZATION regress_priv_user1;
+SELECT mtest1.m1testa();  -- error
+ERROR:  permission denied for function m1testa
+SELECT mtest1.m1testb();  -- ok
+ m1testb 
+---------
+ 1y
+(1 row)
+
+RESET SESSION AUTHORIZATION;
+REVOKE ON MODULE mtest1 REFERENCES ON ALL FUNCTIONS FROM regress_priv_user1;
+REVOKE ON MODULE mtest1 CREATE FROM regress_priv_user1;
+SET SESSION AUTHORIZATION regress_priv_user1;
+SELECT mtest1.m1testa(); -- error
+ERROR:  permission denied for function m1testa
+SELECT mtest1.m1testb(); -- error
+ERROR:  permission denied for function m1testb
+ALTER MODULE mtest1 CREATE OR REPLACE FUNCTION m1testf() RETURNS text
+     LANGUAGE sql
+     RETURN 'm1testf'; -- error
+ERROR:  permission denied for module mtest1
+RESET SESSION AUTHORIZATION;
+GRANT ON MODULE mtest1 REFERENCES ON ALL FUNCTIONS TO regress_priv_user1;
+GRANT ON MODULE mtest1 CREATE TO regress_priv_user1;
+SET SESSION AUTHORIZATION regress_priv_user1;
+SELECT mtest1.m1testa();  -- ok
+ m1testa 
+---------
+ 1x
+(1 row)
+
+SELECT mtest1.m1testb();  -- ok
+ m1testb 
+---------
+ 1y
+(1 row)
+
+ALTER MODULE mtest1 CREATE OR REPLACE FUNCTION m1testf() RETURNS text
+     LANGUAGE sql
+     RETURN 'm1testf';  -- ok
+RESET SESSION AUTHORIZATION;
+SELECT mtest1.m1testf();  -- ok
+ m1testf 
+---------
+ m1testf
+(1 row)
+
+-- rename module and functions in module
+ALTER MODULE mtest1 RENAME TO mtest1renamed;
+SELECT mtest1.m1testd();  -- error
+ERROR:  function mtest1.m1testd() does not exist
+LINE 1: SELECT mtest1.m1testd();
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+SELECT mtest1renamed.m1testd();  -- ok
+     m1testd      
+------------------
+ m1testd replaced
+(1 row)
+
+ALTER FUNCTION mtest1renamed.m1testd() RENAME TO m1testdrenamed;
+SELECT mtest1renamed.m1testd();  -- error
+ERROR:  function mtest1renamed.m1testd() does not exist
+LINE 1: SELECT mtest1renamed.m1testd();
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+SELECT mtest1renamed.m1testdrenamed();  -- ok
+  m1testdrenamed  
+------------------
+ m1testd replaced
+(1 row)
+
+-- drop
+DROP PROCEDURE mtest1renamed.m1testc(text);
+DROP FUNCTION temp_mod_test.mtest2.m2testb();
+DROP MODULE mtest1renamed; -- error
+ERROR:  cannot drop module mtest1renamed because other objects depend on it
+DETAIL:  function public.m1testa() depends on module mtest1renamed
+function public.m1testb() depends on module mtest1renamed
+function public.m1testdrenamed() depends on module mtest1renamed
+function public.m1testf() depends on module mtest1renamed
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- cleanup
+DROP MODULE mtest1renamed CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to function public.m1testa()
+drop cascades to function public.m1testb()
+drop cascades to function public.m1testdrenamed()
+drop cascades to function public.m1testf()
+DROP MODULE temp_mod_test.mtest2 CASCADE;
+NOTICE:  drop cascades to function temp_mod_test.m2testa(text)
+DROP OWNED BY regress_priv_user1;
+DROP ROLE regress_priv_user1;
+DROP SCHEMA temp_mod_test;
+DROP TABLE cm_test;
diff --git a/src/test/regress/expected/misc_sanity.out b/src/test/regress/expected/misc_sanity.out
index a57fd142a9..685f7db692 100644
--- a/src/test/regress/expected/misc_sanity.out
+++ b/src/test/regress/expected/misc_sanity.out
@@ -60,7 +60,8 @@ ORDER BY 1, 2;
  pg_index                | indpred       | pg_node_tree
  pg_largeobject          | data          | bytea
  pg_largeobject_metadata | lomacl        | aclitem[]
-(11 rows)
+ pg_module               | modacl        | aclitem[]
+(12 rows)
 
 -- system catalogs without primary keys
 --
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 215eb899be..5cc2337a5a 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -183,6 +183,7 @@ NOTICE:  checking pg_cast {castsource} => pg_type {oid}
 NOTICE:  checking pg_cast {casttarget} => pg_type {oid}
 NOTICE:  checking pg_cast {castfunc} => pg_proc {oid}
 NOTICE:  checking pg_enum {enumtypid} => pg_type {oid}
+NOTICE:  checking pg_module {modowner} => pg_authid {oid}
 NOTICE:  checking pg_namespace {nspowner} => pg_authid {oid}
 NOTICE:  checking pg_conversion {connamespace} => pg_namespace {oid}
 NOTICE:  checking pg_conversion {conowner} => pg_authid {oid}
diff --git a/src/test/regress/expected/regproc.out b/src/test/regress/expected/regproc.out
index e45ff5483f..6bf01fef50 100644
--- a/src/test/regress/expected/regproc.out
+++ b/src/test/regress/expected/regproc.out
@@ -253,11 +253,11 @@ ERROR:  operator does not exist: ng_catalog.+(int4,int4)
 LINE 1: SELECT regoperator('ng_catalog.+(int4,int4)');
                            ^
 SELECT regproc('ng_catalog.now');
-ERROR:  schema "ng_catalog" does not exist
+ERROR:  function "ng_catalog.now" does not exist
 LINE 1: SELECT regproc('ng_catalog.now');
                        ^
 SELECT regprocedure('ng_catalog.abs(numeric)');
-ERROR:  schema "ng_catalog" does not exist
+ERROR:  function "ng_catalog.abs(numeric)" does not exist
 LINE 1: SELECT regprocedure('ng_catalog.abs(numeric)');
                             ^
 SELECT regclass('ng_catalog.pg_class');
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 63706a28cc..809d3a52e4 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -132,6 +132,7 @@ pg_init_privs|t
 pg_language|t
 pg_largeobject|t
 pg_largeobject_metadata|t
+pg_module|t
 pg_namespace|t
 pg_opclass|t
 pg_operator|t
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 861c30a73a..c55c4232dd 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -54,7 +54,7 @@ test: copy copyselect copydml insert insert_conflict
 # ----------
 # More groups of parallel tests
 # ----------
-test: create_misc create_operator create_procedure
+test: create_misc create_operator create_procedure create_module
 # These depend on create_misc and create_operator
 test: create_index create_index_spgist create_view index_including index_including_gist
 
diff --git a/src/test/regress/sql/create_module.sql b/src/test/regress/sql/create_module.sql
new file mode 100644
index 0000000000..744715d235
--- /dev/null
+++ b/src/test/regress/sql/create_module.sql
@@ -0,0 +1,132 @@
+CREATE TABLE cm_test (a int, b text);
+
+CREATE MODULE mtest1
+  CREATE FUNCTION m1testa() RETURNS text
+     LANGUAGE sql
+     RETURN '1x'
+  CREATE FUNCTION m1testb() RETURNS text
+     LANGUAGE sql
+     RETURN '1y';
+
+CREATE SCHEMA temp_mod_test;
+GRANT ALL ON SCHEMA temp_mod_test TO public;
+
+CREATE MODULE temp_mod_test.mtest2
+  CREATE PROCEDURE m2testa(x text)
+  LANGUAGE SQL
+  AS $$
+  INSERT INTO cm_test VALUES (1, x);
+  $$
+  CREATE FUNCTION m2testb() RETURNS text
+     LANGUAGE sql
+     RETURN '2y';
+
+CREATE MODULE mtest3
+  CREATE FUNCTION m3testa() RETURNS text
+     LANGUAGE sql
+     RETURN '3x';
+
+SELECT mtest1.m1testa();
+SELECT mtest1.m1testb();
+
+SELECT public.mtest1.m1testa();
+SELECT public.mtest1.m1testb();
+
+SELECT temp_mod_test.mtest2.m2testb();
+
+SELECT temp_mod_test.mtest2.m2testa('x');  -- error
+CALL temp_mod_test.mtest2.m2testa('a');  -- ok
+CALL temp_mod_test.mtest2.m2testa('xy' || 'zzy');  -- ok, constant-folded arg
+
+ALTER MODULE mtest1 CREATE PROCEDURE m1testc(x text)
+  LANGUAGE SQL
+  AS $$
+  INSERT INTO cm_test VALUES (2, x);
+  $$;
+
+CALL mtest1.m1testc('a');  -- ok
+
+-- create and modify functions in modules
+
+ALTER MODULE mtest1 CREATE FUNCTION m1testd() RETURNS text
+     LANGUAGE sql
+     RETURN 'm1testd';
+
+SELECT mtest1.m1testd();
+
+ALTER MODULE mtest1 CREATE OR REPLACE FUNCTION m1testd() RETURNS text
+     LANGUAGE sql
+     RETURN 'm1testd replaced';
+
+SELECT mtest1.m1testd();
+
+-- grant and revoke
+
+DROP ROLE IF EXISTS regress_priv_user1;
+CREATE USER regress_priv_user1;
+ALTER ROLE regress_priv_user1 NOINHERIT;
+GRANT CREATE ON SCHEMA public TO regress_priv_user1;
+
+REVOKE ON MODULE mtest1 REFERENCES ON FUNCTION m1testa() FROM public;
+REVOKE ON MODULE mtest1 REFERENCES ON FUNCTION m1testb() FROM public;
+
+GRANT ON MODULE mtest1 REFERENCES ON FUNCTION m1testa() TO regress_priv_user1;
+REVOKE ON MODULE mtest1 REFERENCES ON FUNCTION m1testb() FROM regress_priv_user1;
+
+SET SESSION AUTHORIZATION regress_priv_user1;
+SELECT mtest1.m1testa();  -- ok
+SELECT mtest1.m1testb();  -- error
+RESET SESSION AUTHORIZATION;
+
+REVOKE ON MODULE mtest1 REFERENCES ON FUNCTION m1testa() FROM regress_priv_user1;
+GRANT ON MODULE mtest1 REFERENCES ON FUNCTION m1testb() TO regress_priv_user1;
+SET SESSION AUTHORIZATION regress_priv_user1;
+SELECT mtest1.m1testa();  -- error
+SELECT mtest1.m1testb();  -- ok
+RESET SESSION AUTHORIZATION;
+
+REVOKE ON MODULE mtest1 REFERENCES ON ALL FUNCTIONS FROM regress_priv_user1;
+REVOKE ON MODULE mtest1 CREATE FROM regress_priv_user1;
+SET SESSION AUTHORIZATION regress_priv_user1;
+SELECT mtest1.m1testa(); -- error
+SELECT mtest1.m1testb(); -- error
+ALTER MODULE mtest1 CREATE OR REPLACE FUNCTION m1testf() RETURNS text
+     LANGUAGE sql
+     RETURN 'm1testf'; -- error
+RESET SESSION AUTHORIZATION;
+
+GRANT ON MODULE mtest1 REFERENCES ON ALL FUNCTIONS TO regress_priv_user1;
+GRANT ON MODULE mtest1 CREATE TO regress_priv_user1;
+SET SESSION AUTHORIZATION regress_priv_user1;
+SELECT mtest1.m1testa();  -- ok
+SELECT mtest1.m1testb();  -- ok
+ALTER MODULE mtest1 CREATE OR REPLACE FUNCTION m1testf() RETURNS text
+     LANGUAGE sql
+     RETURN 'm1testf';  -- ok
+RESET SESSION AUTHORIZATION;
+SELECT mtest1.m1testf();  -- ok
+
+-- rename module and functions in module
+ALTER MODULE mtest1 RENAME TO mtest1renamed;
+
+SELECT mtest1.m1testd();  -- error
+SELECT mtest1renamed.m1testd();  -- ok
+
+ALTER FUNCTION mtest1renamed.m1testd() RENAME TO m1testdrenamed;
+
+SELECT mtest1renamed.m1testd();  -- error
+SELECT mtest1renamed.m1testdrenamed();  -- ok
+
+-- drop
+DROP PROCEDURE mtest1renamed.m1testc(text);
+DROP FUNCTION temp_mod_test.mtest2.m2testb();
+DROP MODULE mtest1renamed; -- error
+
+-- cleanup
+DROP MODULE mtest1renamed CASCADE;
+DROP MODULE temp_mod_test.mtest2 CASCADE;
+DROP OWNED BY regress_priv_user1;
+DROP ROLE regress_priv_user1;
+
+DROP SCHEMA temp_mod_test;
+DROP TABLE cm_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 89249ecc97..7bba658389 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -74,6 +74,7 @@ AlterExtensionStmt
 AlterFdwStmt
 AlterForeignServerStmt
 AlterFunctionStmt
+AlterModuleAlterFuncStmt
 AlterObjectDependsStmt
 AlterObjectSchemaStmt
 AlterOpFamilyStmt
@@ -1461,6 +1462,8 @@ MinmaxOpaque
 ModifyTable
 ModifyTablePath
 ModifyTableState
+ModuleGrantStmt
+ModuleRevokeStmt
 MorphOpaque
 MsgType
 MultiAssignRef
@@ -1490,6 +1493,7 @@ NamedLWLockTrancheRequest
 NamedTuplestoreScan
 NamedTuplestoreScanState
 NamespaceInfo
+ModuleInfo
 NestLoop
 NestLoopParam
 NestLoopState