*** a/doc/src/sgml/ref/alter_database.sgml --- b/doc/src/sgml/ref/alter_database.sgml *************** *** 28,33 **** ALTER DATABASE name [ [ WITH ] allowconn CONNECTION LIMIT connlimit IS_TEMPLATE istemplate + CATALOG SECURITY catalog_security ALTER DATABASE name RENAME TO new_name *************** *** 139,144 **** ALTER DATABASE name RESET ALL --- 140,157 ---- + + + catalog_security + + + If true, the row level security policies on system catalog tables are + applied to this database. These policies are used to provide multi-tenancy + under that database. If false, all the policies that are present on the + database are removed. + + + new_name *************** *** 203,208 **** ALTER DATABASE name RESET ALL --- 216,227 ---- Role-specific settings override database-specific ones if there is a conflict. + + + The catalog_security option is not supported at + command. This is because of the limitation of creating row level security policies. + The policies can only be created on that database that is connected by the session. + *** a/src/backend/commands/dbcommands.c --- b/src/backend/commands/dbcommands.c *************** *** 42,47 **** --- 42,48 ---- #include "commands/dbcommands.h" #include "commands/dbcommands_xlog.h" #include "commands/defrem.h" + #include "commands/policy.h" #include "commands/seclabel.h" #include "commands/tablespace.h" #include "mb/pg_wchar.h" *************** *** 226,231 **** createdb(const CreatedbStmt *stmt) --- 227,239 ---- errmsg("LOCATION is not supported anymore"), errhint("Consider using tablespaces instead."))); } + else if (strcmp(defel->defname, "catalog_security") == 0) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("catalog security is not supported with create database command."), + errdetail("Enable catalog security using Alter database command."))); + } else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), *************** *** 519,524 **** createdb(const CreatedbStmt *stmt) --- 527,533 ---- new_record[Anum_pg_database_datfrozenxid - 1] = TransactionIdGetDatum(src_frozenxid); new_record[Anum_pg_database_datminmxid - 1] = TransactionIdGetDatum(src_minmxid); new_record[Anum_pg_database_dattablespace - 1] = ObjectIdGetDatum(dst_deftablespace); + new_record[Anum_pg_database_datcatalogsecurity - 1] = BoolGetDatum(false); /* * We deliberately set datacl to default (NULL), rather than copying it *************** *** 1375,1385 **** AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel) --- 1384,1397 ---- ListCell *option; bool dbistemplate = false; bool dballowconnections = true; + bool dbcatalogsecurity = false; int dbconnlimit = -1; DefElem *distemplate = NULL; DefElem *dallowconnections = NULL; DefElem *dconnlimit = NULL; DefElem *dtablespace = NULL; + DefElem *dcatalogsecurity = NULL; + Form_pg_database pg_database_tuple; Datum new_record[Natts_pg_database]; bool new_record_nulls[Natts_pg_database]; bool new_record_repl[Natts_pg_database]; *************** *** 1421,1426 **** AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel) --- 1433,1447 ---- errmsg("conflicting or redundant options"))); dtablespace = defel; } + else if (strcmp(defel->defname, "catalog_security") == 0) + { + if (dcatalogsecurity) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + + dcatalogsecurity = defel; + } else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), *************** *** 1457,1462 **** AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel) --- 1478,1485 ---- (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid connection limit: %d", dbconnlimit))); } + if (dcatalogsecurity && dcatalogsecurity->arg) + dbcatalogsecurity = defGetBoolean(dcatalogsecurity); /* * Get the old tuple. We don't need a lock on the database per se, *************** *** 1476,1487 **** AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel) --- 1499,1517 ---- (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("database \"%s\" does not exist", stmt->dbname))); + pg_database_tuple = (Form_pg_database)GETSTRUCT(tuple); dboid = HeapTupleGetOid(tuple); if (!pg_database_ownercheck(HeapTupleGetOid(tuple), GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE, stmt->dbname); + if (dcatalogsecurity && (dboid != MyDatabaseId)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Enabling/disabling catalog security can be done" + " only to the connected database \"%s\"", stmt->dbname))); + /* * In order to avoid getting locked out and having to go through * standalone mode, we refuse to disallow connections to the database *************** *** 1493,1498 **** AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel) --- 1523,1539 ---- (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot disallow connections for current database"))); + if (dcatalogsecurity && BoolGetDatum(dbcatalogsecurity) && !pg_database_tuple->datcatalogsecurity) + { + CreateCatalogPolicy(); + CommandCounterIncrement(); + } + else if (pg_database_tuple->datcatalogsecurity) + { + RemoveCatalogPolicy(); + CommandCounterIncrement(); + } + /* * Build an updated tuple, perusing the information just obtained */ *************** *** 1515,1520 **** AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel) --- 1556,1566 ---- new_record[Anum_pg_database_datconnlimit - 1] = Int32GetDatum(dbconnlimit); new_record_repl[Anum_pg_database_datconnlimit - 1] = true; } + if (dcatalogsecurity) + { + new_record[Anum_pg_database_datcatalogsecurity - 1] = BoolGetDatum(dbcatalogsecurity); + new_record_repl[Anum_pg_database_datcatalogsecurity - 1] = true; + } newtuple = heap_modify_tuple(tuple, RelationGetDescr(rel), new_record, new_record_nulls, new_record_repl); *** a/src/backend/commands/policy.c --- b/src/backend/commands/policy.c *************** *** 22,31 **** --- 22,75 ---- #include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/objectaccess.h" + #include "catalog/pg_aggregate.h" + #include "catalog/pg_am.h" + #include "catalog/pg_amop.h" + #include "catalog/pg_amproc.h" + #include "catalog/pg_attrdef.h" + #include "catalog/pg_attribute.h" #include "catalog/pg_authid.h" + #include "catalog/pg_cast.h" + #include "catalog/pg_class.h" + #include "catalog/pg_collation.h" + #include "catalog/pg_constraint.h" + #include "catalog/pg_conversion.h" + #include "catalog/pg_db_role_setting.h" + #include "catalog/pg_default_acl.h" + #include "catalog/pg_depend.h" + #include "catalog/pg_description.h" + #include "catalog/pg_enum.h" + #include "catalog/pg_event_trigger.h" + #include "catalog/pg_extension.h" + #include "catalog/pg_foreign_data_wrapper.h" + #include "catalog/pg_foreign_server.h" + #include "catalog/pg_foreign_table.h" + #include "catalog/pg_index.h" + #include "catalog/pg_inherits.h" + #include "catalog/pg_language.h" + #include "catalog/pg_largeobject.h" + #include "catalog/pg_largeobject_metadata.h" + #include "catalog/pg_namespace.h" + #include "catalog/pg_opclass.h" + #include "catalog/pg_operator.h" + #include "catalog/pg_opfamily.h" #include "catalog/pg_policy.h" + #include "catalog/pg_proc.h" + #include "catalog/pg_range.h" + #include "catalog/pg_rewrite.h" + #include "catalog/pg_seclabel.h" + #include "catalog/pg_statistic.h" + #include "catalog/pg_transform.h" + #include "catalog/pg_trigger.h" + #include "catalog/pg_ts_config.h" + #include "catalog/pg_ts_config_map.h" + #include "catalog/pg_ts_dict.h" + #include "catalog/pg_ts_parser.h" + #include "catalog/pg_ts_template.h" #include "catalog/pg_type.h" + #include "catalog/pg_user_mapping.h" #include "commands/policy.h" + #include "executor/spi.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/pg_list.h" *************** *** 44,56 **** --- 88,109 ---- #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/rel.h" + #include "utils/snapmgr.h" #include "utils/syscache.h" + #define CATALOG_POLICY_STRING_SIZE 65535 + static void RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid, void *arg); static char parse_policy_command(const char *cmd_name); static Datum *policy_role_list_to_array(List *roles, int *num_roles); + static bool generate_catalog_create_policy_string(HeapTuple cache_tuple, char *buf); + static bool generate_catalog_drop_policy_string(HeapTuple cache_tuple, char *buf); + + /* variable to identify whether pg_policy relcache is built is in progress or not? */ + bool policyRelcacheBuiltInProgress = false; + /* * Callback to RangeVarGetRelidExtended(). * *************** *** 195,200 **** RelationBuildRowSecurity(Relation relation) --- 248,263 ---- RowSecurityDesc *volatile rsdesc = NULL; /* + * Build the row security descriptor of a relation, once all + * the critical relations are built. + */ + if (!criticalRelcachesBuilt || !criticalSharedRelcachesBuilt) + return; + + if (relation->rd_id == PolicyRelationId && policyRelcacheBuiltInProgress) + return; + + /* * Create a memory context to hold everything associated with this * relation's row security policy. This makes it easy to clean up during * a relcache flush. *************** *** 216,221 **** RelationBuildRowSecurity(Relation relation) --- 279,290 ---- SysScanDesc sscan; HeapTuple tuple; + if (relation->rd_id == PolicyRelationId) + { + Assert(policyRelcacheBuiltInProgress == false); + policyRelcacheBuiltInProgress = true; + } + rsdesc = MemoryContextAllocZero(rscxt, sizeof(RowSecurityDesc)); rsdesc->rscxt = rscxt; *************** *** 325,334 **** RelationBuildRowSecurity(Relation relation) --- 394,416 ---- /* Delete rscxt, first making sure it isn't active */ MemoryContextSwitchTo(oldcxt); MemoryContextDelete(rscxt); + + if (relation->rd_id == PolicyRelationId) + { + Assert(policyRelcacheBuiltInProgress == true); + policyRelcacheBuiltInProgress = false; + } + PG_RE_THROW(); } PG_END_TRY(); + if (relation->rd_id == PolicyRelationId) + { + Assert(policyRelcacheBuiltInProgress == true); + policyRelcacheBuiltInProgress = false; + } + /* Success --- attach the policy descriptor to the relcache entry */ relation->rd_rsdesc = rsdesc; } *************** *** 407,412 **** RemovePolicyById(Oid policy_id) --- 489,1041 ---- heap_close(pg_policy_rel, RowExclusiveLock); } + static bool + generate_catalog_create_policy_string(HeapTuple tuple, char *buf) + { + bool can_create_policy = false; + Form_pg_class pg_class_tuple; + Oid relationid; + + pg_class_tuple = (Form_pg_class)GETSTRUCT(tuple); + relationid = HeapTupleGetOid(tuple); + + if (IsSharedRelation(relationid)) + return can_create_policy; + + switch (relationid) + { + /* + * Following catalog tables data is accessible to all roles. + * So they doesn't need any speicific RLS policies on them. + */ + case AggregateRelationId: + case AccessMethodRelationId: + case AccessMethodOperatorRelationId: + case AccessMethodProcedureRelationId: + break; + case AttrDefaultRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " (has_column_privilege(adrelid, adnum,'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case AttributeRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " (has_column_privilege(attrelid, attnum,'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case CastRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR has_cast_privilege(oid, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case RelationRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR has_table_privilege(oid,'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case CollationRelationId: + /*sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR (pg_get_userbyid(collowner) = current_user))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true;*/ + break; + case ConstraintRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR has_constraint_privilege(oid,'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case ConversionRelationId: + /*sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR (pg_get_userbyid(conowner) = current_user))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true;*/ + break; + case DefaultAclRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " (pg_get_userbyid(defaclrole) = current_user)", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case DependRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((classid = (select oid from pg_class where relname = 'pg_aggregate'))" + " OR (classid = (select oid from pg_class where relname = 'pg_cast') AND has_cast_privilege(objid, 'any'))" + " OR (classid = (select oid from pg_class where relname = 'pg_collation'))" + " OR (classid = (select oid from pg_class where relname = 'pg_attribute') AND has_column_privilege(objid, objsubid::smallint, 'any'))" + " OR (classid = (select oid from pg_class where relname = 'pg_constraint') AND has_constraint_privilege(objid, 'any'))" + " OR (classid = (select oid from pg_class where relname = 'pg_conversion'))" + " OR (classid = (select oid from pg_class where relname = 'pg_type') AND has_type_privilege(objid, 'any'))" + " OR (classid = (select oid from pg_class where relname = 'pg_extension'))" + " OR (classid = (select oid from pg_class where relname = 'pg_event_trigger'))" + " OR (classid = (select oid from pg_class where relname = 'pg_foreign_data_wrapper') AND has_foreign_data_wrapper_privilege(objid, 'any'))" + " OR (classid = (select oid from pg_class where relname = 'pg_class') AND has_table_privilege(objid,'any'))" + " OR (classid = (select oid from pg_class where relname = 'pg_proc') AND has_function_privilege(objid, 'any'))" + " OR (classid = (select oid from pg_class where relname = 'pg_am'))" + " OR (classid = (select oid from pg_class where relname = 'pg_amop'))" + " OR (classid = (select oid from pg_class where relname = 'pg_amproc'))" + " OR (classid = (select oid from pg_class where relname = 'pg_attdef') AND has_column_default_privilege(objid, 'any'))" + " OR (classid = (select oid from pg_class where relname = 'pg_language') AND has_language_privilege(objid, 'any'))" + " OR (classid = (select oid from pg_class where relname = 'pg_largeobject'))" + " OR (classid = (select oid from pg_class where relname = 'pg_opclass'))" + " OR (classid = (select oid from pg_class where relname = 'pg_operator'))" + " OR (classid = (select oid from pg_class where relname = 'pg_opfamily'))" + " OR (classid = (select oid from pg_class where relname = 'pg_enum'))" + " OR (classid = (select oid from pg_class where relname = 'pg_event_trigger'))" + " OR (classid = (select oid from pg_class where relname = 'pg_extension'))" + " OR (classid = (select oid from pg_class where relname = 'pg_policy') AND has_policy_privilege(objid, 'any'))" + " OR (classid = (select oid from pg_class where relname = 'pg_rewrite'))" + " OR (classid = (select oid from pg_class where relname = 'pg_namespace') AND has_schema_privilege(objid, 'any'))" + " OR (classid = (select oid from pg_class where relname = 'pg_foreign_server') AND has_server_privilege(objid, 'any'))" + " OR (classid = (select oid from pg_class where relname = 'pg_tablespace') AND has_tablespace_privilege(objid, 'any'))" + " OR (classid = (select oid from pg_class where relname = 'pg_ts_dict'))" + " OR (classid = (select oid from pg_class where relname = 'pg_ts_parser'))" + " OR (classid = (select oid from pg_class where relname = 'pg_ts_config'))" + " OR (classid = (select oid from pg_class where relname = 'pg_ts_template'))" + " OR (classid = (select oid from pg_class where relname = 'pg_transform'))" + " OR (classid = (select oid from pg_class where relname = 'pg_trigger') AND has_trigger_privilege(objid, 'any'))" + " OR (classid = (select oid from pg_class where relname = 'pg_user_mapping') AND has_user_mapping_privilege(objid, 'any')))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case DescriptionRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((classoid = (select oid from pg_class where relname = 'pg_aggregate'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_cast') AND has_cast_privilege(objoid, 'any'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_collation'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_attribute') AND has_column_privilege(objoid, objsubid::smallint, 'any'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_constraint') AND has_constraint_privilege(objoid, 'any'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_conversion'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_type') AND has_type_privilege(objoid, 'any'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_extension'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_event_trigger'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_foreign_data_wrapper') AND has_foreign_data_wrapper_privilege(objoid, 'any'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_class') AND has_table_privilege(objoid,'any'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_proc') AND has_function_privilege(objoid, 'any'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_largeobject'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_opclass'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_operator'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_opfamily'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_largeobject_metadata'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_policy') AND has_policy_privilege(objoid, 'any'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_rewrite'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_namespace') AND has_schema_privilege(objoid, 'any'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_foreign_server') AND has_server_privilege(objoid, 'any'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_tablespace') AND has_tablespace_privilege(objoid, 'any'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_range'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_ts_dict'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_ts_parser'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_ts_config'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_ts_template'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_transform'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_trigger') AND has_trigger_privilege(objoid, 'any')))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case EnumRelationId: + break; + case EventTriggerRelationId: + break; + case ExtensionRelationId: + break; + case ForeignDataWrapperRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR has_foreign_data_wrapper_privilege(oid,'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case ForeignServerRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR has_server_privilege(oid,'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case ForeignTableRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " (has_table_privilege(ftrelid, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case IndexRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " (has_table_privilege(indrelid, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case InheritsRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " (has_table_privilege(inhrelid, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case LanguageRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR has_language_privilege(oid, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case LargeObjectRelationId: + break; + case LargeObjectMetadataRelationId: + break; + case NamespaceRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR has_schema_privilege(oid, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case OperatorClassRelationId: + /*sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR (pg_get_userbyid(opcowner) = current_user))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true;*/ + break; + case OperatorRelationId: + /*sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR (pg_get_userbyid(oprowner) = current_user))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true;*/ + break; + case OperatorFamilyRelationId: + /*sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR (pg_get_userbyid(opfowner) = current_user))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true;*/ + break; + case PolicyRelationId: + /* + * Only user with bypass rls or owner of the table can view the + * policies on the table, unless the forcesecurity is specified + * for the owners also. + */ + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " (false)", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case ProcedureRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR has_function_privilege(oid, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case RangeRelationId: + break; + case RewriteRelationId: + break; + case SecLabelRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((classoid = (select oid from pg_class where relname = 'pg_aggregate'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_attribute') AND has_column_privilege(objoid, objsubid::smallint, 'any'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_type') AND has_type_privilege(objoid, 'any'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_class') AND has_table_privilege(objoid,'any'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_proc') AND has_function_privilege(objoid, 'any'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_language') AND has_language_privilege(objoid, 'any'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_largeobject'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_event_trigger'))" + " OR (classoid = (select oid from pg_class where relname = 'pg_tablespace') AND has_tablespace_privilege(objoid, 'any')))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case StatisticRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((starelid < 16384) OR has_column_privilege(starelid, staattnum, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case TransformRelationId: + break; + case TriggerRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR has_table_privilege(tgrelid, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case TSConfigRelationId: + break; + case TSConfigMapRelationId: + break; + case TSDictionaryRelationId: + break; + case TSParserRelationId: + break; + case TSTemplateRelationId: + break; + case TypeRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR has_type_privilege(oid, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case UserMappingRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR pg_has_role(umuser, 'any')" + " OR has_server_privilege(umserver, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + default: + can_create_policy = false; + break; + } + + return can_create_policy; + } + + static bool + generate_catalog_drop_policy_string(HeapTuple tuple, char *buf) + { + bool can_create_policy = false; + Form_pg_class pg_class_tuple; + Oid relationid; + + pg_class_tuple = (Form_pg_class)GETSTRUCT(tuple); + relationid = HeapTupleGetOid(tuple); + + if (IsSharedRelation(relationid)) + return can_create_policy; + + switch (relationid) + { + /* + * Following catalog tables data is accessible to all roles. + * So they doesn't need any speicific RLS policies on them. + */ + case AggregateRelationId: + case AccessMethodRelationId: + case AccessMethodOperatorRelationId: + case AccessMethodProcedureRelationId: + case CollationRelationId: + case ConversionRelationId: + case EnumRelationId: + case EventTriggerRelationId: + case ExtensionRelationId: + case LargeObjectRelationId: + case LargeObjectMetadataRelationId: + case OperatorClassRelationId: + case OperatorRelationId: + case OperatorFamilyRelationId: + case RangeRelationId: + case RewriteRelationId: + case TransformRelationId: + case TSConfigRelationId: + case TSConfigMapRelationId: + case TSDictionaryRelationId: + case TSParserRelationId: + case TSTemplateRelationId: + break; + + case AttrDefaultRelationId: + case AttributeRelationId: + case CastRelationId: + case RelationRelationId: + case ConstraintRelationId: + case DefaultAclRelationId: + case DependRelationId: + case DescriptionRelationId: + case ForeignDataWrapperRelationId: + case ForeignServerRelationId: + case ForeignTableRelationId: + case IndexRelationId: + case InheritsRelationId: + case LanguageRelationId: + case NamespaceRelationId: + case PolicyRelationId: + case ProcedureRelationId: + case SecLabelRelationId: + case StatisticRelationId: + case TriggerRelationId: + case TypeRelationId: + case UserMappingRelationId: + sprintf(buf, "drop policy %s_read_own_data on %s", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + + default: + can_create_policy = false; + break; + } + + return can_create_policy; + } + + /* + * CreateCatalogPolicy - + * handles the execution of the ALTER DATBASE ROW LEVEL SECUTIRY = TRUE command. + */ + void + CreateCatalogPolicy() + { + Relation rel; + ScanKeyData scankey; + SysScanDesc scan; + HeapTuple tuple; + bool allow_sytem_table_mods_old; + char *buf; + + /* + * Get all catalog relations from pg_class system table and + * enable the row level security along with the catalog policy + * command. + */ + SPI_connect(); + PushActiveSnapshot(GetTransactionSnapshot()); + + rel = heap_open(RelationRelationId, RowExclusiveLock); + ScanKeyInit(&scankey, + ObjectIdAttributeNumber, + BTLessStrategyNumber, F_OIDLT, + ObjectIdGetDatum(FirstNormalObjectId)); + scan = systable_beginscan(rel, ClassOidIndexId, true, + NULL, 1, &scankey); + + buf = palloc(CATALOG_POLICY_STRING_SIZE); + allow_sytem_table_mods_old = allowSystemTableMods; + allowSystemTableMods = true; + + PG_TRY(); + { + while ((tuple = systable_getnext(scan)) != NULL) + { + int ret; + HeapTuple cache_tuple; + Form_pg_class pg_class_tuple; + bool can_create_policy; + + pg_class_tuple = (Form_pg_class)GETSTRUCT(tuple); + if (pg_class_tuple->relkind != RELKIND_RELATION) + continue; + + cache_tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(HeapTupleGetOid(tuple))); + + if (!HeapTupleIsValid(cache_tuple)) + elog(ERROR, "cache lookup failed for relation %u", HeapTupleGetOid(tuple)); + + can_create_policy = generate_catalog_create_policy_string(cache_tuple, buf); + if (!can_create_policy) + { + heap_freetuple(cache_tuple); + continue; + } + + ret = SPI_execute(buf, false, 0); + if (ret != SPI_OK_UTILITY) + elog(ERROR, "Creating policy failed : error code %d", ret); + + ((Form_pg_class)GETSTRUCT(cache_tuple))->relrowsecurity = true; + heap_inplace_update(rel, cache_tuple); + + heap_freetuple(cache_tuple); + } + } + PG_CATCH(); + { + allowSystemTableMods = allow_sytem_table_mods_old; + PG_RE_THROW(); + } + PG_END_TRY(); + + allowSystemTableMods = allow_sytem_table_mods_old; + pfree(buf); + + systable_endscan(scan); + heap_close(rel, NoLock); + + SPI_finish(); + PopActiveSnapshot(); + } + + /* + * RemoveCatalogPolicy - + * handles the execution of the ALTER DATBASE ROW LEVEL SECUTIRY = FALSE command. + */ + void + RemoveCatalogPolicy() + { + Relation rel; + ScanKeyData scankey; + SysScanDesc scan; + HeapTuple tuple; + Form_pg_class pg_class_tuple; + bool allow_sytem_table_mods_old; + char *buf; + + /* + * Get all catalog relations from pg_class system table and + * enable the row level security along with the catalog policy + * command. + */ + SPI_connect(); + PushActiveSnapshot(GetTransactionSnapshot()); + + rel = heap_open(RelationRelationId, RowExclusiveLock); + ScanKeyInit(&scankey, + ObjectIdAttributeNumber, + BTLessStrategyNumber, F_OIDLT, + ObjectIdGetDatum(FirstNormalObjectId)); + scan = systable_beginscan(rel, ClassOidIndexId, true, + NULL, 1, &scankey); + + buf = palloc(CATALOG_POLICY_STRING_SIZE); + + allow_sytem_table_mods_old = allowSystemTableMods; + allowSystemTableMods = true; + + PG_TRY(); + { + while ((tuple = systable_getnext(scan)) != NULL) + { + int ret; + HeapTuple cache_tuple; + bool can_drop_policy; + + pg_class_tuple = (Form_pg_class)GETSTRUCT(tuple); + if (pg_class_tuple->relkind != RELKIND_RELATION) + continue; + + cache_tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(HeapTupleGetOid(tuple))); + if (!HeapTupleIsValid(cache_tuple)) + elog(ERROR, "cache lookup failed for relation %u", HeapTupleGetOid(tuple)); + + can_drop_policy = generate_catalog_drop_policy_string(cache_tuple, buf); + if (!can_drop_policy) + { + heap_freetuple(cache_tuple); + continue; + } + + ret = SPI_execute(buf, false, 0); + if (ret != SPI_OK_UTILITY) + elog(ERROR, "Creating policy failed : error code %d", ret); + + ((Form_pg_class)GETSTRUCT(cache_tuple))->relrowsecurity = false; + heap_inplace_update(rel, cache_tuple); + + heap_freetuple(cache_tuple); + } + } + PG_CATCH(); + { + allowSystemTableMods = allow_sytem_table_mods_old; + PG_RE_THROW(); + } + PG_END_TRY(); + + allowSystemTableMods = allow_sytem_table_mods_old; + + pfree(buf); + systable_endscan(scan); + heap_close(rel, NoLock); + + SPI_finish(); + PopActiveSnapshot(); + } + /* * CreatePolicy - * handles the execution of the CREATE POLICY command. *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** *** 8871,8876 **** createdb_opt_name: --- 8871,8877 ---- | OWNER { $$ = pstrdup($1); } | TABLESPACE { $$ = pstrdup($1); } | TEMPLATE { $$ = pstrdup($1); } + | CATALOG_P SECURITY { $$ = pstrdup("catalog_security"); } ; /* *** a/src/backend/utils/adt/acl.c --- b/src/backend/utils/adt/acl.c *************** *** 17,27 **** #include #include "access/htup_details.h" #include "catalog/namespace.h" #include "catalog/pg_authid.h" #include "catalog/pg_auth_members.h" ! #include "catalog/pg_type.h" #include "catalog/pg_class.h" #include "commands/dbcommands.h" #include "commands/proclang.h" #include "commands/tablespace.h" --- 17,35 ---- #include #include "access/htup_details.h" + #include "access/sysattr.h" + #include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/pg_authid.h" #include "catalog/pg_auth_members.h" ! #include "catalog/pg_attrdef.h" ! #include "catalog/pg_cast.h" #include "catalog/pg_class.h" + #include "catalog/pg_constraint.h" + #include "catalog/pg_policy.h" + #include "catalog/pg_trigger.h" + #include "catalog/pg_type.h" + #include "catalog/pg_user_mapping.h" #include "commands/dbcommands.h" #include "commands/proclang.h" #include "commands/tablespace.h" *************** *** 31,36 **** --- 39,45 ---- #include "utils/acl.h" #include "utils/builtins.h" #include "utils/catcache.h" + #include "utils/fmgroids.h" #include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/memutils.h" *************** *** 5267,5269 **** get_rolespec_name(const Node *node) --- 5276,5568 ---- return rolename; } + + /* + * has_cast_privilege_id + * Check user privileges on a cast given + * cast oid, and text priv name. + */ + Datum + has_cast_privilege_id(PG_FUNCTION_ARGS) + { + Oid castoid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_P(1); + Oid roleid; + AclMode mode; + AclResult aclresult1; + AclResult aclresult2; + Relation castDesc; + ScanKeyData skey[1]; + SysScanDesc rcscan; + HeapTuple tup; + Form_pg_cast castForm; + + roleid = GetUserId(); + mode = convert_server_priv_string(priv_type_text); + + castDesc = heap_open(CastRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(castoid)); + + rcscan = systable_beginscan(castDesc, CastOidIndexId, true, + NULL, 1, skey); + + tup = systable_getnext(rcscan); + + if (!HeapTupleIsValid(tup)) + { + systable_endscan(rcscan); + heap_close(castDesc, AccessShareLock); + PG_RETURN_NULL(); + } + + castForm = (Form_pg_cast)GETSTRUCT(tup); + + aclresult1 = pg_type_aclcheck(castForm->castsource, roleid, mode); + aclresult2 = pg_type_aclcheck(castForm->casttarget, roleid, mode); + + systable_endscan(rcscan); + heap_close(castDesc, AccessShareLock); + PG_RETURN_BOOL((aclresult1 == ACLCHECK_OK) || (aclresult2 == ACLCHECK_OK)); + } + + /* + * has_constraint_privilege_id + * Check user privileges on a constraint given + * constraint oid, and text priv name. + */ + Datum + has_constraint_privilege_id(PG_FUNCTION_ARGS) + { + Oid constraintoid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_P(1); + Oid roleid; + AclMode mode; + AclResult aclresult; + Relation constraintDesc; + ScanKeyData skey[1]; + SysScanDesc rcscan; + HeapTuple tup; + Form_pg_constraint constraintForm; + + roleid = GetUserId(); + mode = convert_server_priv_string(priv_type_text); + + constraintDesc = heap_open(ConstraintRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(constraintoid)); + + rcscan = systable_beginscan(constraintDesc, ConstraintOidIndexId, true, + NULL, 1, skey); + + tup = systable_getnext(rcscan); + + if (!HeapTupleIsValid(tup)) + { + systable_endscan(rcscan); + heap_close(constraintDesc, AccessShareLock); + PG_RETURN_NULL(); + } + + constraintForm = (Form_pg_constraint)GETSTRUCT(tup); + + if (constraintForm->contypid) + aclresult = pg_type_aclcheck(constraintForm->contypid, roleid, mode); + else + aclresult = pg_class_aclcheck(constraintForm->conrelid, roleid, mode); + + systable_endscan(rcscan); + heap_close(constraintDesc, AccessShareLock); + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); + } + + /* + * has_column_default_privilege_id + * Check user privileges on a column default given + * attrdefault oid, and text priv name. + */ + Datum + has_column_default_privilege_id(PG_FUNCTION_ARGS) + { + Oid attrdefaulttoid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_P(1); + Oid roleid; + AclMode mode; + int privresult; + Relation attrDefaultDesc; + ScanKeyData skey[1]; + SysScanDesc rcscan; + HeapTuple tup; + Form_pg_attrdef attrDefForm; + + roleid = GetUserId(); + mode = convert_server_priv_string(priv_type_text); + + attrDefaultDesc = heap_open(AttrDefaultRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(attrdefaulttoid)); + + rcscan = systable_beginscan(attrDefaultDesc, AttrDefaultOidIndexId, true, + NULL, 1, skey); + + tup = systable_getnext(rcscan); + + if (!HeapTupleIsValid(tup)) + { + systable_endscan(rcscan); + heap_close(attrDefaultDesc, AccessShareLock); + PG_RETURN_NULL(); + } + + attrDefForm = (Form_pg_attrdef)GETSTRUCT(tup); + + privresult = column_privilege_check(attrDefForm->adrelid, attrDefForm->adnum, roleid, mode); + + systable_endscan(rcscan); + heap_close(attrDefaultDesc, AccessShareLock); + + if (privresult < 0) + PG_RETURN_NULL(); + PG_RETURN_BOOL(privresult); + } + + /* + * has_policy_privilege_id + * Check user privileges on a policy given + * policy oid, and text priv name. + */ + Datum + has_policy_privilege_id(PG_FUNCTION_ARGS) + { + Oid policyoid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_P(1); + Oid roleid; + AclMode mode; + AclResult aclresult; + Relation policyDesc; + ScanKeyData skey[1]; + SysScanDesc rcscan; + HeapTuple tup; + Form_pg_policy policyForm; + + roleid = GetUserId(); + mode = convert_server_priv_string(priv_type_text); + + policyDesc = heap_open(PolicyRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(policyoid)); + + rcscan = systable_beginscan(policyDesc, PolicyOidIndexId, true, + NULL, 1, skey); + + tup = systable_getnext(rcscan); + + if (!HeapTupleIsValid(tup)) + { + systable_endscan(rcscan); + heap_close(policyDesc, AccessShareLock); + PG_RETURN_NULL(); + } + + policyForm = (Form_pg_policy)GETSTRUCT(tup); + + aclresult = pg_class_aclcheck(policyForm->polrelid, roleid, mode); + + systable_endscan(rcscan); + heap_close(policyDesc, AccessShareLock); + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); + } + + /* + * has_trigger_privilege_id + * Check user privileges on a trigger given + * trigger oid, and text priv name. + */ + Datum + has_trigger_privilege_id(PG_FUNCTION_ARGS) + { + Oid triggeroid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_P(1); + Oid roleid; + AclMode mode; + AclResult aclresult; + Relation triggerDesc; + ScanKeyData skey[1]; + SysScanDesc rcscan; + HeapTuple tup; + Form_pg_trigger triggerForm; + + roleid = GetUserId(); + mode = convert_server_priv_string(priv_type_text); + + triggerDesc = heap_open(PolicyRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(triggeroid)); + + rcscan = systable_beginscan(triggerDesc, PolicyOidIndexId, true, + NULL, 1, skey); + + tup = systable_getnext(rcscan); + + if (!HeapTupleIsValid(tup)) + { + systable_endscan(rcscan); + heap_close(triggerDesc, AccessShareLock); + PG_RETURN_NULL(); + } + + triggerForm = (Form_pg_trigger)GETSTRUCT(tup); + + aclresult = pg_class_aclcheck(triggerForm->tgrelid, roleid, mode); + + systable_endscan(rcscan); + heap_close(triggerDesc, AccessShareLock); + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); + } + + /* + * has_user_mapping_privilege_id + * Check user privileges on a user mapping given + * user_mapping oid, and text priv name. + */ + Datum + has_user_mapping_privilege_id(PG_FUNCTION_ARGS) + { + Oid usermapoid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_P(1); + Oid roleid; + AclMode mode; + AclResult aclresult1; + AclResult aclresult2; + HeapTuple tup; + Form_pg_user_mapping usermapForm; + + roleid = GetUserId(); + mode = convert_server_priv_string(priv_type_text); + + tup = SearchSysCache1(USERMAPPINGOID, usermapoid); + if (!HeapTupleIsValid(tup)) + PG_RETURN_NULL(); + + usermapForm = (Form_pg_user_mapping)GETSTRUCT(tup); + + aclresult1 = pg_role_aclcheck(usermapForm->umuser, roleid, mode); + aclresult2 = pg_foreign_server_aclcheck(usermapForm->umserver, roleid, mode); + + PG_RETURN_BOOL((aclresult1 == ACLCHECK_OK) || (aclresult2 == ACLCHECK_OK)); + } *** a/src/backend/utils/cache/relcache.c --- b/src/backend/utils/cache/relcache.c *************** *** 2076,2081 **** RelationClearRelation(Relation relation, bool rebuild) --- 2076,2083 ---- */ if (relation->rd_isnailed) { + HeapTuple pg_class_tuple; + RelationInitPhysicalAddr(relation); if (relation->rd_rel->relkind == RELKIND_INDEX) *************** *** 2084,2089 **** RelationClearRelation(Relation relation, bool rebuild) --- 2086,2112 ---- if (relation->rd_refcnt > 1 && IsTransactionState()) RelationReloadIndexInfo(relation); } + + /* + * A nailed-in system relation never ever blow away from rel cache, because + * we'd be unable to recover. So for such relations, we will update the + * row security descriptor if it is enabled. Usually this happens during + * RelationBuildDesc function, but for nailed-in system relations, we will + * do it here. + */ + if (criticalRelcachesBuilt + && criticalSharedRelcachesBuilt + && IsTransactionState()) + { + /* + * find the tuple in pg_class corresponding to the given relation id + */ + pg_class_tuple = ScanPgRelation(RelationGetRelid(relation), true, false); + + if (((Form_pg_class)GETSTRUCT(pg_class_tuple))->relrowsecurity) + RelationBuildRowSecurity(relation); + heap_freetuple(pg_class_tuple); + } return; } *** a/src/include/catalog/pg_database.h --- b/src/include/catalog/pg_database.h *************** *** 43,49 **** CATALOG(pg_database,1262) BKI_SHARED_RELATION BKI_ROWTYPE_OID(1248) BKI_SCHEMA_M TransactionId datfrozenxid; /* all Xids < this are frozen in this DB */ TransactionId datminmxid; /* all multixacts in the DB are >= this */ Oid dattablespace; /* default table space for this DB */ ! #ifdef CATALOG_VARLEN /* variable-length fields start here */ aclitem datacl[1]; /* access permissions */ #endif --- 43,49 ---- TransactionId datfrozenxid; /* all Xids < this are frozen in this DB */ TransactionId datminmxid; /* all multixacts in the DB are >= this */ Oid dattablespace; /* default table space for this DB */ ! bool datcatalogsecurity; /* catalog security is enabled? */ #ifdef CATALOG_VARLEN /* variable-length fields start here */ aclitem datacl[1]; /* access permissions */ #endif *************** *** 60,81 **** typedef FormData_pg_database *Form_pg_database; * compiler constants for pg_database * ---------------- */ ! #define Natts_pg_database 13 ! #define Anum_pg_database_datname 1 ! #define Anum_pg_database_datdba 2 ! #define Anum_pg_database_encoding 3 ! #define Anum_pg_database_datcollate 4 ! #define Anum_pg_database_datctype 5 ! #define Anum_pg_database_datistemplate 6 ! #define Anum_pg_database_datallowconn 7 ! #define Anum_pg_database_datconnlimit 8 ! #define Anum_pg_database_datlastsysoid 9 ! #define Anum_pg_database_datfrozenxid 10 ! #define Anum_pg_database_datminmxid 11 ! #define Anum_pg_database_dattablespace 12 ! #define Anum_pg_database_datacl 13 ! DATA(insert OID = 1 ( template1 PGUID ENCODING "LC_COLLATE" "LC_CTYPE" t t -1 0 0 1 1663 _null_)); SHDESCR("default template for new databases"); #define TemplateDbOid 1 --- 60,82 ---- * compiler constants for pg_database * ---------------- */ ! #define Natts_pg_database 14 ! #define Anum_pg_database_datname 1 ! #define Anum_pg_database_datdba 2 ! #define Anum_pg_database_encoding 3 ! #define Anum_pg_database_datcollate 4 ! #define Anum_pg_database_datctype 5 ! #define Anum_pg_database_datistemplate 6 ! #define Anum_pg_database_datallowconn 7 ! #define Anum_pg_database_datconnlimit 8 ! #define Anum_pg_database_datlastsysoid 9 ! #define Anum_pg_database_datfrozenxid 10 ! #define Anum_pg_database_datminmxid 11 ! #define Anum_pg_database_dattablespace 12 ! #define Anum_pg_database_datcatalogsecurity 13 ! #define Anum_pg_database_datacl 14 ! DATA(insert OID = 1 ( template1 PGUID ENCODING "LC_COLLATE" "LC_CTYPE" t t -1 0 0 1 1663 f _null_)); SHDESCR("default template for new databases"); #define TemplateDbOid 1 *** a/src/include/catalog/pg_proc.h --- b/src/include/catalog/pg_proc.h *************** *** 3642,3647 **** DESCR("current user privilege on role by role name"); --- 3642,3665 ---- DATA(insert OID = 2710 ( pg_has_role PGNSP PGUID 12 1 0 0 0 f f f f t f s s 2 0 16 "26 25" _null_ _null_ _null_ _null_ _null_ pg_has_role_id _null_ _null_ _null_ )); DESCR("current user privilege on role by role oid"); + DATA(insert OID = 3315 (has_cast_privilege PGNSP PGUID 12 1 0 0 0 f f f f t f s s 2 0 16 "26 25" _null_ _null_ _null_ _null_ _null_ has_cast_privilege_id _null_ _null_ _null_)); + DESCR("current user privilege on cast by cast oid"); + + DATA(insert OID = 3316 (has_constraint_privilege PGNSP PGUID 12 1 0 0 0 f f f f t f s s 2 0 16 "26 25" _null_ _null_ _null_ _null_ _null_ has_constraint_privilege_id _null_ _null_ _null_)); + DESCR("current user privilege on contrainst by constraint oid"); + + DATA(insert OID = 3317 (has_column_default_privilege PGNSP PGUID 12 1 0 0 0 f f f f t f s s 2 0 16 "26 25" _null_ _null_ _null_ _null_ _null_ has_column_default_privilege_id _null_ _null_ _null_)); + DESCR("current user privilege on attrdefault by attrdefault oid"); + + DATA(insert OID = 3318 (has_policy_privilege PGNSP PGUID 12 1 0 0 0 f f f f t f s s 2 0 16 "26 25" _null_ _null_ _null_ _null_ _null_ has_policy_privilege_id _null_ _null_ _null_)); + DESCR("current user privilege on policy by policy oid"); + + DATA(insert OID = 3319 (has_trigger_privilege PGNSP PGUID 12 1 0 0 0 f f f f t f s s 2 0 16 "26 25" _null_ _null_ _null_ _null_ _null_ has_trigger_privilege_id _null_ _null_ _null_)); + DESCR("current user privilege on trigger by trigger oid"); + + DATA(insert OID = 3320 (has_user_mapping_privilege PGNSP PGUID 12 1 0 0 0 f f f f t f s s 2 0 16 "26 25" _null_ _null_ _null_ _null_ _null_ has_user_mapping_privilege_id _null_ _null_ _null_)); + DESCR("current user privilege on user mapping by user_mapping oid"); + DATA(insert OID = 1269 ( pg_column_size PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 23 "2276" _null_ _null_ _null_ _null_ _null_ pg_column_size _null_ _null_ _null_ )); DESCR("bytes required to store the value, perhaps with compression"); DATA(insert OID = 2322 ( pg_tablespace_size PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_tablespace_size_oid _null_ _null_ _null_ )); *** a/src/include/commands/policy.h --- b/src/include/commands/policy.h *************** *** 33,36 **** extern ObjectAddress rename_policy(RenameStmt *stmt); --- 33,39 ---- extern bool relation_has_policies(Relation rel); + extern void CreateCatalogPolicy(void); + extern void RemoveCatalogPolicy(void); + #endif /* POLICY_H */ *** a/src/include/utils/builtins.h --- b/src/include/utils/builtins.h *************** *** 106,111 **** extern Datum pg_has_role_id_name(PG_FUNCTION_ARGS); --- 106,117 ---- extern Datum pg_has_role_id_id(PG_FUNCTION_ARGS); extern Datum pg_has_role_name(PG_FUNCTION_ARGS); extern Datum pg_has_role_id(PG_FUNCTION_ARGS); + extern Datum has_cast_privilege_id(PG_FUNCTION_ARGS); + extern Datum has_constraint_privilege_id(PG_FUNCTION_ARGS); + extern Datum has_column_default_privilege_id(PG_FUNCTION_ARGS); + extern Datum has_policy_privilege_id(PG_FUNCTION_ARGS); + extern Datum has_trigger_privilege_id(PG_FUNCTION_ARGS); + extern Datum has_user_mapping_privilege_id(PG_FUNCTION_ARGS); /* bool.c */ extern Datum boolin(PG_FUNCTION_ARGS); *** /dev/null --- b/src/test/regress/expected/multitenancy.out *************** *** 0 **** --- 1,657 ---- + -- Create roles that are used by the following tests + create role tenancy_user1 login createdb; + create role tenancy_user2 login createdb; + create schema tenancy_user1; + grant all on schema tenancy_user1 to tenancy_user1; + create schema tenancy_user2; + grant all on schema tenancy_user2 to tenancy_user2; + -- Create a type to test + CREATE TYPE tenancytesttype; + CREATE FUNCTION tenancytesttype_in(cstring) + RETURNS tenancytesttype + AS 'textin' + LANGUAGE internal STRICT IMMUTABLE; + NOTICE: return type tenancytesttype is only a shell + CREATE FUNCTION tenancytesttype_out(tenancytesttype) + RETURNS cstring + AS 'textout' + LANGUAGE internal STRICT IMMUTABLE; + NOTICE: argument type tenancytesttype is only a shell + CREATE TYPE tenancytesttype ( + internallength = variable, + input = tenancytesttype_in, + output = tenancytesttype_out, + alignment = int4 + ); + COMMENT ON TYPE tenancytesttype IS 'tenancytesttype'; + REVOKE ALL ON FUNCTION tenancytesttype_in(cstring) FROM public; + REVOKE ALL ON FUNCTION tenancytesttype_out(tenancytesttype) FROM public; + GRANT ALL ON FUNCTION tenancytesttype_in(cstring) TO tenancy_user2; + GRANT ALL ON FUNCTION tenancytesttype_out(tenancytesttype) TO tenancy_user2; + REVOKE ALL ON TYPE tenancytesttype FROM public; + GRANT ALL ON TYPE tenancytesttype TO tenancy_user2; + -- Temp Language create with owner as tenancy_user2 + CREATE TRUSTED LANGUAGE tenancy_lang1 HANDLER plpgsql_call_handler; + REVOKE ALL ON LANGUAGE tenancy_lang1 FROM public; + ALTER LANGUAGE tenancy_lang1 OWNER TO tenancy_user2; + --Foriegn data wrapper, server and user mapping creation for user tenancy_user2 + CREATE FOREIGN DATA WRAPPER tenancy_wrapper; + CREATE SERVER tenancy_server FOREIGN DATA WRAPPER tenancy_wrapper; + CREATE USER MAPPING FOR tenancy_user2 SERVER tenancy_server; + REVOKE ALL ON FOREIGN DATA WRAPPER tenancy_wrapper FROM public; + REVOKE ALL ON FOREIGN SERVER tenancy_server FROM public; + GRANT ALL ON FOREIGN DATA WRAPPER tenancy_wrapper To tenancy_user2; + GRANT ALL ON FOREIGN SERVER tenancy_server To tenancy_user2; + alter database regression with catalog security = true; + -- create objects realted to tenacy_user1 + SET SESSION ROLE tenancy_user1; + create table public.tenancy_user1_tbl1 (tenancy_user1_tbl1_column1 serial, + tenancy_user1_tbl1_column2 char(10) default 'FUJITSU', + tenancy_user1_tbl1_column3 int); + create index tenancy_user1_tbl1_idx on tenancy_user1_tbl1 (tenancy_user1_tbl1_column3); + insert into tenancy_user1_tbl1(tenancy_user1_tbl1_column3) values(1); + create table tenancy_user1.tenancy_user1_tbl2 (tenancy_user1_tbl2_column1 serial, + tenancy_user1_tbl2_column2 char(10) default 'FUJITSU', + tenancy_user1_tbl2_column3 int); + ALTER TABLE tenancy_user1.tenancy_user1_tbl2 ADD CONSTRAINT tenancy_user1_tbl2_constraint UNIQUE (tenancy_user1_tbl2_column3); + CREATE FUNCTION tenancy_user1_trigger_func() RETURNS trigger LANGUAGE plpgsql AS ' + BEGIN + RAISE NOTICE ''tenancy_user1_trigger_func(%) called: action = %, when = %, level = %'', TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL; + RETURN NULL; + END;'; + CREATE TRIGGER tenancy_user1_tbl1_before_ins_stmt_trig BEFORE INSERT ON tenancy_user1_tbl1 + FOR EACH STATEMENT EXECUTE PROCEDURE tenancy_user1_trigger_func('before_ins_stmt'); + CREATE TABLE tenancy_user1_main_table (aa TEXT); + CREATE TABLE tenancy_user1_child_table (bb TEXT) INHERITS (tenancy_user1_main_table); + create view public.tenancy_user1_view1 as select * from tenancy_user1_tbl1; + create materialized view public.tenancy_user1_matview1 as select * from tenancy_user1_tbl1; + select * from tenancy_user1_tbl1; + tenancy_user1_tbl1_column1 | tenancy_user1_tbl1_column2 | tenancy_user1_tbl1_column3 + ----------------------------+----------------------------+---------------------------- + 1 | FUJITSU | 1 + (1 row) + + select * from tenancy_user1_view1; + tenancy_user1_tbl1_column1 | tenancy_user1_tbl1_column2 | tenancy_user1_tbl1_column3 + ----------------------------+----------------------------+---------------------------- + 1 | FUJITSU | 1 + (1 row) + + select * from tenancy_user1_matview1; + tenancy_user1_tbl1_column1 | tenancy_user1_tbl1_column2 | tenancy_user1_tbl1_column3 + ----------------------------+----------------------------+---------------------------- + 1 | FUJITSU | 1 + (1 row) + + + -- verify all system catalogs related to the objects created by tenancy_user1 + select relname from pg_class where relname = 'tenancy_user1_tbl1'; + relname + -------------------- + tenancy_user1_tbl1 + (1 row) + + select relname from pg_class where relname = 'tenancy_user1_tbl1_idx'; + relname + ------------------------ + tenancy_user1_tbl1_idx + (1 row) + + select relname from pg_class where relname = 'tenancy_user1_view1'; + relname + --------------------- + tenancy_user1_view1 + (1 row) + + select relname from pg_class where relname = 'tenancy_user1_matview1'; + relname + ------------------------ + tenancy_user1_matview1 + (1 row) + + select relname from pg_class where relname = 'tenancy_user1_tbl1_tenancy_user1_tbl1_column1_seq'; + relname + --------------------------------------------------- + tenancy_user1_tbl1_tenancy_user1_tbl1_column1_seq + (1 row) + + select attname from pg_attribute where attname = 'tenancy_user1_tbl1_column1'; + attname + ---------------------------- + tenancy_user1_tbl1_column1 + tenancy_user1_tbl1_column1 + tenancy_user1_tbl1_column1 + (3 rows) + + select adnum, adsrc from pg_attrdef where adrelid in (select oid from pg_class where relname = 'tenancy_user1_tbl1'); + adnum | adsrc + -------+------------------------------------------------------------------------ + 1 | nextval('tenancy_user1_tbl1_tenancy_user1_tbl1_column1_seq'::regclass) + 2 | 'FUJITSU'::bpchar + (2 rows) + + select indnatts, indkey from pg_index where indrelid in (select oid from pg_class where relname = 'tenancy_user1_tbl1'); + indnatts | indkey + ----------+-------- + 1 | 3 + (1 row) + + select nspname, nspowner from pg_namespace where nspname = 'tenancy_user1'; + nspname | nspowner + ---------------+---------- + tenancy_user1 | 10 + (1 row) + + select conname from pg_constraint where conname = 'tenancy_user1_tbl2_constraint'; + conname + ------------------------------- + tenancy_user1_tbl2_constraint + (1 row) + + select classid::regclass, objid::regclass, deptype from pg_depend + where classid = (select oid from pg_class where relname = 'pg_class') + and objid = (select oid from pg_class where relname = 'tenancy_user1_tbl2'); + classid | objid | deptype + ----------+--------------------+--------- + pg_class | tenancy_user1_tbl2 | n + (1 row) + + select inhrelid::regclass, inhparent::regclass from pg_inherits + where inhrelid = (select oid from pg_class where relname = 'tenancy_user1_child_table'); + inhrelid | inhparent + ---------------------------+-------------------------- + tenancy_user1_child_table | tenancy_user1_main_table + (1 row) + + + --pg_class policy view, is shouldn't be visible to normal users + select polname from pg_policy where polrelid = (select oid from pg_class where relname = 'pg_class'); + polname + --------- + (0 rows) + + select tgname, tgtype from pg_trigger where tgname = 'tenancy_user1_tbl1_before_ins_stmt_trig'; + tgname | tgtype + -----------------------------------------+-------- + tenancy_user1_tbl1_before_ins_stmt_trig | 6 + (1 row) + + + -- verify all system views related to the objects created by tenancy_user1 + select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user1_tbl1'; + schemaname | relname + ------------+-------------------- + public | tenancy_user1_tbl1 + (1 row) + + select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user1_tbl2' and schemaname = 'tenancy_user1'; + schemaname | relname + ---------------+-------------------- + tenancy_user1 | tenancy_user1_tbl2 + (1 row) + + --works only when the shared catalog security is enabled + --select rolname, rolsuper from pg_roles where rolname like 'tenancy_user%'; + select schemaname, tablename from pg_policies where tablename = 'pg_class'; + schemaname | tablename + ------------+----------- + (0 rows) + + select schemaname, viewname from pg_views where viewname = 'tenancy_user1_view1'; + schemaname | viewname + ------------+--------------------- + public | tenancy_user1_view1 + (1 row) + + select schemaname, tablename from pg_tables where tablename = 'tenancy_user1_tbl2'; + schemaname | tablename + ---------------+-------------------- + tenancy_user1 | tenancy_user1_tbl2 + (1 row) + + select schemaname, matviewname from pg_matviews where matviewname = 'tenancy_user1_matview1'; + schemaname | matviewname + ------------+------------------------ + public | tenancy_user1_matview1 + (1 row) + + select schemaname, tablename, indexname from pg_indexes where indexname = 'tenancy_user1_tbl1_idx'; + schemaname | tablename | indexname + ------------+--------------------+------------------------ + public | tenancy_user1_tbl1 | tenancy_user1_tbl1_idx + (1 row) + + select schemaname, relname from pg_statio_all_tables where relname = 'tenancy_user1_tbl1'; + schemaname | relname + ------------+-------------------- + public | tenancy_user1_tbl1 + (1 row) + + select schemaname, relname, indexrelname from pg_stat_all_indexes where indexrelname = 'tenancy_user1_tbl1_idx'; + schemaname | relname | indexrelname + ------------+--------------------+------------------------ + public | tenancy_user1_tbl1 | tenancy_user1_tbl1_idx + (1 row) + + --information_schema views + select * from information_schema.table_constraints where constraint_name = 'tenancy_user1_tbl2_constraint'; + constraint_catalog | constraint_schema | constraint_name | table_catalog | table_schema | table_name | constraint_type | is_deferrable | initially_deferred + --------------------+-------------------+-------------------------------+---------------+---------------+--------------------+-----------------+---------------+-------------------- + regression | tenancy_user1 | tenancy_user1_tbl2_constraint | regression | tenancy_user1 | tenancy_user1_tbl2 | UNIQUE | NO | NO + (1 row) + + select catalog_name, schema_name from information_schema.schemata where schema_name = 'tenancy_user1'; + catalog_name | schema_name + --------------+--------------- + regression | tenancy_user1 + (1 row) + + select sequence_catalog, sequence_schema, sequence_name from information_schema.sequences + where sequence_name = 'tenancy_user1_tbl1_tenancy_user1_tbl1_column1_seq'; + sequence_catalog | sequence_schema | sequence_name + ------------------+-----------------+--------------------------------------------------- + regression | public | tenancy_user1_tbl1_tenancy_user1_tbl1_column1_seq + (1 row) + + select * from information_schema.tables where table_name = 'tenancy_user1_tbl1'; + table_catalog | table_schema | table_name | table_type | self_referencing_column_name | reference_generation | user_defined_type_catalog | user_defined_type_schema | user_defined_type_name | is_insertable_into | is_typed | commit_action + ---------------+--------------+--------------------+------------+------------------------------+----------------------+---------------------------+--------------------------+------------------------+--------------------+----------+--------------- + regression | public | tenancy_user1_tbl1 | BASE TABLE | | | | | | YES | NO | + (1 row) + + select * from information_schema.triggers where trigger_name = 'tenancy_user1_tbl1_before_ins_stmt_trig'; + trigger_catalog | trigger_schema | trigger_name | event_manipulation | event_object_catalog | event_object_schema | event_object_table | action_order | action_condition | action_statement | action_orientation | action_timing | action_reference_old_table | action_reference_new_table | action_reference_old_row | action_reference_new_row | created + -----------------+----------------+-----------------------------------------+--------------------+----------------------+---------------------+--------------------+--------------+------------------+-----------------------------------------------------------------+--------------------+---------------+----------------------------+----------------------------+--------------------------+--------------------------+--------- + regression | public | tenancy_user1_tbl1_before_ins_stmt_trig | INSERT | regression | public | tenancy_user1_tbl1 | | | EXECUTE PROCEDURE tenancy_user1_trigger_func('before_ins_stmt') | STATEMENT | BEFORE | | | | | + (1 row) + + select * from information_schema.views where table_name = 'tenancy_user1_view1'; + table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into + ---------------+--------------+---------------------+--------------------------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+---------------------------- + regression | public | tenancy_user1_view1 | SELECT tenancy_user1_tbl1.tenancy_user1_tbl1_column1,+| NONE | YES | YES | NO | NO | NO + | | | tenancy_user1_tbl1.tenancy_user1_tbl1_column2, +| | | | | | + | | | tenancy_user1_tbl1.tenancy_user1_tbl1_column3 +| | | | | | + | | | FROM tenancy_user1_tbl1; | | | | | | + (1 row) + + RESET ROLE; + -- create objects realted to tenacy_user2 + SET SESSION ROLE tenancy_user2; + CREATE FOREIGN TABLE tenancy_user2_ft1 ( + tenancy_user2_c1 integer OPTIONS ("param 1" 'val1') NOT NULL, + tenancy_user2_c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (tenancy_user2_c2 <> ''), + tenancy_user2_c3 date, + CHECK (tenancy_user2_c3 BETWEEN '1994-01-01'::date AND '1994-01-31'::date) + ) SERVER tenancy_server OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); + -- a dummy function to test the new type + CREATE FUNCTION tenancy_user2_tenancytestfunc(text) RETURNS int4 LANGUAGE SQL AS + $$ SELECT 1; $$; + SELECT tenancy_user2_tenancytestfunc('foo'::text); + tenancy_user2_tenancytestfunc + ------------------------------- + 1 + (1 row) + + --verify system catalogs to view the details of type, function and cast. + select proname from pg_proc where proname = 'tenancy_user2_tenancytestfunc'; + proname + ------------------------------- + tenancy_user2_tenancytestfunc + (1 row) + + select proname, pronamespace from pg_proc where proname = 'tenancytesttype_in'; + proname | pronamespace + --------------------+-------------- + tenancytesttype_in | 2200 + (1 row) + + select proname, pronamespace from pg_proc where proname = 'tenancytesttype_out'; + proname | pronamespace + ---------------------+-------------- + tenancytesttype_out | 2200 + (1 row) + + --select the description of casttesttype + select description from pg_description where classoid = (select oid from pg_class where relname = 'pg_type') + and objoid = (select oid from pg_type where typname = 'tenancytesttype'); + description + ----------------- + tenancytesttype + (1 row) + + + --select the language that is owned by the tenancy_user2 + select lanname, lanispl from pg_language where lanname = 'tenancy_lang1'; + lanname | lanispl + ---------------+--------- + tenancy_lang1 | t + (1 row) + + --Foriegn data wrapper, server and foreign table details + SELECT fdwname, fdwhandler::regproc, fdwvalidator::regproc, fdwoptions FROM pg_foreign_data_wrapper ORDER BY 1, 2, 3; + fdwname | fdwhandler | fdwvalidator | fdwoptions + -----------------+------------+--------------+------------ + tenancy_wrapper | - | - | + (1 row) + + SELECT srvname, srvoptions FROM pg_foreign_server; + srvname | srvoptions + ----------------+------------ + tenancy_server | + (1 row) + + Select srvname FROM pg_user_mappings where srvname = 'tenancy_server'; + srvname + ---------------- + tenancy_server + (1 row) + + SELECT relname, relkind from pg_class where relname = 'tenancy_user2_ft1'; + relname | relkind + -------------------+--------- + tenancy_user2_ft1 | f + (1 row) + + + --information_schema views + select foreign_data_wrapper_catalog, foreign_data_wrapper_name from information_schema.foreign_data_wrappers + where foreign_data_wrapper_name = 'tenancy_wrapper'; + foreign_data_wrapper_catalog | foreign_data_wrapper_name + ------------------------------+--------------------------- + regression | tenancy_wrapper + (1 row) + + select foreign_server_catalog, foreign_server_name, foreign_data_wrapper_catalog, foreign_data_wrapper_name + from information_schema.foreign_servers where foreign_server_name = 'tenancy_server'; + foreign_server_catalog | foreign_server_name | foreign_data_wrapper_catalog | foreign_data_wrapper_name + ------------------------+---------------------+------------------------------+--------------------------- + regression | tenancy_server | regression | tenancy_wrapper + (1 row) + + select * from information_schema.foreign_tables where foreign_table_name = 'tenancy_user2_ft1'; + foreign_table_catalog | foreign_table_schema | foreign_table_name | foreign_server_catalog | foreign_server_name + -----------------------+----------------------+--------------------+------------------------+--------------------- + regression | tenancy_user2 | tenancy_user2_ft1 | regression | tenancy_server + (1 row) + + select * from information_schema.user_mappings where foreign_server_name = 'tenancy_server'; + authorization_identifier | foreign_server_catalog | foreign_server_name + --------------------------+------------------------+--------------------- + tenancy_user2 | regression | tenancy_server + (1 row) + + RESET ROLE; + -- Try to get the objects created by tenancy_user1 from tenancy_user2 + SET SESSION ROLE tenancy_user2; + select * from tenancy_user1_tbl1; + ERROR: permission denied for relation tenancy_user1_tbl1 + select * from tenancy_user1_view1; + ERROR: permission denied for relation tenancy_user1_view1 + select * from tenancy_user1_matview1; + ERROR: permission denied for relation tenancy_user1_matview1 + select * from tenancy_user1.tenancy_user1_tbl2; + ERROR: permission denied for schema tenancy_user1 + LINE 1: select * from tenancy_user1.tenancy_user1_tbl2; + ^ + -- verify all system catalogs related to the objects created by tenancy_user1 + select relname from pg_class where relname = 'tenancy_user1_tbl1'; + relname + --------- + (0 rows) + + select relname from pg_class where relname = 'tenancy_user1_tbl1_idx'; + relname + --------- + (0 rows) + + select relname from pg_class where relname = 'tenancy_user1_view1'; + relname + --------- + (0 rows) + + select relname from pg_class where relname = 'tenancy_user1_matview1'; + relname + --------- + (0 rows) + + select relname from pg_class where relname = 'tenancy_user1_tbl1_tenancy_user1_tbl1_column1_seq'; + relname + --------- + (0 rows) + + select attname from pg_attribute where attname = 'tenancy_user1_tbl1_column1'; + attname + --------- + (0 rows) + + select adnum, adsrc from pg_attrdef where adrelid in (select oid from pg_class where relname = 'tenancy_user1_tbl1'); + adnum | adsrc + -------+------- + (0 rows) + + select indnatts, indkey from pg_index where indrelid in (select oid from pg_class where relname = 'tenancy_user1_tbl1'); + indnatts | indkey + ----------+-------- + (0 rows) + + select nspname, nspowner from pg_namespace where nspname = 'tenancy_user1'; + nspname | nspowner + ---------+---------- + (0 rows) + + select conname from pg_constraint where conname = 'tenancy_user1_tbl2_constraint'; + conname + --------- + (0 rows) + + select classid::regclass, objid::regclass, deptype from pg_depend + where classid = (select oid from pg_class where relname = 'pg_class') + and objid = (select oid from pg_class where relname = 'tenancy_user1_tbl2'); + classid | objid | deptype + ---------+-------+--------- + (0 rows) + + select inhrelid::regclass, inhparent::regclass from pg_inherits + where inhrelid = (select oid from pg_class where relname = 'tenancy_user1_child_table'); + inhrelid | inhparent + ----------+----------- + (0 rows) + + + --pg_class policy view, is shouldn't be visible to normal users + select polname from pg_policy where polrelid = (select oid from pg_class where relname = 'pg_class'); + polname + --------- + (0 rows) + + select tgname, tgtype from pg_trigger where tgname = 'tenancy_user1_tbl1_before_ins_stmt_trig'; + tgname | tgtype + --------+-------- + (0 rows) + + -- verify all system views related to the objects created by tenancy_user1 + select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user1_tbl1'; + schemaname | relname + ------------+--------- + (0 rows) + + select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user1_tbl2' and schemaname = 'tenancy_user1'; + schemaname | relname + ------------+--------- + (0 rows) + + --works only when the shared catalog security is enabled + --select rolname, rolsuper from pg_roles where rolname like 'tenancy_user%'; + select schemaname, tablename from pg_policies where tablename = 'pg_class'; + schemaname | tablename + ------------+----------- + (0 rows) + + select schemaname, viewname from pg_views where viewname = 'tenancy_user1_view1'; + schemaname | viewname + ------------+---------- + (0 rows) + + select schemaname, tablename from pg_tables where tablename = 'tenancy_user1_tbl2'; + schemaname | tablename + ------------+----------- + (0 rows) + + select schemaname, matviewname from pg_matviews where matviewname = 'tenancy_user1_matview1'; + schemaname | matviewname + ------------+------------- + (0 rows) + + select schemaname, tablename, indexname from pg_indexes where indexname = 'tenancy_user1_tbl1_idx'; + schemaname | tablename | indexname + ------------+-----------+----------- + (0 rows) + + select schemaname, relname from pg_statio_all_tables where relname = 'tenancy_user1_tbl1'; + schemaname | relname + ------------+--------- + (0 rows) + + select schemaname, relname, indexrelname from pg_stat_all_indexes where indexrelname = 'tenancy_user1_tbl1_idx'; + schemaname | relname | indexrelname + ------------+---------+-------------- + (0 rows) + + --information_schema views + select * from information_schema.table_constraints where constraint_name = 'tenancy_user1_tbl2_constraint'; + constraint_catalog | constraint_schema | constraint_name | table_catalog | table_schema | table_name | constraint_type | is_deferrable | initially_deferred + --------------------+-------------------+-----------------+---------------+--------------+------------+-----------------+---------------+-------------------- + (0 rows) + + select catalog_name, schema_name from information_schema.schemata where schema_name = 'tenancy_user1'; + catalog_name | schema_name + --------------+------------- + (0 rows) + + select sequence_catalog, sequence_schema, sequence_name from information_schema.sequences + where sequence_name = 'tenancy_user1_tbl1_tenancy_user1_tbl1_column1_seq'; + sequence_catalog | sequence_schema | sequence_name + ------------------+-----------------+--------------- + (0 rows) + + select * from information_schema.tables where table_name = 'tenancy_user1_tbl1'; + table_catalog | table_schema | table_name | table_type | self_referencing_column_name | reference_generation | user_defined_type_catalog | user_defined_type_schema | user_defined_type_name | is_insertable_into | is_typed | commit_action + ---------------+--------------+------------+------------+------------------------------+----------------------+---------------------------+--------------------------+------------------------+--------------------+----------+--------------- + (0 rows) + + select * from information_schema.triggers where trigger_name = 'tenancy_user1_tbl1_before_ins_stmt_trig'; + trigger_catalog | trigger_schema | trigger_name | event_manipulation | event_object_catalog | event_object_schema | event_object_table | action_order | action_condition | action_statement | action_orientation | action_timing | action_reference_old_table | action_reference_new_table | action_reference_old_row | action_reference_new_row | created + -----------------+----------------+--------------+--------------------+----------------------+---------------------+--------------------+--------------+------------------+------------------+--------------------+---------------+----------------------------+----------------------------+--------------------------+--------------------------+--------- + (0 rows) + + select * from information_schema.views where table_name = 'tenancy_user1_view1'; + table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into + ---------------+--------------+------------+-----------------+--------------+--------------+--------------------+----------------------+----------------------+---------------------------- + (0 rows) + + RESET ROLE; + -- Try to get the objects created by tenancy_user2 from tenancy_user1 + SET SESSION ROLE tenancy_user1; + --verify system catalogs to view the details of type, function and cast. + select proname from pg_proc where proname = 'tenancy_user2_tenancytestfunc'; + proname + ------------------------------- + tenancy_user2_tenancytestfunc + (1 row) + + select proname, pronamespace from pg_proc where proname = 'tenancytesttype_in'; + proname | pronamespace + ---------+-------------- + (0 rows) + + select proname, pronamespace from pg_proc where proname = 'tenancytesttype_out'; + proname | pronamespace + ---------+-------------- + (0 rows) + + --select the description of casttesttype + select description from pg_description where classoid = (select oid from pg_class where relname = 'pg_type') + and objoid = (select oid from pg_type where typname = 'tenancytesttype'); + description + ------------- + (0 rows) + + --select the language that is owned by the tenancy_user2 + select lanname, lanispl from pg_language where lanname = 'tenancy_lang1'; + lanname | lanispl + ---------+--------- + (0 rows) + + --Foriegn data wrapper, server and foreign table details + SELECT fdwname, fdwhandler::regproc, fdwvalidator::regproc, fdwoptions FROM pg_foreign_data_wrapper ORDER BY 1, 2, 3; + fdwname | fdwhandler | fdwvalidator | fdwoptions + ---------+------------+--------------+------------ + (0 rows) + + SELECT srvname, srvoptions FROM pg_foreign_server; + srvname | srvoptions + ---------+------------ + (0 rows) + + Select srvname FROM pg_user_mappings where srvname = 'tenancy_server'; + srvname + --------- + (0 rows) + + SELECT relname, relkind from pg_class where relname = 'tenancy_user2_ft1'; + relname | relkind + ---------+--------- + (0 rows) + + --information_schema views + select foreign_data_wrapper_catalog, foreign_data_wrapper_name from information_schema.foreign_data_wrappers + where foreign_data_wrapper_name = 'tenancy_wrapper'; + foreign_data_wrapper_catalog | foreign_data_wrapper_name + ------------------------------+--------------------------- + (0 rows) + + select foreign_server_catalog, foreign_server_name, foreign_data_wrapper_catalog, foreign_data_wrapper_name + from information_schema.foreign_servers where foreign_server_name = 'tenancy_server'; + foreign_server_catalog | foreign_server_name | foreign_data_wrapper_catalog | foreign_data_wrapper_name + ------------------------+---------------------+------------------------------+--------------------------- + (0 rows) + + select * from information_schema.foreign_tables where foreign_table_name = 'tenancy_user2_ft1'; + foreign_table_catalog | foreign_table_schema | foreign_table_name | foreign_server_catalog | foreign_server_name + -----------------------+----------------------+--------------------+------------------------+--------------------- + (0 rows) + + select * from information_schema.user_mappings where foreign_server_name = 'tenancy_server'; + authorization_identifier | foreign_server_catalog | foreign_server_name + --------------------------+------------------------+--------------------- + (0 rows) + + RESET ROLE; + -- Delete the roles and it's associated objects. + SET SESSION ROLE tenancy_user1; + DROP TABLE tenancy_user1_tbl1 cascade; + NOTICE: drop cascades to 2 other objects + DETAIL: drop cascades to view tenancy_user1_view1 + drop cascades to materialized view tenancy_user1_matview1 + DROP TABLE tenancy_user1_tbl2 cascade; + DROP FUNCTION tenancy_user1_trigger_func() cascade; + DROP TABLE tenancy_user1_main_table cascade; + NOTICE: drop cascades to table tenancy_user1_child_table + RESET ROLE; + SET SESSION ROLE tenancy_user2; + DROP FUNCTION tenancy_user2_tenancytestfunc(text); + DROP FOREIGN TABLE tenancy_user2_ft1; + DROP LANGUAGE tenancy_lang1; + RESET ROLE; + DROP TYPE tenancytesttype cascade; + NOTICE: drop cascades to 2 other objects + DETAIL: drop cascades to function tenancytesttype_in(cstring) + drop cascades to function tenancytesttype_out(tenancytesttype) + DROP USER MAPPING FOR tenancy_user2 SERVER tenancy_server; + DROP SERVER tenancy_server; + DROP FOREIGN DATA WRAPPER tenancy_wrapper; + alter database regression with catalog security = false; + drop schema tenancy_user1; + drop schema tenancy_user2; + drop role tenancy_user1; + drop role tenancy_user2; *** a/src/test/regress/parallel_schedule --- b/src/test/regress/parallel_schedule *************** *** 60,66 **** test: create_index create_view # ---------- # Another group of parallel tests # ---------- ! test: create_aggregate create_function_3 create_cast constraints triggers inherit create_table_like typed_table vacuum drop_if_exists updatable_views rolenames roleattributes # ---------- # sanity_check does a vacuum, affecting the sort order of SELECT * --- 60,66 ---- # ---------- # Another group of parallel tests # ---------- ! test: create_aggregate create_function_3 create_cast constraints triggers inherit create_table_like typed_table vacuum drop_if_exists updatable_views rolenames roleattributes multitenancy # ---------- # sanity_check does a vacuum, affecting the sort order of SELECT * *** a/src/test/regress/serial_schedule --- b/src/test/regress/serial_schedule *************** *** 158,161 **** test: largeobject --- 158,162 ---- test: with test: xml test: event_trigger + test: multitenancy test: stats *** /dev/null --- b/src/test/regress/sql/multitenancy.sql *************** *** 0 **** --- 1,304 ---- + -- Create roles that are used by the following tests + create role tenancy_user1 login createdb; + create role tenancy_user2 login createdb; + + create schema tenancy_user1; + grant all on schema tenancy_user1 to tenancy_user1; + + create schema tenancy_user2; + grant all on schema tenancy_user2 to tenancy_user2; + + -- Create a type to test + CREATE TYPE tenancytesttype; + + CREATE FUNCTION tenancytesttype_in(cstring) + RETURNS tenancytesttype + AS 'textin' + LANGUAGE internal STRICT IMMUTABLE; + CREATE FUNCTION tenancytesttype_out(tenancytesttype) + RETURNS cstring + AS 'textout' + LANGUAGE internal STRICT IMMUTABLE; + + CREATE TYPE tenancytesttype ( + internallength = variable, + input = tenancytesttype_in, + output = tenancytesttype_out, + alignment = int4 + ); + + COMMENT ON TYPE tenancytesttype IS 'tenancytesttype'; + + REVOKE ALL ON FUNCTION tenancytesttype_in(cstring) FROM public; + REVOKE ALL ON FUNCTION tenancytesttype_out(tenancytesttype) FROM public; + GRANT ALL ON FUNCTION tenancytesttype_in(cstring) TO tenancy_user2; + GRANT ALL ON FUNCTION tenancytesttype_out(tenancytesttype) TO tenancy_user2; + + REVOKE ALL ON TYPE tenancytesttype FROM public; + GRANT ALL ON TYPE tenancytesttype TO tenancy_user2; + + -- Temp Language create with owner as tenancy_user2 + CREATE TRUSTED LANGUAGE tenancy_lang1 HANDLER plpgsql_call_handler; + REVOKE ALL ON LANGUAGE tenancy_lang1 FROM public; + ALTER LANGUAGE tenancy_lang1 OWNER TO tenancy_user2; + + --Foriegn data wrapper, server and user mapping creation for user tenancy_user2 + CREATE FOREIGN DATA WRAPPER tenancy_wrapper; + CREATE SERVER tenancy_server FOREIGN DATA WRAPPER tenancy_wrapper; + CREATE USER MAPPING FOR tenancy_user2 SERVER tenancy_server; + + REVOKE ALL ON FOREIGN DATA WRAPPER tenancy_wrapper FROM public; + REVOKE ALL ON FOREIGN SERVER tenancy_server FROM public; + GRANT ALL ON FOREIGN DATA WRAPPER tenancy_wrapper To tenancy_user2; + GRANT ALL ON FOREIGN SERVER tenancy_server To tenancy_user2; + + alter database regression with catalog security = true; + + -- create objects realted to tenacy_user1 + SET SESSION ROLE tenancy_user1; + + create table public.tenancy_user1_tbl1 (tenancy_user1_tbl1_column1 serial, + tenancy_user1_tbl1_column2 char(10) default 'FUJITSU', + tenancy_user1_tbl1_column3 int); + create index tenancy_user1_tbl1_idx on tenancy_user1_tbl1 (tenancy_user1_tbl1_column3); + + insert into tenancy_user1_tbl1(tenancy_user1_tbl1_column3) values(1); + + create table tenancy_user1.tenancy_user1_tbl2 (tenancy_user1_tbl2_column1 serial, + tenancy_user1_tbl2_column2 char(10) default 'FUJITSU', + tenancy_user1_tbl2_column3 int); + ALTER TABLE tenancy_user1.tenancy_user1_tbl2 ADD CONSTRAINT tenancy_user1_tbl2_constraint UNIQUE (tenancy_user1_tbl2_column3); + + CREATE FUNCTION tenancy_user1_trigger_func() RETURNS trigger LANGUAGE plpgsql AS ' + BEGIN + RAISE NOTICE ''tenancy_user1_trigger_func(%) called: action = %, when = %, level = %'', TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL; + RETURN NULL; + END;'; + + CREATE TRIGGER tenancy_user1_tbl1_before_ins_stmt_trig BEFORE INSERT ON tenancy_user1_tbl1 + FOR EACH STATEMENT EXECUTE PROCEDURE tenancy_user1_trigger_func('before_ins_stmt'); + + CREATE TABLE tenancy_user1_main_table (aa TEXT); + CREATE TABLE tenancy_user1_child_table (bb TEXT) INHERITS (tenancy_user1_main_table); + + create view public.tenancy_user1_view1 as select * from tenancy_user1_tbl1; + create materialized view public.tenancy_user1_matview1 as select * from tenancy_user1_tbl1; + + select * from tenancy_user1_tbl1; + select * from tenancy_user1_view1; + select * from tenancy_user1_matview1; + + + -- verify all system catalogs related to the objects created by tenancy_user1 + select relname from pg_class where relname = 'tenancy_user1_tbl1'; + select relname from pg_class where relname = 'tenancy_user1_tbl1_idx'; + select relname from pg_class where relname = 'tenancy_user1_view1'; + select relname from pg_class where relname = 'tenancy_user1_matview1'; + select relname from pg_class where relname = 'tenancy_user1_tbl1_tenancy_user1_tbl1_column1_seq'; + + select attname from pg_attribute where attname = 'tenancy_user1_tbl1_column1'; + select adnum, adsrc from pg_attrdef where adrelid in (select oid from pg_class where relname = 'tenancy_user1_tbl1'); + select indnatts, indkey from pg_index where indrelid in (select oid from pg_class where relname = 'tenancy_user1_tbl1'); + select nspname, nspowner from pg_namespace where nspname = 'tenancy_user1'; + select conname from pg_constraint where conname = 'tenancy_user1_tbl2_constraint'; + select classid::regclass, objid::regclass, deptype from pg_depend + where classid = (select oid from pg_class where relname = 'pg_class') + and objid = (select oid from pg_class where relname = 'tenancy_user1_tbl2'); + select inhrelid::regclass, inhparent::regclass from pg_inherits + where inhrelid = (select oid from pg_class where relname = 'tenancy_user1_child_table'); + + --pg_class policy view, is shouldn't be visible to normal users + select polname from pg_policy where polrelid = (select oid from pg_class where relname = 'pg_class'); + + select tgname, tgtype from pg_trigger where tgname = 'tenancy_user1_tbl1_before_ins_stmt_trig'; + + -- verify all system views related to the objects created by tenancy_user1 + select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user1_tbl1'; + select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user1_tbl2' and schemaname = 'tenancy_user1'; + --works only when the shared catalog security is enabled + --select rolname, rolsuper from pg_roles where rolname like 'tenancy_user%'; + select schemaname, tablename from pg_policies where tablename = 'pg_class'; + select schemaname, viewname from pg_views where viewname = 'tenancy_user1_view1'; + select schemaname, tablename from pg_tables where tablename = 'tenancy_user1_tbl2'; + select schemaname, matviewname from pg_matviews where matviewname = 'tenancy_user1_matview1'; + select schemaname, tablename, indexname from pg_indexes where indexname = 'tenancy_user1_tbl1_idx'; + select schemaname, relname from pg_statio_all_tables where relname = 'tenancy_user1_tbl1'; + select schemaname, relname, indexrelname from pg_stat_all_indexes where indexrelname = 'tenancy_user1_tbl1_idx'; + + --information_schema views + select * from information_schema.table_constraints where constraint_name = 'tenancy_user1_tbl2_constraint'; + select catalog_name, schema_name from information_schema.schemata where schema_name = 'tenancy_user1'; + select sequence_catalog, sequence_schema, sequence_name from information_schema.sequences + where sequence_name = 'tenancy_user1_tbl1_tenancy_user1_tbl1_column1_seq'; + select * from information_schema.tables where table_name = 'tenancy_user1_tbl1'; + select * from information_schema.triggers where trigger_name = 'tenancy_user1_tbl1_before_ins_stmt_trig'; + select * from information_schema.views where table_name = 'tenancy_user1_view1'; + + + RESET ROLE; + + + -- create objects realted to tenacy_user2 + SET SESSION ROLE tenancy_user2; + + CREATE FOREIGN TABLE tenancy_user2_ft1 ( + tenancy_user2_c1 integer OPTIONS ("param 1" 'val1') NOT NULL, + tenancy_user2_c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (tenancy_user2_c2 <> ''), + tenancy_user2_c3 date, + CHECK (tenancy_user2_c3 BETWEEN '1994-01-01'::date AND '1994-01-31'::date) + ) SERVER tenancy_server OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); + + -- a dummy function to test the new type + CREATE FUNCTION tenancy_user2_tenancytestfunc(text) RETURNS int4 LANGUAGE SQL AS + $$ SELECT 1; $$; + + SELECT tenancy_user2_tenancytestfunc('foo'::text); + + --verify system catalogs to view the details of type, function and cast. + select proname from pg_proc where proname = 'tenancy_user2_tenancytestfunc'; + select proname, pronamespace from pg_proc where proname = 'tenancytesttype_in'; + select proname, pronamespace from pg_proc where proname = 'tenancytesttype_out'; + + --select the description of casttesttype + select description from pg_description where classoid = (select oid from pg_class where relname = 'pg_type') + and objoid = (select oid from pg_type where typname = 'tenancytesttype'); + + --select the language that is owned by the tenancy_user2 + select lanname, lanispl from pg_language where lanname = 'tenancy_lang1'; + + --Foriegn data wrapper, server and foreign table details + SELECT fdwname, fdwhandler::regproc, fdwvalidator::regproc, fdwoptions FROM pg_foreign_data_wrapper ORDER BY 1, 2, 3; + SELECT srvname, srvoptions FROM pg_foreign_server; + Select srvname FROM pg_user_mappings where srvname = 'tenancy_server'; + + SELECT relname, relkind from pg_class where relname = 'tenancy_user2_ft1'; + + --information_schema views + select foreign_data_wrapper_catalog, foreign_data_wrapper_name from information_schema.foreign_data_wrappers + where foreign_data_wrapper_name = 'tenancy_wrapper'; + select foreign_server_catalog, foreign_server_name, foreign_data_wrapper_catalog, foreign_data_wrapper_name + from information_schema.foreign_servers where foreign_server_name = 'tenancy_server'; + select * from information_schema.foreign_tables where foreign_table_name = 'tenancy_user2_ft1'; + select * from information_schema.user_mappings where foreign_server_name = 'tenancy_server'; + + RESET ROLE; + + -- Try to get the objects created by tenancy_user1 from tenancy_user2 + SET SESSION ROLE tenancy_user2; + + select * from tenancy_user1_tbl1; + select * from tenancy_user1_view1; + select * from tenancy_user1_matview1; + + select * from tenancy_user1.tenancy_user1_tbl2; + + -- verify all system catalogs related to the objects created by tenancy_user1 + select relname from pg_class where relname = 'tenancy_user1_tbl1'; + select relname from pg_class where relname = 'tenancy_user1_tbl1_idx'; + select relname from pg_class where relname = 'tenancy_user1_view1'; + select relname from pg_class where relname = 'tenancy_user1_matview1'; + select relname from pg_class where relname = 'tenancy_user1_tbl1_tenancy_user1_tbl1_column1_seq'; + + select attname from pg_attribute where attname = 'tenancy_user1_tbl1_column1'; + select adnum, adsrc from pg_attrdef where adrelid in (select oid from pg_class where relname = 'tenancy_user1_tbl1'); + select indnatts, indkey from pg_index where indrelid in (select oid from pg_class where relname = 'tenancy_user1_tbl1'); + select nspname, nspowner from pg_namespace where nspname = 'tenancy_user1'; + select conname from pg_constraint where conname = 'tenancy_user1_tbl2_constraint'; + select classid::regclass, objid::regclass, deptype from pg_depend + where classid = (select oid from pg_class where relname = 'pg_class') + and objid = (select oid from pg_class where relname = 'tenancy_user1_tbl2'); + select inhrelid::regclass, inhparent::regclass from pg_inherits + where inhrelid = (select oid from pg_class where relname = 'tenancy_user1_child_table'); + + --pg_class policy view, is shouldn't be visible to normal users + select polname from pg_policy where polrelid = (select oid from pg_class where relname = 'pg_class'); + + select tgname, tgtype from pg_trigger where tgname = 'tenancy_user1_tbl1_before_ins_stmt_trig'; + + -- verify all system views related to the objects created by tenancy_user1 + select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user1_tbl1'; + select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user1_tbl2' and schemaname = 'tenancy_user1'; + --works only when the shared catalog security is enabled + --select rolname, rolsuper from pg_roles where rolname like 'tenancy_user%'; + select schemaname, tablename from pg_policies where tablename = 'pg_class'; + select schemaname, viewname from pg_views where viewname = 'tenancy_user1_view1'; + select schemaname, tablename from pg_tables where tablename = 'tenancy_user1_tbl2'; + select schemaname, matviewname from pg_matviews where matviewname = 'tenancy_user1_matview1'; + select schemaname, tablename, indexname from pg_indexes where indexname = 'tenancy_user1_tbl1_idx'; + select schemaname, relname from pg_statio_all_tables where relname = 'tenancy_user1_tbl1'; + select schemaname, relname, indexrelname from pg_stat_all_indexes where indexrelname = 'tenancy_user1_tbl1_idx'; + + --information_schema views + select * from information_schema.table_constraints where constraint_name = 'tenancy_user1_tbl2_constraint'; + select catalog_name, schema_name from information_schema.schemata where schema_name = 'tenancy_user1'; + select sequence_catalog, sequence_schema, sequence_name from information_schema.sequences + where sequence_name = 'tenancy_user1_tbl1_tenancy_user1_tbl1_column1_seq'; + select * from information_schema.tables where table_name = 'tenancy_user1_tbl1'; + select * from information_schema.triggers where trigger_name = 'tenancy_user1_tbl1_before_ins_stmt_trig'; + select * from information_schema.views where table_name = 'tenancy_user1_view1'; + + RESET ROLE; + + -- Try to get the objects created by tenancy_user2 from tenancy_user1 + SET SESSION ROLE tenancy_user1; + + --verify system catalogs to view the details of type, function and cast. + select proname from pg_proc where proname = 'tenancy_user2_tenancytestfunc'; + select proname, pronamespace from pg_proc where proname = 'tenancytesttype_in'; + select proname, pronamespace from pg_proc where proname = 'tenancytesttype_out'; + + --select the description of casttesttype + select description from pg_description where classoid = (select oid from pg_class where relname = 'pg_type') + and objoid = (select oid from pg_type where typname = 'tenancytesttype'); + + --select the language that is owned by the tenancy_user2 + select lanname, lanispl from pg_language where lanname = 'tenancy_lang1'; + + --Foriegn data wrapper, server and foreign table details + SELECT fdwname, fdwhandler::regproc, fdwvalidator::regproc, fdwoptions FROM pg_foreign_data_wrapper ORDER BY 1, 2, 3; + SELECT srvname, srvoptions FROM pg_foreign_server; + Select srvname FROM pg_user_mappings where srvname = 'tenancy_server'; + + SELECT relname, relkind from pg_class where relname = 'tenancy_user2_ft1'; + + --information_schema views + select foreign_data_wrapper_catalog, foreign_data_wrapper_name from information_schema.foreign_data_wrappers + where foreign_data_wrapper_name = 'tenancy_wrapper'; + select foreign_server_catalog, foreign_server_name, foreign_data_wrapper_catalog, foreign_data_wrapper_name + from information_schema.foreign_servers where foreign_server_name = 'tenancy_server'; + select * from information_schema.foreign_tables where foreign_table_name = 'tenancy_user2_ft1'; + select * from information_schema.user_mappings where foreign_server_name = 'tenancy_server'; + + RESET ROLE; + + + -- Delete the roles and it's associated objects. + SET SESSION ROLE tenancy_user1; + + DROP TABLE tenancy_user1_tbl1 cascade; + DROP TABLE tenancy_user1_tbl2 cascade; + DROP FUNCTION tenancy_user1_trigger_func() cascade; + DROP TABLE tenancy_user1_main_table cascade; + + RESET ROLE; + + SET SESSION ROLE tenancy_user2; + + DROP FUNCTION tenancy_user2_tenancytestfunc(text); + DROP FOREIGN TABLE tenancy_user2_ft1; + DROP LANGUAGE tenancy_lang1; + + RESET ROLE; + + DROP TYPE tenancytesttype cascade; + DROP USER MAPPING FOR tenancy_user2 SERVER tenancy_server; + DROP SERVER tenancy_server; + DROP FOREIGN DATA WRAPPER tenancy_wrapper; + + alter database regression with catalog security = false; + + drop schema tenancy_user1; + drop schema tenancy_user2; + + drop role tenancy_user1; + drop role tenancy_user2;