use crate::identifier::{Identifier, QualifiedName};
use crate::ir::grant::GrantTarget;
use crate::ir::policy::Policy;
#[must_use]
pub fn create_policy(table: &QualifiedName, p: &Policy) -> String {
let mut sql = format!(
"CREATE POLICY {} ON {} AS {} FOR {} TO {}",
p.name.render_sql(),
table.render_sql(),
if p.permissive {
"PERMISSIVE"
} else {
"RESTRICTIVE"
},
p.command.sql_keyword(),
render_roles(&p.roles),
);
if let Some(u) = &p.using {
sql.push_str(&format!(" USING ({})", u.canonical_text));
}
if let Some(c) = &p.with_check {
sql.push_str(&format!(" WITH CHECK ({})", c.canonical_text));
}
sql.push(';');
sql
}
#[must_use]
pub fn alter_policy(table: &QualifiedName, p: &Policy) -> String {
let mut sql = format!(
"ALTER POLICY {} ON {} TO {}",
p.name.render_sql(),
table.render_sql(),
render_roles(&p.roles),
);
if let Some(u) = &p.using {
sql.push_str(&format!(" USING ({})", u.canonical_text));
}
if let Some(c) = &p.with_check {
sql.push_str(&format!(" WITH CHECK ({})", c.canonical_text));
}
sql.push(';');
sql
}
#[must_use]
pub fn drop_policy(table: &QualifiedName, name: &Identifier) -> String {
format!(
"DROP POLICY {} ON {};",
name.render_sql(),
table.render_sql()
)
}
#[must_use]
pub fn set_table_row_security(qname: &QualifiedName, enable: bool) -> String {
let verb = if enable { "ENABLE" } else { "DISABLE" };
format!(
"ALTER TABLE {} {} ROW LEVEL SECURITY;",
qname.render_sql(),
verb
)
}
#[must_use]
pub fn set_table_force_row_security(qname: &QualifiedName, force: bool) -> String {
let verb = if force { "FORCE" } else { "NO FORCE" };
format!(
"ALTER TABLE {} {} ROW LEVEL SECURITY;",
qname.render_sql(),
verb
)
}
fn render_roles(roles: &[GrantTarget]) -> String {
let parts: Vec<String> = roles
.iter()
.map(|r| match r {
GrantTarget::Public => "PUBLIC".to_string(),
GrantTarget::Role(id) => id.render_sql(),
})
.collect();
parts.join(", ")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::default_expr::NormalizedExpr;
use crate::ir::policy::PolicyCommand;
fn id(s: &str) -> Identifier {
Identifier::from_unquoted(s).unwrap()
}
fn qn(schema: &str, name: &str) -> QualifiedName {
QualifiedName::new(id(schema), id(name))
}
fn simple_policy() -> Policy {
Policy {
name: id("p1"),
permissive: true,
command: PolicyCommand::All,
roles: vec![GrantTarget::Public],
using: Some(NormalizedExpr::from_canonical_text("true")),
with_check: None,
}
}
#[test]
fn renders_create_policy() {
let sql = create_policy(&qn("app", "docs"), &simple_policy());
assert_eq!(
sql,
"CREATE POLICY p1 ON app.docs AS PERMISSIVE FOR ALL TO PUBLIC USING (true);"
);
}
#[test]
fn renders_restrictive_with_check() {
let mut p = simple_policy();
p.permissive = false;
p.command = PolicyCommand::Insert;
p.with_check = Some(NormalizedExpr::from_canonical_text("author = current_user"));
p.using = None;
let sql = create_policy(&qn("app", "docs"), &p);
assert_eq!(
sql,
"CREATE POLICY p1 ON app.docs AS RESTRICTIVE FOR INSERT TO PUBLIC WITH CHECK (author = current_user);"
);
}
#[test]
fn renders_multi_role_to_clause() {
let mut p = simple_policy();
p.roles = vec![GrantTarget::Public, GrantTarget::Role(id("readers"))];
let sql = create_policy(&qn("app", "docs"), &p);
assert!(sql.contains("TO PUBLIC, readers"), "got: {sql}");
}
#[test]
fn renders_alter_policy() {
let sql = alter_policy(&qn("app", "docs"), &simple_policy());
assert_eq!(sql, "ALTER POLICY p1 ON app.docs TO PUBLIC USING (true);");
}
#[test]
fn renders_drop_policy() {
let sql = drop_policy(&qn("app", "docs"), &id("p1"));
assert_eq!(sql, "DROP POLICY p1 ON app.docs;");
}
#[test]
fn renders_enable_disable_rls() {
assert_eq!(
set_table_row_security(&qn("app", "docs"), true),
"ALTER TABLE app.docs ENABLE ROW LEVEL SECURITY;"
);
assert_eq!(
set_table_row_security(&qn("app", "docs"), false),
"ALTER TABLE app.docs DISABLE ROW LEVEL SECURITY;"
);
}
#[test]
fn renders_force_no_force_rls() {
assert_eq!(
set_table_force_row_security(&qn("app", "docs"), true),
"ALTER TABLE app.docs FORCE ROW LEVEL SECURITY;"
);
assert_eq!(
set_table_force_row_security(&qn("app", "docs"), false),
"ALTER TABLE app.docs NO FORCE ROW LEVEL SECURITY;"
);
}
}