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.
+
@@ -83,6 +84,7 @@ Complete list of usable sgml source files in this directory.
+
@@ -132,6 +134,7 @@ Complete list of usable sgml source files in this directory.
+
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 @@
+
+
+
+
+ ALTER MODULE
+
+
+
+ ALTER MODULE
+ 7
+ SQL - Language Statements
+
+
+
+ ALTER MODULE
+ change the definition of a module
+
+
+
+
+ALTER MODULE name RENAME TO new_name
+ALTER MODULE name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
+ALTER MODULE module_name CREATE [ OR REPLACE ] FUNCTION
+ function_name ( [ [ argmode ] [ argname ] argtype [ { DEFAULT | = } default_expr ] [, ...] ] )
+ [ RETURNS rettype
+ | RETURNS TABLE ( column_name column_type [, ...] ) ]
+ { LANGUAGE lang_name
+ | TRANSFORM { FOR TYPE type_name } [, ... ]
+ | 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 execution_cost
+ | ROWS result_rows
+ | SUPPORT support_function
+ | SET configuration_parameter { TO value | = value | FROM CURRENT }
+ | AS 'definition'
+ | AS 'obj_file', 'link_symbol'
+ | sql_body
+ } ...
+
+
+
+
+ Description
+
+
+ ALTER MODULE changes the definition of a module.
+
+
+
+ You must own the module to use ALTER MODULE.
+
+
+
+ CREATE FUNCTION defines a new function in the module.
+ CREATE OR REPLACE FUNCTION 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
+ CREATE privilege on the module.
+
+
+
+
+
+ Parameters
+
+
+
+ name
+
+
+ The name of an existing module.
+
+
+
+
+
+ new_name
+
+
+ The new name of the module.
+
+
+
+
+
+ new_owner
+
+
+ The new owner of the module.
+
+
+
+
+
+ For parameters to ALTER MODULE module_name CREATE [ OR REPLACE ] FUNCTION ..., see
+ .
+ For parameters to ALTER MODULE module_name CREATE [ OR REPLACE ] PROCEDURE ..., see
+ .
+
+
+
+ See Also
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+ CREATE MODULE
+
+
+
+ CREATE MODULE
+ 7
+ SQL - Language Statements
+
+
+
+ CREATE MODULE
+ define a new module
+
+
+
+
+CREATE MODULE module_name [ OWNER role_specification ] [ module_element [ ... ] ]
+CREATE MODULE IF NOT EXISTS module_name [ OWNER role_specification ]
+
+where role_specification can be:
+
+ user_name
+ | CURRENT_ROLE
+ | CURRENT_USER
+ | SESSION_USER
+
+
+
+
+ Description
+
+
+ CREATE MODULE 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.
+
+
+
+ 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 qualifying
+ their names with the module name as a prefix.
+
+
+
+ Optionally, CREATE MODULE can include subcommands
+ to create objects (functions, and procedures) within the new module.
+
+
+
+
+ Parameters
+
+
+
+ module_name
+
+
+ The name of a module to be created.
+
+
+
+
+
+ user_name
+
+
+ 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.
+
+
+
+
+
+ module_element
+
+
+ An SQL statement defining an object to be created within the
+ schema. Currently, only CREATE
+ FUNCTION and CREATE PROCEDURE are accepted as clauses
+ within CREATE MODULE.
+
+
+
+
+
+ IF NOT EXISTS
+
+
+ Do nothing (except issuing a notice) if a module with the same name
+ already exists in the current schema.
+
+
+
+
+
+
+
+ Notes
+
+
+ To create a module, the invoking user, if not a superuser, must have the
+ CREATE privilege for the current schema.
+ If the invoking user is a superuser, this check is bypassed.
+
+
+
+
+ Examples
+
+
+ Create a module:
+
+CREATE MODULE mymodule;
+
+
+
+
+ Create a module named test that will be owned by user
+ jill, unless there already is a module named test
+ in the current schema.
+ (It does not matter whether joe owns the pre-existing module.)
+
+CREATE SCHEMA IF NOT EXISTS test OWNER jill;
+
+
+
+
+ Create a module and create a procedure and function within it:
+
+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';
+
+ Notice that the individual subcommands do not end with semicolons.
+
+
+
+ The following is an equivalent way of accomplishing the same result:
+
+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';
+
+
+
+
+
+ Compatibility
+
+
+ The IF NOT EXISTS option is a
+ PostgreSQL extension.
+
+
+
+
+ See Also
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+ DROP MODULE
+
+
+
+ DROP MODULE
+ 7
+ SQL - Language Statements
+
+
+
+ DROP MODULE
+ remove a module
+
+
+
+
+DROP MODULE [ IF EXISTS ] name [, ...] [ CASCADE | RESTRICT ]
+
+
+
+
+ Description
+
+
+ DROP MODULE removes modules from the database.
+
+
+
+ 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.
+
+
+
+
+ Parameters
+
+
+
+ IF EXISTS
+
+
+ Do not throw an error if the module does not exist. A notice is issued
+ in this case.
+
+
+
+
+
+ name
+
+
+ The name of a module.
+
+
+
+
+
+ CASCADE
+
+
+ Automatically drop objects (procedures, functions) that are
+ contained in the module,
+ and in turn all objects that depend on those objects
+ (see ).
+
+
+
+
+
+ RESTRICT
+
+
+ Refuse to drop the module if it contains any objects. This is
+ the default.
+
+
+
+
+
+
+
+ Examples
+
+
+ To remove module mymodule from the database,
+ along with all functions and procedures it contains:
+
+
+DROP MODULE mymoule CASCADE;
+
+
+
+
+ See Also
+
+
+
+
+
+
+
+
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 stmt toplevel_stmt schema_stmt routine_body_stmt
+%type 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 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 opt_in_database
%type OptSchemaName
-%type OptSchemaEltList
+%type OptSchemaEltList OptModuleEltList
%type am_type
@@ -388,7 +390,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type 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 qualified_name insert_target OptConstrFromTable
@@ -404,7 +406,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type grantee_list
%type privilege
%type privileges privilege_list
-%type privilege_target
+ //%type module_privilege
+ //%type module_privileges
+ //%type module_privilege_list
+%type privilege_target module_privilege_target privilege_target_proc
%type function_with_argtypes aggregate_with_argtypes operator_with_argtypes
%type function_with_argtypes_list aggregate_with_argtypes_list operator_with_argtypes_list
%type 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
* 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