use crate::control::catalog_entry::CatalogEntry;
use crate::control::metadata_proposer::propose_catalog_entry;
use crate::control::security::audit::AuditEvent;
use crate::control::security::identity::AuthenticatedIdentity;
use crate::control::state::SharedState;
use crate::types::TenantId;
use super::super::super::result::{DdlError, DdlResult};
use super::super::auth_support::strip_if_exists;
use super::support::{ddl_err, resolve_tenant_ref, status, tenant_exists};
pub fn drop_tenant(
state: &SharedState,
identity: &AuthenticatedIdentity,
parts: &[&str],
) -> Result<Vec<DdlResult>, DdlError> {
if !identity.is_superuser {
return Err(ddl_err(
"42501",
"permission denied: only superuser can drop tenants",
));
}
if crate::control::server::shared::session::ddl_buffer::is_active() {
return Err(ddl_err(
"0A000",
"DROP TENANT is not allowed inside an explicit transaction",
));
}
let (if_exists, parts) = strip_if_exists(parts, 2);
if parts.len() < 3 {
return Err(ddl_err(
"42601",
"syntax: DROP TENANT [IF EXISTS] <id|name>",
));
}
let tenant_id = match resolve_tenant_ref(state, parts[2])? {
Some(tid) => tid,
None => {
if if_exists {
return Ok(status("DROP TENANT"));
}
return Err(ddl_err(
"42704",
format!("tenant '{}' does not exist", parts[2]),
));
}
};
let tid = tenant_id.as_u64();
if tid == 0 {
return Err(ddl_err("42501", "cannot drop system tenant (0)"));
}
if !tenant_exists(state, tenant_id)? {
if if_exists {
return Ok(status("DROP TENANT"));
}
return Err(ddl_err(
"42704",
format!("tenant '{}' does not exist", parts[2]),
));
}
reconcile_tenant_users(state, identity, tenant_id)?;
let entry = CatalogEntry::DeleteTenant { tenant_id: tid };
let log_index = propose_catalog_entry(state, &entry)
.map_err(|e| ddl_err("XX000", format!("metadata propose: {e}")))?;
if log_index == 0 {
{
let catalog = state.credentials.catalog();
catalog
.delete_tenant(tid)
.map_err(|e| ddl_err("XX000", format!("catalog write: {e}")))?;
}
let mut tenants = match state.tenants.lock() {
Ok(t) => t,
Err(p) => p.into_inner(),
};
tenants.remove_quota(tenant_id);
}
state.audit_record(
AuditEvent::TenantDeleted,
Some(tenant_id),
&identity.username,
&format!("dropped tenant {tenant_id}"),
);
Ok(status("DROP TENANT"))
}
fn reconcile_tenant_users(
state: &SharedState,
identity: &AuthenticatedIdentity,
tenant_id: TenantId,
) -> Result<(), DdlError> {
let Some(admin_username) = state
.credentials
.catalog()
.authoritative_tenant_admin(tenant_id.as_u64())
.map_err(|error| ddl_err("XX000", format!("load tenant administrator: {error}")))?
else {
return Ok(());
};
let members: Vec<String> = state
.credentials
.list_user_details()
.into_iter()
.filter(|u| u.tenant_id == tenant_id)
.map(|u| u.username)
.collect();
let mut lifecycle_admin = None;
let mut others = Vec::new();
for username in members {
if username == admin_username {
lifecycle_admin = Some(username);
} else {
others.push(username);
}
}
if !others.is_empty() {
others.sort();
return Err(ddl_err(
"42501",
format!(
"cannot drop tenant: {} user(s) still belong to it; drop or \
reassign them first: {}",
others.len(),
others.join(", ")
),
));
}
if let Some(admin) = lifecycle_admin {
let parts = ["DROP", "USER", admin.as_str()];
super::super::user::drop_tenant_admin(state, identity, &parts)?;
}
Ok(())
}