use crate::catalog::id::DbObjectId;
use crate::catalog::policy::{Policy, PolicyCommand};
use crate::diff::operations::{PolicyIdentifier, PolicyOperation};
use crate::render::{RenderedSql, SqlRenderer, quote_ident};
impl SqlRenderer for PolicyOperation {
fn to_sql(&self) -> Vec<RenderedSql> {
match self {
PolicyOperation::Create { policy } => {
vec![render_create_policy(policy)]
}
PolicyOperation::Drop { identifier } => {
vec![render_drop_policy(identifier)]
}
PolicyOperation::Alter {
identifier,
new_roles,
new_using,
new_with_check,
} => {
vec![render_alter_policy(
identifier,
new_roles,
new_using,
new_with_check,
)]
}
PolicyOperation::Replace { new_policy, .. } => {
vec![
render_drop_policy(&PolicyIdentifier::from_policy(new_policy)),
render_create_policy(new_policy),
]
}
}
}
fn db_object_id(&self) -> DbObjectId {
match self {
PolicyOperation::Create { policy } => DbObjectId::Policy {
schema: policy.schema.clone(),
table: policy.table_name.clone(),
name: policy.name.clone(),
},
PolicyOperation::Drop { identifier } | PolicyOperation::Alter { identifier, .. } => {
DbObjectId::Policy {
schema: identifier.schema.clone(),
table: identifier.table.clone(),
name: identifier.name.clone(),
}
}
PolicyOperation::Replace { new_policy, .. } => DbObjectId::Policy {
schema: new_policy.schema.clone(),
table: new_policy.table_name.clone(),
name: new_policy.name.clone(),
},
}
}
}
fn render_create_policy(policy: &Policy) -> RenderedSql {
let mut sql = format!(
"CREATE POLICY {} ON {}.{}",
quote_ident(&policy.name),
quote_ident(&policy.schema),
quote_ident(&policy.table_name)
);
if !policy.permissive {
sql.push_str(" AS RESTRICTIVE");
}
let cmd = match policy.command {
PolicyCommand::All => "ALL",
PolicyCommand::Select => "SELECT",
PolicyCommand::Insert => "INSERT",
PolicyCommand::Update => "UPDATE",
PolicyCommand::Delete => "DELETE",
};
sql.push_str(&format!(" FOR {}", cmd));
if policy.roles.is_empty() {
sql.push_str(" TO PUBLIC");
} else {
let roles: Vec<String> = policy.roles.iter().map(|r| quote_ident(r)).collect();
sql.push_str(&format!(" TO {}", roles.join(", ")));
}
if let Some(using) = &policy.using_expr {
sql.push_str(&format!(" USING ({})", using));
}
if let Some(check) = &policy.with_check_expr {
sql.push_str(&format!(" WITH CHECK ({})", check));
}
sql.push(';');
RenderedSql::new(sql)
}
fn render_drop_policy(identifier: &PolicyIdentifier) -> RenderedSql {
let sql = format!(
"DROP POLICY {} ON {}.{};",
quote_ident(&identifier.name),
quote_ident(&identifier.schema),
quote_ident(&identifier.table)
);
RenderedSql::new(sql)
}
fn render_alter_policy(
identifier: &PolicyIdentifier,
new_roles: &Option<Vec<String>>,
new_using: &Option<Option<String>>,
new_with_check: &Option<Option<String>>,
) -> RenderedSql {
let mut sql = format!(
"ALTER POLICY {} ON {}.{}",
quote_ident(&identifier.name),
quote_ident(&identifier.schema),
quote_ident(&identifier.table)
);
let mut parts = Vec::new();
if let Some(roles) = new_roles {
if roles.is_empty() {
parts.push("TO PUBLIC".to_string());
} else {
let role_list: Vec<String> = roles.iter().map(|r| quote_ident(r)).collect();
parts.push(format!("TO {}", role_list.join(", ")));
}
}
if let Some(using) = new_using
&& let Some(expr) = using
{
parts.push(format!("USING ({})", expr));
}
if let Some(check) = new_with_check
&& let Some(expr) = check
{
parts.push(format!("WITH CHECK ({})", expr));
}
if !parts.is_empty() {
sql.push(' ');
sql.push_str(&parts.join(" "));
}
sql.push(';');
RenderedSql::new(sql)
}