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