use sqlx::PgPool;
use pgroles_core::model::MembershipEdge;
#[derive(Debug, sqlx::FromRow)]
pub struct MembershipRow {
pub role_name: String,
pub member_name: String,
pub admin_option: bool,
pub inherit_option: bool,
}
impl MembershipRow {
pub fn to_membership_edge(&self) -> MembershipEdge {
MembershipEdge {
role: self.role_name.clone(),
member: self.member_name.clone(),
inherit: self.inherit_option,
admin: self.admin_option,
}
}
}
pub async fn fetch_memberships(
pool: &PgPool,
managed_roles: Option<&[&str]>,
) -> Result<Vec<MembershipRow>, sqlx::Error> {
let has_inherit_option = check_pg16_inherit_option(pool).await?;
if has_inherit_option {
fetch_memberships_pg16(pool, managed_roles).await
} else {
fetch_memberships_legacy(pool, managed_roles).await
}
}
async fn check_pg16_inherit_option(pool: &PgPool) -> Result<bool, sqlx::Error> {
let row: (bool,) = sqlx::query_as(
r#"
SELECT EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = 'pg_catalog'
AND table_name = 'pg_auth_members'
AND column_name = 'inherit_option'
)
"#,
)
.fetch_one(pool)
.await?;
Ok(row.0)
}
async fn fetch_memberships_pg16(
pool: &PgPool,
managed_roles: Option<&[&str]>,
) -> Result<Vec<MembershipRow>, sqlx::Error> {
match managed_roles {
Some(names) => {
sqlx::query_as::<_, MembershipRow>(
r#"
SELECT
gr.rolname AS role_name,
mr.rolname AS member_name,
m.admin_option,
m.inherit_option
FROM pg_auth_members m
JOIN pg_roles gr ON gr.oid = m.roleid
JOIN pg_roles mr ON mr.oid = m.member
WHERE gr.rolname = ANY($1)
ORDER BY gr.rolname, mr.rolname
"#,
)
.bind(names)
.fetch_all(pool)
.await
}
None => {
sqlx::query_as::<_, MembershipRow>(
r#"
SELECT
gr.rolname AS role_name,
mr.rolname AS member_name,
m.admin_option,
m.inherit_option
FROM pg_auth_members m
JOIN pg_roles gr ON gr.oid = m.roleid
JOIN pg_roles mr ON mr.oid = m.member
WHERE gr.rolname NOT LIKE 'pg_%'
ORDER BY gr.rolname, mr.rolname
"#,
)
.fetch_all(pool)
.await
}
}
}
async fn fetch_memberships_legacy(
pool: &PgPool,
managed_roles: Option<&[&str]>,
) -> Result<Vec<MembershipRow>, sqlx::Error> {
match managed_roles {
Some(names) => {
sqlx::query_as::<_, MembershipRow>(
r#"
SELECT
gr.rolname AS role_name,
mr.rolname AS member_name,
m.admin_option,
mr.rolinherit AS inherit_option
FROM pg_auth_members m
JOIN pg_roles gr ON gr.oid = m.roleid
JOIN pg_roles mr ON mr.oid = m.member
WHERE gr.rolname = ANY($1)
ORDER BY gr.rolname, mr.rolname
"#,
)
.bind(names)
.fetch_all(pool)
.await
}
None => {
sqlx::query_as::<_, MembershipRow>(
r#"
SELECT
gr.rolname AS role_name,
mr.rolname AS member_name,
m.admin_option,
mr.rolinherit AS inherit_option
FROM pg_auth_members m
JOIN pg_roles gr ON gr.oid = m.roleid
JOIN pg_roles mr ON mr.oid = m.member
WHERE gr.rolname NOT LIKE 'pg_%'
ORDER BY gr.rolname, mr.rolname
"#,
)
.fetch_all(pool)
.await
}
}
}