Support for CREATE MODULE?

Started by Jim Mlodgenskiover 4 years ago11 messages
#1Jim Mlodgenski
jimmy76@gmail.com
4 attachment(s)

Working with users over the years, some have large libraries of server
side code sometimes consisting of 100k+ lines of code over 1000+ functions
and procedures. This usually comes from a migration of a commercial
database like Oracle where it was best practice to put all of your
business logic into stored procedures. In these types of apps, just
managing the code is a challenge. To help classify objects, schemas
are used, but you are at the mercy of a naming convention to show
association. For example, a frequent naming convention would be having
related schemas with the names of foo_bar and foo_baz. For devs, that's
akin to keeping a file like xlog.c in a directory structure like
backend_access_transam instead of backend/access/transam. IMHO, having
a hierarchy makes it simpler to reason about related code bits.

The SQL spec does have a concept of modules that help address this. It's
defined as a persistent object within a schema that contains one or more
routines. It also defines other things like local temporary tables and
path specifications. There are other databases like DB2 that have
implemented module support each with their own way of defining the
routines within the module. The spec doesn't really give guidance on
how to manipulate the objects within the module.

Attached is a POC patch for modules. I modeled it as a sub-schema because
that is more what it seems like to me. It adds additional columns to
pg_namespace and allows for 3-part (or 4 with the database name) naming
of objects within the module. This simple example works with the patch.

CREATE SCHEMA foo;
CREATE MODULE foo.bar
CREATE FUNCTION hello() RETURNS text
LANGUAGE sql
RETURN 'hello'
CREATE FUNCTION world() RETURNS text
LANGUAGE sql
RETURN 'world';
SELECT foo.bar.hello();

Questions
- Do we want to add module support?

- If we do, should it be implemented as a type of namespace or should it
be its own object type that lives in something like pg_module?

- How should users interact with objects within a module? They could be
mostly independent like the current POC or we can introduce a path like
ALTER MODULE foo ADD FUNCTION blah

--Jim

Attachments:

modules_poc_1.patchapplication/octet-stream; name=modules_poc_1.patchDownload
unchanged:
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -706,7 +706,7 @@ objectNamesToOids(ObjectType objtype, List *objnames)
 				char	   *nspname = strVal(lfirst(cell));
 				Oid			oid;
 
-				oid = get_namespace_oid(nspname, false);
+				oid = get_namespace_oid(nspname, InvalidOid, false);
 				objects = lappend_oid(objects, oid);
 			}
 			break;
@@ -1113,7 +1113,7 @@ SetDefaultACLsInSchemas(InternalDefaultACL *iacls, List *nspnames)
 		{
 			char	   *nspname = strVal(lfirst(nspcell));
 
-			iacls->nspid = get_namespace_oid(nspname, false);
+			iacls->nspid = get_namespace_oid(nspname, InvalidOid, false);
 
 			/*
 			 * We used to insist that the target role have CREATE privileges
@@ -3372,6 +3372,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;
@@ -3500,6 +3503,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;
unchanged:
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -473,7 +473,7 @@ RangeVarGetCreationNamespace(const RangeVar *newRelation)
 			return myTempNamespace;
 		}
 		/* use exact schema given */
-		namespaceId = get_namespace_oid(newRelation->schemaname, false);
+		namespaceId = get_namespace_oid(newRelation->schemaname, InvalidOid, false);
 		/* we do not check for USAGE rights here! */
 	}
 	else if (newRelation->relpersistence == RELPERSISTENCE_TEMP)
@@ -945,9 +945,11 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
 {
 	FuncCandidateList resultList = NULL;
 	bool		any_special = false;
-	char	   *schemaname;
+	char	   *nspname;
+	char	   *modulename;
 	char	   *funcname;
 	Oid			namespaceId;
+	Oid			moduleId;
 	CatCList   *catlist;
 	int			i;
 
@@ -955,19 +957,31 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
 	Assert(nargs >= 0 || !(expand_variadic | expand_defaults));
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(names, &schemaname, &funcname);
+	DeconstructQualifiedName(names, &nspname, &modulename, &funcname, true);
 
-	if (schemaname)
+	if (nspname && modulename)
 	{
 		/* use exact schema given */
-		namespaceId = LookupExplicitNamespace(schemaname, missing_ok);
+		namespaceId = LookupExplicitNamespace(nspname, missing_ok);
 		if (!OidIsValid(namespaceId))
 			return NULL;
+
+		moduleId = get_namespace_oid(modulename, namespaceId, true);
+		if (!OidIsValid(moduleId))
+			return NULL;
+	}
+	else if (nspname)
+	{
+		moduleId = InvalidOid;
+		namespaceId = LookupExplicitNamespace(nspname, true);
+		if (!OidIsValid(namespaceId))
+			recomputeNamespacePath();
 	}
 	else
 	{
 		/* flag to indicate we need namespace search */
 		namespaceId = InvalidOid;
+		moduleId = InvalidOid;
 		recomputeNamespacePath();
 	}
 
@@ -987,9 +1001,15 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
 		int		   *argnumbers = NULL;
 		FuncCandidateList newResult;
 
-		if (OidIsValid(namespaceId))
+		if (OidIsValid(moduleId))
 		{
-			/* Consider only procs in specified namespace */
+			/* Consider only procs in specified module */
+			if (procform->pronamespace != moduleId)
+				continue;
+		}
+		else if (OidIsValid(namespaceId))
+		{
+			/* Consider only procs in specified schema */
 			if (procform->pronamespace != namespaceId)
 				continue;
 		}
@@ -1003,7 +1023,16 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
 
 			foreach(nsp, activeSearchPath)
 			{
-				if (procform->pronamespace == lfirst_oid(nsp) &&
+				/* check if there is a module in the schema */
+				if (nspname)
+				{
+					moduleId = get_namespace_oid(nspname, lfirst_oid(nsp), true);
+					if (OidIsValid(moduleId))
+						if (procform->pronamespace == moduleId &&
+							procform->pronamespace != myTempNamespace)
+							break;
+				}
+				else if (procform->pronamespace == lfirst_oid(nsp) &&
 					procform->pronamespace != myTempNamespace)
 					break;
 				pathpos++;
@@ -1449,7 +1478,6 @@ FunctionIsVisible(Oid funcid)
 
 		clist = FuncnameGetCandidates(list_make1(makeString(proname)),
 									  nargs, NIL, false, false, false);
-
 		for (; clist; clist = clist->next)
 		{
 			if (memcmp(clist->args, procform->proargtypes.values,
@@ -1488,7 +1516,7 @@ OpernameGetOprid(List *names, Oid oprleft, Oid oprright)
 	ListCell   *l;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(names, &schemaname, &opername);
+	DeconstructQualifiedName(names, &schemaname, NULL, &opername, false);
 
 	if (schemaname)
 	{
@@ -1595,7 +1623,7 @@ OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok)
 	int			i;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(names, &schemaname, &opername);
+	DeconstructQualifiedName(names, &schemaname, NULL, &opername, false);
 
 	if (schemaname)
 	{
@@ -2183,7 +2211,7 @@ get_statistics_object_oid(List *names, bool missing_ok)
 	ListCell   *l;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(names, &schemaname, &stats_name);
+	DeconstructQualifiedName(names, &schemaname, NULL, &stats_name, false);
 
 	if (schemaname)
 	{
@@ -2224,6 +2252,60 @@ get_statistics_object_oid(List *names, bool missing_ok)
 	return stats_oid;
 }
 
+/*
+ * get_module_oid - find a module by possibly qualified name
+ *
+ * If not found, returns InvalidOid if missing_ok, else throws error
+ */
+Oid
+get_module_oid(List *names, bool missing_ok)
+{
+	char	   *schemaname;
+	char	   *modulename;
+	Oid			namespaceId;
+	Oid			moduleId = InvalidOid;
+	ListCell   *l;
+
+	/* deconstruct the name list */
+	DeconstructQualifiedName(names, &schemaname, NULL, &modulename, false);
+
+	if (schemaname)
+	{
+		/* use exact schema given */
+		namespaceId = LookupExplicitNamespace(schemaname, missing_ok);
+		if (missing_ok && !OidIsValid(namespaceId))
+			moduleId = InvalidOid;
+		else
+			moduleId = GetSysCacheOid2(NAMESPACENAME, Anum_pg_namespace_oid,
+						  CStringGetDatum(modulename), ObjectIdGetDatum(namespaceId));
+	}
+	else
+	{
+		/* search for it in search path */
+		recomputeNamespacePath();
+
+		foreach(l, activeSearchPath)
+		{
+			namespaceId = lfirst_oid(l);
+
+			if (namespaceId == myTempNamespace)
+				continue;		/* do not look in temp namespace */
+			moduleId = GetSysCacheOid2(NAMESPACENAME, Anum_pg_namespace_oid,
+						  CStringGetDatum(modulename), ObjectIdGetDatum(namespaceId));
+			if (OidIsValid(moduleId))
+				break;
+		}
+	}
+
+	if (!OidIsValid(moduleId) && !missing_ok)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("module \"%s\" does not exist",
+						NameListToString(names))));
+
+	return moduleId;
+}
+
 /*
  * StatisticsObjIsVisible
  *		Determine whether a statistics object (identified by OID) is visible in
@@ -2305,7 +2387,7 @@ get_ts_parser_oid(List *names, bool missing_ok)
 	ListCell   *l;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(names, &schemaname, &parser_name);
+	DeconstructQualifiedName(names, &schemaname, NULL, &parser_name, false);
 
 	if (schemaname)
 	{
@@ -2431,7 +2513,7 @@ get_ts_dict_oid(List *names, bool missing_ok)
 	ListCell   *l;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(names, &schemaname, &dict_name);
+	DeconstructQualifiedName(names, &schemaname, NULL, &dict_name, false);
 
 	if (schemaname)
 	{
@@ -2558,7 +2640,7 @@ get_ts_template_oid(List *names, bool missing_ok)
 	ListCell   *l;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(names, &schemaname, &template_name);
+	DeconstructQualifiedName(names, &schemaname, NULL, &template_name, false);
 
 	if (schemaname)
 	{
@@ -2684,7 +2766,7 @@ get_ts_config_oid(List *names, bool missing_ok)
 	ListCell   *l;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(names, &schemaname, &config_name);
+	DeconstructQualifiedName(names, &schemaname, NULL, &config_name, false);
 
 	if (schemaname)
 	{
@@ -2800,17 +2882,21 @@ TSConfigIsVisible(Oid cfgid)
 /*
  * DeconstructQualifiedName
  *		Given a possibly-qualified name expressed as a list of String nodes,
- *		extract the schema name and object name.
+ *		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
 DeconstructQualifiedName(List *names,
 						 char **nspname_p,
-						 char **objname_p)
+						 char **modname_p,
+						 char **objname_p,
+						 bool check_module)
 {
 	char	   *catalogname;
-	char	   *schemaname = NULL;
+	char	   *nspname = NULL;
+	char	   *modulename = NULL;
 	char	   *objname = NULL;
 
 	switch (list_length(names))
@@ -2819,22 +2905,70 @@ DeconstructQualifiedName(List *names,
 			objname = strVal(linitial(names));
 			break;
 		case 2:
-			schemaname = strVal(linitial(names));
+			nspname = strVal(linitial(names));
 			objname = strVal(lsecond(names));
 			break;
 		case 3:
-			catalogname = strVal(linitial(names));
-			schemaname = strVal(lsecond(names));
-			objname = strVal(lthird(names));
+			if (check_module)
+			{
+				/*
+				 * 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)
+				{
+					nspname = strVal(linitial(names));
+					modulename = strVal(lsecond(names));
+				}
+				else
+				{
+					catalogname = strVal(linitial(names));
+					nspname = strVal(lsecond(names));
+				}
 
-			/*
-			 * We check the catalog name and then ignore it.
-			 */
-			if (strcmp(catalogname, get_database_name(MyDatabaseId)) != 0)
+				objname = strVal(lthird(names));
+			}
+			else
+			{
+				catalogname = strVal(linitial(names));
+				nspname = strVal(lsecond(names));
+				objname = strVal(lthird(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;
+		case 4:
+			if (check_module)
+			{
+				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))));
+			}
+			else
+			{
 				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("cross-database references are not implemented: %s",
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("improper qualified name (too many dotted names): %s",
 								NameListToString(names))));
+			}
 			break;
 		default:
 			ereport(ERROR,
@@ -2844,7 +2978,9 @@ DeconstructQualifiedName(List *names,
 			break;
 	}
 
-	*nspname_p = schemaname;
+	*nspname_p = nspname;
+	if (modname_p)
+		*modname_p = modulename;
 	*objname_p = objname;
 }
 
@@ -2878,7 +3014,7 @@ LookupNamespaceNoError(const char *nspname)
 		return InvalidOid;
 	}
 
-	return get_namespace_oid(nspname, true);
+	return get_namespace_oid(nspname, InvalidOid, true);
 }
 
 /*
@@ -2907,7 +3043,7 @@ LookupExplicitNamespace(const char *nspname, bool missing_ok)
 		 */
 	}
 
-	namespaceId = get_namespace_oid(nspname, missing_ok);
+	namespaceId = get_namespace_oid(nspname, InvalidOid, missing_ok);
 	if (missing_ok && !OidIsValid(namespaceId))
 		return InvalidOid;
 
@@ -2945,7 +3081,7 @@ LookupCreationNamespace(const char *nspname)
 		return myTempNamespace;
 	}
 
-	namespaceId = get_namespace_oid(nspname, false);
+	namespaceId = get_namespace_oid(nspname, InvalidOid, false);
 
 	aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_CREATE);
 	if (aclresult != ACLCHECK_OK)
@@ -2991,25 +3127,34 @@ CheckSetNamespace(Oid oldNspOid, Oid nspOid)
  * if we have to create or clean out the temp namespace.
  */
 Oid
-QualifiedNameGetCreationNamespace(List *names, char **objname_p)
+QualifiedNameGetCreationNamespace(List *names, char **objname_p, bool check_module)
 {
-	char	   *schemaname;
+	char	   *nspname = NULL;
+	char	   *modulename = NULL;
+	Oid			nspparent;
 	Oid			namespaceId;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(names, &schemaname, objname_p);
+	DeconstructQualifiedName(names, &nspname, &modulename, objname_p, check_module);
 
-	if (schemaname)
+	if (nspname && modulename)
+	{
+		/* use exact schema given */
+		nspparent = LookupExplicitNamespace(nspname, false);
+
+		namespaceId = get_namespace_oid(modulename, nspparent, false);
+	}
+	else if (nspname)
 	{
 		/* check for pg_temp alias */
-		if (strcmp(schemaname, "pg_temp") == 0)
+		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, InvalidOid, false);
 		/* we do not check for USAGE rights here! */
 	}
 	else
@@ -3039,12 +3184,12 @@ QualifiedNameGetCreationNamespace(List *names, char **objname_p)
  * true, just return InvalidOid.
  */
 Oid
-get_namespace_oid(const char *nspname, bool missing_ok)
+get_namespace_oid(const char *nspname, Oid nspnamespace, bool missing_ok)
 {
 	Oid			oid;
 
-	oid = GetSysCacheOid1(NAMESPACENAME, Anum_pg_namespace_oid,
-						  CStringGetDatum(nspname));
+	oid = GetSysCacheOid2(NAMESPACENAME, Anum_pg_namespace_oid,
+						  CStringGetDatum(nspname), ObjectIdGetDatum(nspnamespace));
 	if (!OidIsValid(oid) && !missing_ok)
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_SCHEMA),
@@ -3607,7 +3752,7 @@ get_collation_oid(List *name, bool missing_ok)
 	ListCell   *l;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(name, &schemaname, &collation_name);
+	DeconstructQualifiedName(name, &schemaname, NULL, &collation_name, false);
 
 	if (schemaname)
 	{
@@ -3660,7 +3805,7 @@ get_conversion_oid(List *name, bool missing_ok)
 	ListCell   *l;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(name, &schemaname, &conversion_name);
+	DeconstructQualifiedName(name, &schemaname, NULL, &conversion_name, false);
 
 	if (schemaname)
 	{
@@ -3789,7 +3934,7 @@ recomputeNamespacePath(void)
 				char	   *rname;
 
 				rname = NameStr(((Form_pg_authid) GETSTRUCT(tuple))->rolname);
-				namespaceId = get_namespace_oid(rname, true);
+				namespaceId = get_namespace_oid(rname, InvalidOid, true);
 				ReleaseSysCache(tuple);
 				if (OidIsValid(namespaceId) &&
 					!list_member_oid(oidlist, namespaceId) &&
@@ -3818,7 +3963,7 @@ recomputeNamespacePath(void)
 		else
 		{
 			/* normal namespace reference */
-			namespaceId = get_namespace_oid(curname, true);
+			namespaceId = get_namespace_oid(curname, InvalidOid, true);
 			if (OidIsValid(namespaceId) &&
 				!list_member_oid(oidlist, namespaceId) &&
 				pg_namespace_aclcheck(namespaceId, roleid,
@@ -3987,7 +4132,7 @@ InitTempTableNamespace(void)
 
 	snprintf(namespaceName, sizeof(namespaceName), "pg_temp_%d", MyBackendId);
 
-	namespaceId = get_namespace_oid(namespaceName, true);
+	namespaceId = get_namespace_oid(namespaceName, InvalidOid, true);
 	if (!OidIsValid(namespaceId))
 	{
 		/*
@@ -3998,8 +4143,8 @@ 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, InvalidOid, NSPKIND_SCHEMA,
+									  BOOTSTRAP_SUPERUSERID, true);
 		/* Advance command counter to make namespace visible */
 		CommandCounterIncrement();
 	}
@@ -4020,11 +4165,11 @@ InitTempTableNamespace(void)
 	snprintf(namespaceName, sizeof(namespaceName), "pg_toast_temp_%d",
 			 MyBackendId);
 
-	toastspaceId = get_namespace_oid(namespaceName, true);
+	toastspaceId = get_namespace_oid(namespaceName, InvalidOid, true);
 	if (!OidIsValid(toastspaceId))
 	{
-		toastspaceId = NamespaceCreate(namespaceName, BOOTSTRAP_SUPERUSERID,
-									   true);
+		toastspaceId = NamespaceCreate(namespaceName, InvalidOid, NSPKIND_SCHEMA,
+									   BOOTSTRAP_SUPERUSERID, true);
 		/* Advance command counter to make namespace visible */
 		CommandCounterIncrement();
 	}
unchanged:
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -406,12 +406,26 @@ static const ObjectPropertyType ObjectProperty[] =
 		NAMESPACENAME,
 		Anum_pg_namespace_oid,
 		Anum_pg_namespace_nspname,
-		InvalidAttrNumber,
+		Anum_pg_namespace_nspnamespace,
 		Anum_pg_namespace_nspowner,
 		Anum_pg_namespace_nspacl,
 		OBJECT_SCHEMA,
 		true
 	},
+	{
+		"module",
+		NamespaceRelationId,
+		NamespaceOidIndexId,
+		NAMESPACEOID,
+		NAMESPACENAME,
+		Anum_pg_namespace_oid,
+		Anum_pg_namespace_nspname,
+		Anum_pg_namespace_nspnamespace,
+		Anum_pg_namespace_nspowner,
+		Anum_pg_namespace_nspacl,
+		OBJECT_MODULE,
+		true
+	},
 	{
 		"relation",
 		RelationRelationId,
@@ -765,6 +779,10 @@ static const struct object_type_map
 	{
 		"schema", OBJECT_SCHEMA
 	},
+	/* OCLASS_MODULE */
+	{
+		"module", OBJECT_MODULE
+	},
 	/* OCLASS_TSPARSER */
 	{
 		"text search parser", OBJECT_TSPARSER
@@ -1128,6 +1146,12 @@ get_object_address(ObjectType objtype, Node *object,
 															 missing_ok);
 				address.objectSubId = 0;
 				break;
+			case OBJECT_MODULE:
+				address.classId = NamespaceRelationId;
+				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 */
@@ -1281,7 +1305,7 @@ get_object_address_unqualified(ObjectType objtype,
 			break;
 		case OBJECT_SCHEMA:
 			address.classId = NamespaceRelationId;
-			address.objectId = get_namespace_oid(name, missing_ok);
+			address.objectId = get_namespace_oid(name, InvalidOid, missing_ok);
 			address.objectSubId = 0;
 			break;
 		case OBJECT_LANGUAGE:
@@ -2013,7 +2037,7 @@ get_object_address_defacl(List *object, bool missing_ok)
 	 */
 	if (schema)
 	{
-		schemaid = get_namespace_oid(schema, true);
+		schemaid = get_namespace_oid(schema, InvalidOid, true);
 		if (schemaid == InvalidOid)
 			goto not_found;
 	}
@@ -2267,6 +2291,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:
@@ -2431,6 +2456,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 							   NameListToString((castNode(ObjectWithArgs, object))->objname));
 			break;
 		case OBJECT_SCHEMA:
+		case OBJECT_MODULE:
 			if (!pg_namespace_ownercheck(address.objectId, roleid))
 				aclcheck_error(ACLCHECK_NOT_OWNER, objtype,
 							   strVal((Value *) object));
unchanged:
--- a/src/backend/catalog/pg_namespace.c
+++ b/src/backend/catalog/pg_namespace.c
@@ -40,7 +40,8 @@
  * ---------------
  */
 Oid
-NamespaceCreate(const char *nspName, Oid ownerId, bool isTemp)
+NamespaceCreate(const char *nspName, Oid nspnamespace, char nspkind,
+				Oid ownerId, bool isTemp)
 {
 	Relation	nspdesc;
 	HeapTuple	tup;
@@ -58,7 +59,7 @@ NamespaceCreate(const char *nspName, Oid ownerId, bool isTemp)
 		elog(ERROR, "no namespace name supplied");
 
 	/* make sure there is no existing namespace of same name */
-	if (SearchSysCacheExists1(NAMESPACENAME, PointerGetDatum(nspName)))
+	if (SearchSysCacheExists2(NAMESPACENAME, PointerGetDatum(nspName), nspnamespace))
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_SCHEMA),
 				 errmsg("schema \"%s\" already exists", nspName)));
@@ -85,6 +86,8 @@ NamespaceCreate(const char *nspName, Oid ownerId, bool isTemp)
 	namestrcpy(&nname, nspName);
 	values[Anum_pg_namespace_nspname - 1] = NameGetDatum(&nname);
 	values[Anum_pg_namespace_nspowner - 1] = ObjectIdGetDatum(ownerId);
+	values[Anum_pg_namespace_nspnamespace - 1] = ObjectIdGetDatum(nspnamespace);
+	values[Anum_pg_namespace_nspkind - 1] = CharGetDatum(nspkind);
 	if (nspacl != NULL)
 		values[Anum_pg_namespace_nspacl - 1] = PointerGetDatum(nspacl);
 	else
unchanged:
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1460,7 +1460,7 @@ CreateExtensionInternal(char *extensionName,
 	if (schemaName)
 	{
 		/* If the user is giving us the schema name, it must exist already. */
-		schemaOid = get_namespace_oid(schemaName, false);
+		schemaOid = get_namespace_oid(schemaName, InvalidOid, false);
 	}
 
 	if (control->schema != NULL)
@@ -1484,7 +1484,7 @@ CreateExtensionInternal(char *extensionName,
 		schemaName = control->schema;
 
 		/* Find or create the schema in case it does not exist. */
-		schemaOid = get_namespace_oid(schemaName, true);
+		schemaOid = get_namespace_oid(schemaName, InvalidOid, true);
 
 		if (!OidIsValid(schemaOid))
 		{
@@ -1501,7 +1501,7 @@ CreateExtensionInternal(char *extensionName,
 			 * CreateSchemaCommand includes CommandCounterIncrement, so new
 			 * schema is now visible.
 			 */
-			schemaOid = get_namespace_oid(schemaName, false);
+			schemaOid = get_namespace_oid(schemaName, InvalidOid, false);
 		}
 	}
 	else if (!OidIsValid(schemaOid))
unchanged:
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -113,7 +113,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 	 * creation-permission check first, we do likewise.
 	 */
 	if (stmt->if_not_exists &&
-		SearchSysCacheExists1(NAMESPACENAME, PointerGetDatum(schemaName)))
+		SearchSysCacheExists2(NAMESPACENAME, PointerGetDatum(schemaName), InvalidOid))
 	{
 		ereport(NOTICE,
 				(errcode(ERRCODE_DUPLICATE_SCHEMA),
@@ -135,7 +135,8 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 							   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
 
 	/* Create the schema's namespace */
-	namespaceId = NamespaceCreate(schemaName, owner_uid, false);
+	namespaceId = NamespaceCreate(schemaName, InvalidOid, NSPKIND_SCHEMA,
+								  owner_uid, false);
 
 	/* Advance cmd counter to make the namespace visible */
 	CommandCounterIncrement();
@@ -226,7 +227,7 @@ RenameSchema(const char *oldname, const char *newname)
 
 	rel = table_open(NamespaceRelationId, RowExclusiveLock);
 
-	tup = SearchSysCacheCopy1(NAMESPACENAME, CStringGetDatum(oldname));
+	tup = SearchSysCacheCopy2(NAMESPACENAME, CStringGetDatum(oldname), InvalidOid);
 	if (!HeapTupleIsValid(tup))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_SCHEMA),
@@ -236,7 +237,7 @@ RenameSchema(const char *oldname, const char *newname)
 	nspOid = nspform->oid;
 
 	/* make sure the new name doesn't exist */
-	if (OidIsValid(get_namespace_oid(newname, true)))
+	if (OidIsValid(get_namespace_oid(newname, InvalidOid, true)))
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_SCHEMA),
 				 errmsg("schema \"%s\" already exists", newname)));
@@ -306,7 +307,7 @@ AlterSchemaOwner(const char *name, Oid newOwnerId)
 
 	rel = table_open(NamespaceRelationId, RowExclusiveLock);
 
-	tup = SearchSysCache1(NAMESPACENAME, CStringGetDatum(name));
+	tup = SearchSysCache2(NAMESPACENAME, CStringGetDatum(name), InvalidOid);
 	if (!HeapTupleIsValid(tup))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_SCHEMA),
unchanged:
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -3827,7 +3827,7 @@ convert_schema_name(text *schemaname)
 {
 	char	   *nspname = text_to_cstring(schemaname);
 
-	return get_namespace_oid(nspname, false);
+	return get_namespace_oid(nspname, InvalidOid, false);
 }
 
 /*
unchanged:
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -184,7 +184,7 @@ binary_upgrade_create_empty_extension(PG_FUNCTION_ARGS)
 
 	InsertExtensionTuple(text_to_cstring(extName),
 						 GetUserId(),
-						 get_namespace_oid(text_to_cstring(schemaName), false),
+						 get_namespace_oid(text_to_cstring(schemaName), InvalidOid, false),
 						 relocatable,
 						 text_to_cstring(extVersion),
 						 extConfig,
unchanged:
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -1762,7 +1762,7 @@ regnamespacein(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_INVALID_NAME),
 				 errmsg("invalid name syntax")));
 
-	result = get_namespace_oid(strVal(linitial(names)), false);
+	result = get_namespace_oid(strVal(linitial(names)), InvalidOid, false);
 
 	PG_RETURN_OID(result);
 }
@@ -1786,7 +1786,7 @@ to_regnamespace(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_INVALID_NAME),
 				 errmsg("invalid name syntax")));
 
-	result = get_namespace_oid(strVal(linitial(names)), true);
+	result = get_namespace_oid(strVal(linitial(names)), InvalidOid, true);
 
 	if (OidIsValid(result))
 		PG_RETURN_OID(result);
unchanged:
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -509,10 +509,10 @@ static const struct cachedesc cacheinfo[] = {
 	},
 	{NamespaceRelationId,		/* NAMESPACENAME */
 		NamespaceNameIndexId,
-		1,
+		2,
 		{
 			Anum_pg_namespace_nspname,
-			0,
+			Anum_pg_namespace_nspnamespace,
 			0,
 			0
 		},
only in patch2:
unchanged:
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -135,15 +135,18 @@ 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);
+											char **nspname_p,
+											char **modname_p,
+											char **objname_p,
+											bool check_module);
 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_namespace_oid(const char *nspname, Oid nspnamespace, bool missing_ok);
+extern Oid	get_module_oid(List *names, 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	QualifiedNameGetCreationNamespace(List *names, char **objname_p, bool check_module);
 extern RangeVar *makeRangeVarFromNameList(List *names);
 extern char *NameListToString(List *names);
 extern char *NameListToQuotedString(List *names);
only in patch2:
unchanged:
--- a/src/include/catalog/pg_namespace.dat
+++ b/src/include/catalog/pg_namespace.dat
@@ -14,12 +14,15 @@
 
 { oid => '11', oid_symbol => 'PG_CATALOG_NAMESPACE',
   descr => 'system catalog schema',
-  nspname => 'pg_catalog', nspacl => '_null_' },
+  nspname => 'pg_catalog', nspnamespace => '0',
+  nspkind => 's', nspacl => '_null_' },
 { oid => '99', oid_symbol => 'PG_TOAST_NAMESPACE',
   descr => 'reserved schema for TOAST tables',
-  nspname => 'pg_toast', nspacl => '_null_' },
+  nspname => 'pg_toast', nspnamespace => '0',
+  nspkind => 's', nspacl => '_null_' },
 { oid => '2200', oid_symbol => 'PG_PUBLIC_NAMESPACE',
   descr => 'standard public schema',
-  nspname => 'public', nspacl => '_null_' },
+  nspname => 'public', nspnamespace => '0',
+  nspkind => 's', nspacl => '_null_' },
 
 ]
only in patch2:
unchanged:
--- a/src/include/catalog/pg_namespace.h
+++ b/src/include/catalog/pg_namespace.h
@@ -29,6 +29,8 @@
  *
  *	nspname				name of the namespace
  *	nspowner			owner (creator) of the namespace
+ *	nspnamespace		parent of the namespace
+ *	nspkind				the type namespace
  *	nspacl				access privilege list
  * ----------------------------------------------------------------
  */
@@ -38,6 +40,9 @@ CATALOG(pg_namespace,2615,NamespaceRelationId)
 
 	NameData	nspname;
 	Oid			nspowner BKI_DEFAULT(POSTGRES) BKI_LOOKUP(pg_authid);
+	Oid			nspnamespace BKI_DEFAULT(0);
+	char		nspkind BKI_DEFAULT(s);
+
 
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 	aclitem		nspacl[1];
@@ -53,14 +58,18 @@ typedef FormData_pg_namespace *Form_pg_namespace;
 
 DECLARE_TOAST(pg_namespace, 4163, 4164);
 
-DECLARE_UNIQUE_INDEX(pg_namespace_nspname_index, 2684, on pg_namespace using btree(nspname name_ops));
+DECLARE_UNIQUE_INDEX(pg_namespace_nspname_index, 2684, on pg_namespace using btree(nspname name_ops, nspnamespace oid_ops));
 #define NamespaceNameIndexId  2684
 DECLARE_UNIQUE_INDEX_PKEY(pg_namespace_oid_index, 2685, on pg_namespace using btree(oid oid_ops));
 #define NamespaceOidIndexId  2685
 
+#define		NSPKIND_SCHEMA			's'		/* schema */
+#define		NSPKIND_MODULE			'm'		/* module */
+
 /*
  * prototypes for functions in pg_namespace.c
  */
-extern Oid	NamespaceCreate(const char *nspName, Oid ownerId, bool isTemp);
+extern Oid	NamespaceCreate(const char *nspName, Oid nspnamespace, char nspkind,
+							Oid ownerId, bool isTemp);
 
 #endif							/* PG_NAMESPACE_H */
modules_poc_2.patchapplication/octet-stream; name=modules_poc_2.patchDownload
unchanged:
--- 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 \
unchanged:
--- /dev/null
+++ b/src/backend/commands/modulecmds.c
@@ -0,0 +1,202 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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_namespace.h"
+#include "commands/dbcommands.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"
+
+/*
+ * 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(ParseState *pstate, CreateModuleStmt *stmt, const char *queryString,
+					int stmt_location, int stmt_len)
+{
+	char	   *modulename;
+	Oid			namespaceId;
+	Oid			moduleId;
+	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;
+
+	GetUserIdAndSecContext(&saved_uid, &save_sec_context);
+
+	/*
+	 * Who is supposed to own the new module?
+	 */
+	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 = QualifiedNameGetCreationNamespace(stmt->modulename,
+													&modulename, false);
+
+	/* 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,
+					   get_namespace_name(namespaceId));
+
+	/*
+	 * 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
+	 * NamespaceCreate 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(NAMESPACENAME, PointerGetDatum(modulename),
+							  ObjectIdGetDatum(namespaceId)))
+	{
+		ereport(NOTICE,
+				(errcode(ERRCODE_DUPLICATE_SCHEMA),
+				 errmsg("module \"%s\" already exists, skipping",
+						modulename)));
+		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 namespace */
+	moduleId = NamespaceCreate(modulename, namespaceId, NSPKIND_MODULE,
+							   owner_uid, false);
+
+	/* 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);
+	/* XXX 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, NamespaceRelationId, 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;
+
+		ProcessUtility(wrapper,
+					   queryString,
+					   PROCESS_UTILITY_SUBCOMMAND,
+					   NULL,
+					   NULL,
+					   None_Receiver,
+					   NULL);
+
+		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, NamespaceRelationId, namespaceId);
+	add_exact_object_address(&referenced, addrs);
+
+	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
+	free_object_addresses(addrs);
+
+	recordDependencyOnOwner(NamespaceRelationId, moduleId, owner_uid);
+
+	return myself;
+}
only in patch2:
unchanged:
--- /dev/null
+++ b/src/include/commands/modulecmds.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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(ParseState *pstate,
+											CreateModuleStmt *parsetree,
+											const char *queryString,
+											int stmt_location, int stmt_len);
+
+#endif							/* MODULECMDS_H */
modules_poc_3.patchapplication/octet-stream; name=modules_poc_3.patchDownload
unchanged:
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -604,7 +604,8 @@ get_other_operator(List *otherOp, Oid otherLeftTypeId, Oid otherRightTypeId,
 	}
 
 	otherNamespace = QualifiedNameGetCreationNamespace(otherOp,
-													   &otherName);
+													   &otherName,
+													   false);
 
 	if (strcmp(otherName, operatorName) == 0 &&
 		otherNamespace == operatorNamespace &&
unchanged:
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -101,7 +101,7 @@ DefineAggregate(ParseState *pstate,
 	ListCell   *pl;
 
 	/* Convert list of names to a name and namespace */
-	aggNamespace = QualifiedNameGetCreationNamespace(name, &aggName);
+	aggNamespace = QualifiedNameGetCreationNamespace(name, &aggName, false);
 
 	/* Check we have creation rights in target namespace */
 	aclresult = pg_namespace_aclcheck(aggNamespace, GetUserId(), ACL_CREATE);
unchanged:
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -384,6 +384,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:
unchanged:
--- a/src/backend/commands/collationcmds.c
+++ b/src/backend/commands/collationcmds.c
@@ -73,7 +73,7 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
 	Oid			newoid;
 	ObjectAddress address;
 
-	collNamespace = QualifiedNameGetCreationNamespace(names, &collName);
+	collNamespace = QualifiedNameGetCreationNamespace(names, &collName, false);
 
 	aclresult = pg_namespace_aclcheck(collNamespace, GetUserId(), ACL_CREATE);
 	if (aclresult != ACLCHECK_OK)
unchanged:
--- a/src/backend/commands/conversioncmds.c
+++ b/src/backend/commands/conversioncmds.c
@@ -51,7 +51,7 @@ CreateConversionCommand(CreateConversionStmt *stmt)
 
 	/* Convert list of names to a name and namespace */
 	namespaceId = QualifiedNameGetCreationNamespace(stmt->conversion_name,
-													&conversion_name);
+													&conversion_name, false);
 
 	/* Check we have creation rights in target namespace */
 	aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_CREATE);
unchanged:
--- 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:
@@ -2087,6 +2088,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:
@@ -2169,6 +2172,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:
unchanged:
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -148,7 +148,7 @@ compute_return_type(TypeName *returnType, Oid languageOid,
 				 errmsg("type \"%s\" is not yet defined", typnam),
 				 errdetail("Creating a shell type definition.")));
 		namespaceId = QualifiedNameGetCreationNamespace(returnType->names,
-														&typname);
+														&typname, false);
 		aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(),
 										  ACL_CREATE);
 		if (aclresult != ACLCHECK_OK)
@@ -1050,7 +1050,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
 
 	/* Convert list of names to a name and namespace */
 	namespaceId = QualifiedNameGetCreationNamespace(stmt->funcname,
-													&funcname);
+													&funcname, true);
 
 	/* Check we have creation rights in target namespace */
 	aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_CREATE);
unchanged:
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2039,7 +2039,7 @@ ResolveOpClass(List *opclass, Oid attrType,
 	 */
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(opclass, &schemaname, &opcname);
+	DeconstructQualifiedName(opclass, &schemaname, NULL, &opcname, false);
 
 	if (schemaname)
 	{
@@ -2787,7 +2787,7 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	 */
 	if (objectKind == REINDEX_OBJECT_SCHEMA)
 	{
-		objectOid = get_namespace_oid(objectName, false);
+		objectOid = get_namespace_oid(objectName, InvalidOid, false);
 
 		if (!pg_namespace_ownercheck(objectOid, GetUserId()))
 			aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
unchanged:
--- 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);
+	DeconstructQualifiedName(opfamilyname, &schemaname, NULL, &opfname, false);
 
 	if (schemaname)
 	{
@@ -166,7 +166,7 @@ OpClassCacheLookup(Oid amID, List *opclassname, bool missing_ok)
 	HeapTuple	htup;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(opclassname, &schemaname, &opcname);
+	DeconstructQualifiedName(opclassname, &schemaname, NULL, &opcname, false);
 
 	if (schemaname)
 	{
@@ -354,7 +354,7 @@ DefineOpClass(CreateOpClassStmt *stmt)
 
 	/* Convert list of names to a name and namespace */
 	namespaceoid = QualifiedNameGetCreationNamespace(stmt->opclassname,
-													 &opcname);
+													 &opcname, false);
 
 	/* Check we have creation rights in target namespace */
 	aclresult = pg_namespace_aclcheck(namespaceoid, GetUserId(), ACL_CREATE);
@@ -769,7 +769,7 @@ DefineOpFamily(CreateOpFamilyStmt *stmt)
 
 	/* Convert list of names to a name and namespace */
 	namespaceoid = QualifiedNameGetCreationNamespace(stmt->opfamilyname,
-													 &opfname);
+													 &opfname, false);
 
 	/* Check we have creation rights in target namespace */
 	aclresult = pg_namespace_aclcheck(namespaceoid, GetUserId(), ACL_CREATE);
unchanged:
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -87,7 +87,7 @@ DefineOperator(List *names, List *parameters)
 	ListCell   *pl;
 
 	/* Convert list of names to a name and namespace */
-	oprNamespace = QualifiedNameGetCreationNamespace(names, &oprName);
+	oprNamespace = QualifiedNameGetCreationNamespace(names, &oprName, false);
 
 	/* Check we have creation rights in target namespace */
 	aclresult = pg_namespace_aclcheck(oprNamespace, GetUserId(), ACL_CREATE);
unchanged:
--- 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:
unchanged:
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -163,7 +163,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 	 */
 	if (stmt->defnames)
 		namespaceId = QualifiedNameGetCreationNamespace(stmt->defnames,
-														&namestr);
+														&namestr, false);
 	else
 	{
 		namespaceId = RelationGetNamespace(rel);
@@ -630,7 +630,7 @@ AlterStatistics(AlterStatsStmt *stmt)
 
 		Assert(stmt->missing_ok);
 
-		DeconstructQualifiedName(stmt->defnames, &schemaname, &statname);
+		DeconstructQualifiedName(stmt->defnames, &schemaname, NULL, &statname, false);
 
 		if (schemaname)
 			ereport(NOTICE,
unchanged:
--- a/src/backend/commands/tsearchcmds.c
+++ b/src/backend/commands/tsearchcmds.c
@@ -196,7 +196,7 @@ DefineTSParser(List *names, List *parameters)
 	prsRel = table_open(TSParserRelationId, RowExclusiveLock);
 
 	/* Convert list of names to a name and namespace */
-	namespaceoid = QualifiedNameGetCreationNamespace(names, &prsname);
+	namespaceoid = QualifiedNameGetCreationNamespace(names, &prsname, false);
 
 	/* initialize tuple fields with name/namespace */
 	memset(values, 0, sizeof(values));
@@ -405,7 +405,7 @@ DefineTSDictionary(List *names, List *parameters)
 	ObjectAddress address;
 
 	/* Convert list of names to a name and namespace */
-	namespaceoid = QualifiedNameGetCreationNamespace(names, &dictname);
+	namespaceoid = QualifiedNameGetCreationNamespace(names, &dictname, false);
 
 	/* Check we have creation rights in target namespace */
 	aclresult = pg_namespace_aclcheck(namespaceoid, GetUserId(), ACL_CREATE);
@@ -701,7 +701,7 @@ DefineTSTemplate(List *names, List *parameters)
 				 errmsg("must be superuser to create text search templates")));
 
 	/* Convert list of names to a name and namespace */
-	namespaceoid = QualifiedNameGetCreationNamespace(names, &tmplname);
+	namespaceoid = QualifiedNameGetCreationNamespace(names, &tmplname, false);
 
 	tmplRel = table_open(TSTemplateRelationId, RowExclusiveLock);
 
@@ -908,7 +908,7 @@ DefineTSConfiguration(List *names, List *parameters, ObjectAddress *copied)
 	ObjectAddress address;
 
 	/* Convert list of names to a name and namespace */
-	namespaceoid = QualifiedNameGetCreationNamespace(names, &cfgname);
+	namespaceoid = QualifiedNameGetCreationNamespace(names, &cfgname, false);
 
 	/* Check we have creation rights in target namespace */
 	aclresult = pg_namespace_aclcheck(namespaceoid, GetUserId(), ACL_CREATE);
unchanged:
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -217,7 +217,7 @@ DefineType(ParseState *pstate, List *names, List *parameters)
 				 errmsg("must be superuser to create a base type")));
 
 	/* Convert list of names to a name and namespace */
-	typeNamespace = QualifiedNameGetCreationNamespace(names, &typeName);
+	typeNamespace = QualifiedNameGetCreationNamespace(names, &typeName, false);
 
 #ifdef NOT_USED
 	/* XXX this is unnecessary given the superuser check above */
@@ -733,7 +733,7 @@ DefineDomain(CreateDomainStmt *stmt)
 
 	/* Convert list of names to a name and namespace */
 	domainNamespace = QualifiedNameGetCreationNamespace(stmt->domainname,
-														&domainName);
+														&domainName, false);
 
 	/* Check we have creation rights in target namespace */
 	aclresult = pg_namespace_aclcheck(domainNamespace, GetUserId(),
@@ -1149,7 +1149,7 @@ DefineEnum(CreateEnumStmt *stmt)
 
 	/* Convert list of names to a name and namespace */
 	enumNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
-													  &enumName);
+													  &enumName, false);
 
 	/* Check we have creation rights in target namespace */
 	aclresult = pg_namespace_aclcheck(enumNamespace, GetUserId(), ACL_CREATE);
@@ -1369,7 +1369,7 @@ DefineRange(CreateRangeStmt *stmt)
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
-													  &typeName);
+													  &typeName, false);
 
 	/* Check we have creation rights in target namespace */
 	aclresult = pg_namespace_aclcheck(typeNamespace, GetUserId(), ACL_CREATE);
@@ -1457,7 +1457,7 @@ DefineRange(CreateRangeStmt *stmt)
 						 errmsg("conflicting or redundant options")));
 			/* we can look up the subtype name immediately */
 			multirangeNamespace = QualifiedNameGetCreationNamespace(defGetQualifiedName(defel),
-																	&multirangeTypeName);
+																	&multirangeTypeName, false);
 		}
 		else
 			ereport(ERROR,
unchanged:
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -262,7 +262,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct GroupClause  *groupclause;
 }
 
-%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
@@ -301,6 +301,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
@@ -344,7 +345,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
 
@@ -368,7 +369,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
 
@@ -681,7 +682,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
@@ -954,6 +956,7 @@ stmt:
 			| CreateFunctionStmt
 			| CreateGroupStmt
 			| CreateMatViewStmt
+			| CreateModuleStmt
 			| CreateOpClassStmt
 			| CreateOpFamilyStmt
 			| CreatePublicationStmt
@@ -1466,6 +1469,84 @@ 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
+			;
+
 
 /*****************************************************************************
  *
@@ -6308,6 +6389,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 */
@@ -8569,6 +8670,15 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ALTER MODULE module_name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_MODULE;
+					n->object = (Node *) $3;
+					n->newname = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			| ALTER opt_procedural LANGUAGE name RENAME TO name
 				{
 					RenameStmt *n = makeNode(RenameStmt);
@@ -15639,6 +15749,7 @@ unreserved_keyword:
 			| MINUTE_P
 			| MINVALUE
 			| MODE
+			| MODULE
 			| MONTH_P
 			| MOVE
 			| NAME_P
@@ -16205,6 +16316,7 @@ bare_label_keyword:
 			| METHOD
 			| MINVALUE
 			| MODE
+			| MODULE
 			| MOVE
 			| NAME_P
 			| NAMES
unchanged:
--- 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"
@@ -1421,7 +1423,6 @@ func_get_detail(List *funcname,
 	raw_candidates = FuncnameGetCandidates(funcname, nargs, fargnames,
 										   expand_variadic, expand_defaults,
 										   false);
-
 	/*
 	 * Quickly check if there is an exact match to the input datatypes (there
 	 * can be only one)
@@ -1549,7 +1550,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...
 			 */
@@ -1882,6 +1882,16 @@ 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
+	 */
+	if (list_length(funcname) == 3)
+	{
+		if (strcmp(strVal(linitial(funcname)), get_database_name(MyDatabaseId)) != 0)
+			return InvalidOid;
+	}
+
 	/*
 	 * temp_ok=false protects the <refsect1 id="sql-createfunction-security">
 	 * contract for writing SECURITY DEFINER functions safely.
@@ -2058,7 +2068,6 @@ LookupFuncNameInternal(List *funcname, int nargs, const Oid *argtypes,
 
 	clist = FuncnameGetCandidates(funcname, nargs, NIL, false, false,
 								  missing_ok);
-
 	/*
 	 * If no arguments were specified, the name must yield a unique candidate.
 	 */
unchanged:
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -954,7 +954,7 @@ make_oper_cache_key(ParseState *pstate, OprCacheKey *key, List *opname,
 	char	   *opername;
 
 	/* deconstruct the name list */
-	DeconstructQualifiedName(opname, &schemaname, &opername);
+	DeconstructQualifiedName(opname, &schemaname, NULL, &opername, false);
 
 	/* ensure zero-fill for stable hashing */
 	MemSet(key, 0, sizeof(OprCacheKey));
unchanged:
--- 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);
+		DeconstructQualifiedName(typeName->names, &schemaname, NULL, &typname, false);
 
 		if (schemaname)
 		{
unchanged:
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -113,6 +113,14 @@ typedef struct
 	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	   *functions;		/* CREATE FUNCTION items */
+} CreateModuleStmtContext;
 
 static void transformColumnDefinition(CreateStmtContext *cxt,
 									  ColumnDef *column);
@@ -3944,6 +3952,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.functions = 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_SCHEMA_DEFINITION),
+								 errmsg("CREATE FUNCTION (%s) specifies a "
+										"namespace inside of CREATE MODULE (%s)",
+										NameListToString(elp->funcname),
+										NameListToString(cxt.modulename))));
+
+					cxt.functions = lappend(cxt.functions, element);
+				}
+				break;
+
+			default:
+				elog(ERROR, "unrecognized node type: %d",
+					 (int) nodeTag(element));
+		}
+	}
+
+	result = NIL;
+	result = list_concat(result, cxt.functions);
+
+	return result;
+}
+
 /*
  * setSchemaName
  *		Set or check schema name in an element of a CREATE SCHEMA command
unchanged:
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -42,6 +42,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"
@@ -178,6 +179,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:
@@ -1097,6 +1099,19 @@ ProcessUtilitySlow(ParseState *pstate,
 				commandCollected = true;
 				break;
 
+			case T_CreateModuleStmt:	/* CREATE Module */
+				CreateModuleCommand(pstate, (CreateModuleStmt *) parsetree,
+									queryString,
+									pstmt->stmt_location,
+									pstmt->stmt_len);
+				/*
+				 * EventTriggerCollectSimpleCommand called by
+				 * CreateModuleCommand
+				 */
+				commandCollected = true;
+				break;
+
+
 			case T_CreateStmt:
 			case T_CreateForeignTableStmt:
 				{
@@ -2280,6 +2295,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
 		case OBJECT_STATISTIC_EXT:
 			tag = CMDTAG_ALTER_STATISTICS;
 			break;
+		case OBJECT_MODULE:
+			tag = CMDTAG_ALTER_MODULE;
+			break;
 		default:
 			tag = CMDTAG_UNKNOWN;
 			break;
@@ -2584,6 +2602,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;
 			}
@@ -2739,6 +2760,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;
@@ -3382,6 +3407,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_CreateModuleStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 		case T_IndexStmt:
 			lev = LOGSTMT_DDL;
 			break;
only in patch2:
unchanged:
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -432,6 +432,7 @@ typedef enum NodeTag
 	T_AlterCollationStmt,
 	T_CallStmt,
 	T_AlterStatsStmt,
+	T_CreateModuleStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
only in patch2:
unchanged:
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1798,6 +1798,7 @@ typedef enum ObjectType
 	OBJECT_LANGUAGE,
 	OBJECT_LARGEOBJECT,
 	OBJECT_MATVIEW,
+	OBJECT_MODULE,
 	OBJECT_OPCLASS,
 	OBJECT_OPERATOR,
 	OBJECT_OPFAMILY,
@@ -2975,6 +2976,24 @@ typedef struct AlterFunctionStmt
 	List	   *actions;		/* list of DefElem */
 } AlterFunctionStmt;
 
+/* ----------------------
+ *		Create Module Statement
+ *
+ * NOTE: the moduleElts list contains raw parsetrees for component statements
+ * of the schema, such as CREATE FUNCTION, CREATE PROCEDURE, etc.  These are
+ * analyzed and executed after the module itself is created.
+ * ----------------------
+ */
+typedef struct CreateModuleStmt
+{
+	NodeTag		type;
+	List	   *modulename;		/* the name of the schema to create */
+	RoleSpec   *authrole;		/* the owner of the created schema */
+	List	   *moduleElts;		/* module components (list of parsenodes) */
+	bool		if_not_exists;	/* just do nothing if module already exists? */
+} CreateModuleStmt;
+
+
 /* ----------------------
  *		DO Statement
  *
only in patch2:
unchanged:
--- 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)
only in patch2:
unchanged:
--- 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,
only in patch2:
unchanged:
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -43,6 +43,7 @@ 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, "ALTER MODULE", 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 +99,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 +152,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)
modules_poc_4.patchapplication/octet-stream; name=modules_poc_4.patchDownload
diff --git a/src/test/regress/expected/create_module.out b/src/test/regress/expected/create_module.out
new file mode 100644
index 0000000000..634df03e4c
--- /dev/null
+++ b/src/test/regress/expected/create_module.out
@@ -0,0 +1,83 @@
+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 mtest3.m3testa() RETURNS text
+     LANGUAGE sql
+     RETURN '3x';
+ERROR:  CREATE FUNCTION (mtest3.m3testa) specifies a namespace inside of CREATE MODULE (mtest3)
+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
+CREATE PROCEDURE mtest1.m1testc(x text)
+  LANGUAGE SQL
+  AS $$
+  INSERT INTO cm_test VALUES (2, x);
+  $$;
+ERROR:  schema "mtest1" does not exist
+CALL mtest1.m1testc('a');  -- ok
+ERROR:  schema "mtest1" does not exist
+LINE 1: CALL mtest1.m1testc('a');
+             ^
+DROP PROCEDURE mtest1.m1testc(text);
+ERROR:  procedure mtest1.m1testc(text) does not exist
+DROP FUNCTION temp_mod_test.mtest2.m2testb();
+-- cleanup
+DROP MODULE mtest1 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to function mtest1.m1testa()
+drop cascades to function mtest1.m1testb()
+DROP MODULE temp_mod_test.mtest2 CASCADE;
+NOTICE:  drop cascades to function mtest2.m2testa(text)
+DROP SCHEMA temp_mod_test;
+DROP TABLE cm_test;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 22b0d3584d..e572bf212d 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -51,7 +51,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..8c1db1e636
--- /dev/null
+++ b/src/test/regress/sql/create_module.sql
@@ -0,0 +1,58 @@
+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 mtest3.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
+
+CREATE PROCEDURE mtest1.m1testc(x text)
+  LANGUAGE SQL
+  AS $$
+  INSERT INTO cm_test VALUES (2, x);
+  $$;
+
+CALL mtest1.m1testc('a');  -- ok
+
+DROP PROCEDURE mtest1.m1testc(text);
+DROP FUNCTION temp_mod_test.mtest2.m2testb();
+
+-- cleanup
+
+DROP MODULE mtest1 CASCADE;
+DROP MODULE temp_mod_test.mtest2 CASCADE;
+
+DROP SCHEMA temp_mod_test;
+DROP TABLE cm_test;
#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jim Mlodgenski (#1)
Re: Support for CREATE MODULE?

Jim Mlodgenski <jimmy76@gmail.com> writes:

Questions
- Do we want to add module support?

Certainly many people have asked for that, or things like that.

- If we do, should it be implemented as a type of namespace or should it
be its own object type that lives in something like pg_module?

While I didn't read the actual patch, your sketch just above this makes
me want to run away screaming. In the first place, what do you think
the primary key of pg_namespace is now? But the bigger problem is that
sub-namespaces just do not work in SQL syntax. Back when we first added
schema support, I had some ambitions towards allowing nested schemas,
which is a big part of the reason why pg_namespace is named that and not
pg_schema. But the idea fell apart after I understood the syntactic
ambiguities it'd introduce. It's already quite hard to tell which part
of a multiply.qualified.name is which, given that SQL says that you can
optionally put a "catalog" (database) name in front of the others.
I really doubt there is a way to shoehorn sub-schemas in there without
creating terrible ambiguities. Is "a.b.c" a reference to object c in
schema b in database a, or is it a reference to object c in sub-schema b
in schema a? This is why we've ended up with bastard syntax like
(table.column).subcolumn.

- How should users interact with objects within a module? They could be
mostly independent like the current POC or we can introduce a path like
ALTER MODULE foo ADD FUNCTION blah

I wonder whether it'd be better to consider modules as a kind of
extension, or at least things with the same sort of ownership relations
as extensions have.

regards, tom lane

#3Jim Mlodgenski
jimmy76@gmail.com
In reply to: Tom Lane (#2)
Re: Support for CREATE MODULE?

On Wed, Jun 2, 2021 at 9:58 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:

In the first place, what do you think the primary key of pg_namespace is now?

In the patch the unique constraint is (nspname, nspnamespace) which is
certainly awkward. I initially went down the pg_module route to avoid
adding another catalog, but in retrospect, that may be a cleaner way.

It's already quite hard to tell which part
of a multiply.qualified.name is which, given that SQL says that you can
optionally put a "catalog" (database) name in front of the others.
I really doubt there is a way to shoehorn sub-schemas in there without
creating terrible ambiguities. Is "a.b.c" a reference to object c in
schema b in database a, or is it a reference to object c in sub-schema b
in schema a?

That was the area I had the most difficult part to reason about. I tried to make
some simplifying assumptions by checking if "a" was the current database.
Since we don't support cross database access, if it was not, I assumed "a"
was a schema. I not sure if that would be valid, but it did scope things
to a more manageable problem.

I wonder whether it'd be better to consider modules as a kind of
extension, or at least things with the same sort of ownership relations
as extensions have.

That would solve the problem of associating objects which is the larger
problem for users today. The objects can all live in their respective
schemas with the module tying them all together.

#4Bruce Momjian
bruce@momjian.us
In reply to: Jim Mlodgenski (#3)
Re: Support for CREATE MODULE?

On Wed, Jun 2, 2021 at 10:43:10AM -0400, Jim Mlodgenski wrote:

On Wed, Jun 2, 2021 at 9:58 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:

In the first place, what do you think the primary key of pg_namespace is now?

In the patch the unique constraint is (nspname, nspnamespace) which is
certainly awkward. I initially went down the pg_module route to avoid
adding another catalog, but in retrospect, that may be a cleaner way.

It's already quite hard to tell which part
of a multiply.qualified.name is which, given that SQL says that you can
optionally put a "catalog" (database) name in front of the others.
I really doubt there is a way to shoehorn sub-schemas in there without
creating terrible ambiguities. Is "a.b.c" a reference to object c in
schema b in database a, or is it a reference to object c in sub-schema b
in schema a?

That was the area I had the most difficult part to reason about. I tried to make
some simplifying assumptions by checking if "a" was the current database.
Since we don't support cross database access, if it was not, I assumed "a"
was a schema. I not sure if that would be valid, but it did scope things
to a more manageable problem.

If we go in this direction, I assume we would just disallow a schema
name matching the database name. CREATE DATABASE with TEMPLATE would
have to check that. Also the common case where you create a database
name to match the user name, and also a schema inside to match the
username, would have to be disallowed, e.g. creating a 'postgres' schema
to match the 'postgres' user in the 'postgres' database.

--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EDB https://enterprisedb.com

If only the physical world exists, free will is an illusion.

#5Joe Conway
mail@joeconway.com
In reply to: Jim Mlodgenski (#3)
Re: Support for CREATE MODULE?

On 6/2/21 10:43 AM, Jim Mlodgenski wrote:

On Wed, Jun 2, 2021 at 9:58 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:

I wonder whether it'd be better to consider modules as a kind of
extension, or at least things with the same sort of ownership relations
as extensions have.

That would solve the problem of associating objects which is the larger
problem for users today. The objects can all live in their respective
schemas with the module tying them all together.

Maybe something similar to "CREATE EXTENSION ... FROM unpackaged"?

Something like:
CREATE EXTENSION myfoo; /* shell extension */
ALTER EXTENSION myfoo ADD type ...;
ALTER EXTENSION myfoo ADD function ...;
...

Joe

--
Crunchy Data - http://crunchydata.com
PostgreSQL Support for Secure Enterprises
Consulting, Training, & Open Source Development

#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#4)
Re: Support for CREATE MODULE?

Bruce Momjian <bruce@momjian.us> writes:

If we go in this direction, I assume we would just disallow a schema
name matching the database name.

That seems quite impossible to enforce.

regression=# create database d1;
CREATE DATABASE
regression=# alter database d1 rename to d2;
ALTER DATABASE

The system had no way to know that d1 doesn't contain a schema named d2.
And you can't fix that by restricting the ALTER to be done on the
current database:

regression=# \c d2
You are now connected to database "d2" as user "postgres".
d2=# alter database d2 rename to d3;
ERROR: current database cannot be renamed

Between that and the point that this restriction would certainly break
existing installations, this is a non-starter.

regards, tom lane

#7Joel Jacobson
joel@compiler.org
In reply to: Jim Mlodgenski (#3)
Re: Support for CREATE MODULE?

On Wed, Jun 2, 2021, at 16:43, Jim Mlodgenski wrote:

On Wed, Jun 2, 2021 at 9:58 AM Tom Lane <tgl@sss.pgh.pa.us <mailto:tgl%40sss.pgh.pa.us>> wrote:

I wonder whether it'd be better to consider modules as a kind of
extension, or at least things with the same sort of ownership relations
as extensions have.

That would solve the problem of associating objects which is the larger
problem for users today. The objects can all live in their respective
schemas with the module tying them all together.

I like the idea of somehow using extensions.

Right now, extensions can only be added from the command-line, via `make install`.

But maybe a new extension could be packaged from the SQL prompt, out of existing database objects that are not already part of an extension?

Maybe the interface could be:

init_new_extension(extension_name text) function, to register a new empty extension.
add_object_to_extension(extension_name text, type text, object_names text[], object_args text[])

Then, if dropping the extension, all objects would be dropped, and if creating the extension, all objects would be restored.

I don't have an idea on how to handle update scripts, but since it's not mandatory to provide extension update scripts, maybe that's not a problem.

/Joel

#8Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Jim Mlodgenski (#1)
Re: Support for CREATE MODULE?

On 2021-Jun-02, Jim Mlodgenski wrote:

Attached is a POC patch for modules. I modeled it as a sub-schema because
that is more what it seems like to me. It adds additional columns to
pg_namespace and allows for 3-part (or 4 with the database name) naming
of objects within the module. This simple example works with the patch.

Given the downthread discussion, this idea doesn't seem workable.
People are now discussing "what if the module is some kind of
extension". But to me that seems to go against the grain; you'd have to
implement a ton of stuff in order to let "extension-modules" be
installed without on-disk foo.control files.

But what if the module is just a particular kind of *namespace*? I
mean, what if CREATE MODULE is implemented by creating a row in
pg_namespace with nspkind='m'? So a pg_namespace row can refer to
either a regular schema (nspkind='s') or a module. In a schema you can
create objects of any kind just like today, but in a module you're
restricted to having only functions (and maybe also operators? other
types of objects?).

Then, a qualified object name foo.bar() can refer to either the routine
bar() in schema foo, or routine bar in module foo. To the low-level
code it's pretty much the same thing (look the namespace in pg_namespace
just as today).

What other properties do you want modules to have? Are there "private"
functions? (What *is* a private function in this context? I mean, how
does "being in a module" interact with object lookup rules? Does
plpgsql have to be aware that a routine is in a module?)
Are there module-scoped variables? (If so, you probably want Pavel
Stehule's variable patch pushed ahead of time).

--
�lvaro Herrera 39�49'30"S 73�17'W

#9Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Jim Mlodgenski (#3)
Re: Support for CREATE MODULE?

On 02.06.21 16:43, Jim Mlodgenski wrote:

It's already quite hard to tell which part
of a multiply.qualified.name is which, given that SQL says that you can
optionally put a "catalog" (database) name in front of the others.
I really doubt there is a way to shoehorn sub-schemas in there without
creating terrible ambiguities. Is "a.b.c" a reference to object c in
schema b in database a, or is it a reference to object c in sub-schema b
in schema a?

That was the area I had the most difficult part to reason about. I tried to make
some simplifying assumptions by checking if "a" was the current database.
Since we don't support cross database access, if it was not, I assumed "a"
was a schema. I not sure if that would be valid, but it did scope things
to a more manageable problem.

Given that, as you said, the concept of modules is in the SQL standard,
there is surely some guidance in there about how this is supposed to
affect name resolution. So let's start with that. Maybe we won't like
it in the end or whatever, but we should surely look there first.

#10Jim Mlodgenski
jimmy76@gmail.com
In reply to: Peter Eisentraut (#9)
Re: Support for CREATE MODULE?

On Thu, Jun 3, 2021 at 8:49 AM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:

Given that, as you said, the concept of modules is in the SQL standard,
there is surely some guidance in there about how this is supposed to
affect name resolution. So let's start with that. Maybe we won't like
it in the end or whatever, but we should surely look there first.

Studying the spec further, catalog/schema/module are all used to
identify a module-level routine. I don't see it spelled out that
is needs to be in that format of catalog.schema.module.routine to
fully qualify the routine, but it would likely be awkward for users
to come up with an alternative syntax like
(catalog.schema.module).routine or catalog.scheme.module->routine

The way the spec is worded, I read it as that schemas take precedence
over modules regarding path resolution. So for example with 2-level
naming if there is a schema 'foo' and a module 'public.foo' both with
functions 'bar' 'foo.bar' would refer to the schema-level function not
the module-level function. I've not found guidance on throwing catalog
into the mix and 3-level naming. Say we had a catalog 'postgres' with a
schema 'foo' with a function 'bar' and a schema 'postgres' with a module
'foo' with a function 'bar'. What would 'postgres.foo.bar' refer to? If
the SQL was executed from a catalog other than 'postgres', we'd have no
way of knowing if 'foo.bar' existed there. So if it's implementation
dependent, saying schemas take precedence over catalogs may make sense
and 'postgres.foo.bar' refers to the module-level function in the
'postgres' schema.

#11Pavel Stehule
pavel.stehule@gmail.com
In reply to: Jim Mlodgenski (#1)
Re: Support for CREATE MODULE?

Hi

st 2. 6. 2021 v 15:39 odesílatel Jim Mlodgenski <jimmy76@gmail.com> napsal:

Working with users over the years, some have large libraries of server
side code sometimes consisting of 100k+ lines of code over 1000+ functions
and procedures. This usually comes from a migration of a commercial
database like Oracle where it was best practice to put all of your
business logic into stored procedures. In these types of apps, just
managing the code is a challenge. To help classify objects, schemas
are used, but you are at the mercy of a naming convention to show
association. For example, a frequent naming convention would be having
related schemas with the names of foo_bar and foo_baz. For devs, that's
akin to keeping a file like xlog.c in a directory structure like
backend_access_transam instead of backend/access/transam. IMHO, having
a hierarchy makes it simpler to reason about related code bits.

The SQL spec does have a concept of modules that help address this. It's
defined as a persistent object within a schema that contains one or more
routines. It also defines other things like local temporary tables and
path specifications. There are other databases like DB2 that have
implemented module support each with their own way of defining the
routines within the module. The spec doesn't really give guidance on
how to manipulate the objects within the module.

Attached is a POC patch for modules. I modeled it as a sub-schema because
that is more what it seems like to me. It adds additional columns to
pg_namespace and allows for 3-part (or 4 with the database name) naming
of objects within the module. This simple example works with the patch.

CREATE SCHEMA foo;
CREATE MODULE foo.bar
CREATE FUNCTION hello() RETURNS text
LANGUAGE sql
RETURN 'hello'
CREATE FUNCTION world() RETURNS text
LANGUAGE sql
RETURN 'world';
SELECT foo.bar.hello();

Questions
- Do we want to add module support?

- If we do, should it be implemented as a type of namespace or should it
be its own object type that lives in something like pg_module?

- How should users interact with objects within a module? They could be
mostly independent like the current POC or we can introduce a path like
ALTER MODULE foo ADD FUNCTION blah

I never liked the SQL/PSM concept of modules. The possibility to assign
database objects to schema or to modules looks like schizophrenia.

There are only two advantages of modules - a) possibility to define private
objects, b) local scope - the objects from modules shadows external objects
without dependency of search_path.

But both these features are pretty hard to implement in PL/pgSQL - where
expression executor is SQL executor.

Without these features I don't see strong benefits for modules.

Regards

Pavel

Show quoted text

--Jim