diff --git a/.sqlx/query-5e12c1d242ea9fcc68c20807b72300a7e131d2fb17fc74bd7f40a60b68df56c0.json b/.sqlx/query-5e12c1d242ea9fcc68c20807b72300a7e131d2fb17fc74bd7f40a60b68df56c0.json new file mode 100644 index 00000000..fa456c71 --- /dev/null +++ b/.sqlx/query-5e12c1d242ea9fcc68c20807b72300a7e131d2fb17fc74bd7f40a60b68df56c0.json @@ -0,0 +1,44 @@ +{ + "db_name": "PostgreSQL", + "query": "select \n rolname as \"name!\", \n rolsuper as \"is_super_user!\", \n rolcreatedb as \"can_create_db!\", \n rolcanlogin as \"can_login!\",\n rolbypassrls as \"can_bypass_rls!\"\nfrom pg_catalog.pg_roles;", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "name!", + "type_info": "Name" + }, + { + "ordinal": 1, + "name": "is_super_user!", + "type_info": "Bool" + }, + { + "ordinal": 2, + "name": "can_create_db!", + "type_info": "Bool" + }, + { + "ordinal": 3, + "name": "can_login!", + "type_info": "Bool" + }, + { + "ordinal": 4, + "name": "can_bypass_rls!", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + true, + true, + true, + true, + true + ] + }, + "hash": "5e12c1d242ea9fcc68c20807b72300a7e131d2fb17fc74bd7f40a60b68df56c0" +} diff --git a/crates/pgt_schema_cache/src/lib.rs b/crates/pgt_schema_cache/src/lib.rs index d9be527d..186fbdb9 100644 --- a/crates/pgt_schema_cache/src/lib.rs +++ b/crates/pgt_schema_cache/src/lib.rs @@ -5,6 +5,7 @@ mod columns; mod functions; mod policies; +mod roles; mod schema_cache; mod schemas; mod tables; @@ -15,6 +16,7 @@ mod versions; pub use columns::*; pub use functions::{Behavior, Function, FunctionArg, FunctionArgs}; pub use policies::{Policy, PolicyCommand}; +pub use roles::*; pub use schema_cache::SchemaCache; pub use schemas::Schema; pub use tables::{ReplicaIdentity, Table}; diff --git a/crates/pgt_schema_cache/src/queries/roles.sql b/crates/pgt_schema_cache/src/queries/roles.sql new file mode 100644 index 00000000..da5d0bfc --- /dev/null +++ b/crates/pgt_schema_cache/src/queries/roles.sql @@ -0,0 +1,7 @@ +select + rolname as "name!", + rolsuper as "is_super_user!", + rolcreatedb as "can_create_db!", + rolcanlogin as "can_login!", + rolbypassrls as "can_bypass_rls!" +from pg_catalog.pg_roles; \ No newline at end of file diff --git a/crates/pgt_schema_cache/src/roles.rs b/crates/pgt_schema_cache/src/roles.rs new file mode 100644 index 00000000..c212b791 --- /dev/null +++ b/crates/pgt_schema_cache/src/roles.rs @@ -0,0 +1,85 @@ +use crate::schema_cache::SchemaCacheItem; + +#[derive(Debug, PartialEq, Eq)] +pub struct Role { + pub name: String, + pub is_super_user: bool, + pub can_create_db: bool, + pub can_login: bool, + pub can_bypass_rls: bool, +} + +impl SchemaCacheItem for Role { + type Item = Role; + + async fn load(pool: &sqlx::PgPool) -> Result, sqlx::Error> { + sqlx::query_file_as!(Role, "src/queries/roles.sql") + .fetch_all(pool) + .await + } +} + +#[cfg(test)] +mod tests { + use crate::SchemaCache; + use pgt_test_utils::test_database::get_new_test_db; + use sqlx::Executor; + + #[tokio::test] + async fn loads_roles() { + let test_db = get_new_test_db().await; + + let setup = r#" + do $$ + begin + if not exists ( + select from pg_catalog.pg_roles + where rolname = 'test_super' + ) then + create role test_super superuser createdb login bypassrls; + end if; + if not exists ( + select from pg_catalog.pg_roles + where rolname = 'test_nologin' + ) then + create role test_nologin; + end if; + if not exists ( + select from pg_catalog.pg_roles + where rolname = 'test_login' + ) then + create role test_login login; + end if; + end $$; + "#; + + test_db + .execute(setup) + .await + .expect("Failed to setup test database"); + + let cache = SchemaCache::load(&test_db) + .await + .expect("Failed to load Schema Cache"); + + let roles = &cache.roles; + + let super_role = roles.iter().find(|r| r.name == "test_super").unwrap(); + assert!(super_role.is_super_user); + assert!(super_role.can_create_db); + assert!(super_role.can_login); + assert!(super_role.can_bypass_rls); + + let nologin_role = roles.iter().find(|r| r.name == "test_nologin").unwrap(); + assert!(!nologin_role.is_super_user); + assert!(!nologin_role.can_create_db); + assert!(!nologin_role.can_login); + assert!(!nologin_role.can_bypass_rls); + + let login_role = roles.iter().find(|r| r.name == "test_login").unwrap(); + assert!(!login_role.is_super_user); + assert!(!login_role.can_create_db); + assert!(login_role.can_login); + assert!(!login_role.can_bypass_rls); + } +} diff --git a/crates/pgt_schema_cache/src/schema_cache.rs b/crates/pgt_schema_cache/src/schema_cache.rs index b21d2baf..516b37e6 100644 --- a/crates/pgt_schema_cache/src/schema_cache.rs +++ b/crates/pgt_schema_cache/src/schema_cache.rs @@ -1,6 +1,5 @@ use sqlx::postgres::PgPool; -use crate::Trigger; use crate::columns::Column; use crate::functions::Function; use crate::policies::Policy; @@ -8,6 +7,7 @@ use crate::schemas::Schema; use crate::tables::Table; use crate::types::PostgresType; use crate::versions::Version; +use crate::{Role, Trigger}; #[derive(Debug, Default)] pub struct SchemaCache { @@ -19,11 +19,12 @@ pub struct SchemaCache { pub columns: Vec, pub policies: Vec, pub triggers: Vec, + pub roles: Vec, } impl SchemaCache { pub async fn load(pool: &PgPool) -> Result { - let (schemas, tables, functions, types, versions, columns, policies, triggers) = futures_util::try_join!( + let (schemas, tables, functions, types, versions, columns, policies, triggers, roles) = futures_util::try_join!( Schema::load(pool), Table::load(pool), Function::load(pool), @@ -32,6 +33,7 @@ impl SchemaCache { Column::load(pool), Policy::load(pool), Trigger::load(pool), + Role::load(pool) )?; Ok(SchemaCache { @@ -43,6 +45,7 @@ impl SchemaCache { columns, policies, triggers, + roles, }) }