use nodedb_types::id::DatabaseId;
use smallvec::SmallVec;
use crate::control::security::identity::DatabaseSet;
use crate::control::state::SharedState;
use super::super::super::result::DdlError;
pub(super) fn err(sqlstate: &str, message: impl Into<String>) -> DdlError {
DdlError {
sqlstate: sqlstate.to_string(),
message: message.into(),
}
}
pub(super) fn parse_key_scopes(
parts: &[&str],
state: &SharedState,
) -> Result<Vec<crate::control::security::apikey::KeyScope>, DdlError> {
let scopes_idx = parts.iter().position(|p| p.to_uppercase() == "SCOPES");
let Some(idx) = scopes_idx else {
return Ok(vec![]);
};
if idx == 0 || parts[idx - 1].to_uppercase() != "WITH" {
return Ok(vec![]);
}
let scope_names: Vec<&str> = parts[idx + 1..]
.iter()
.take_while(|p| {
let up = p.to_uppercase();
!up.starts_with("EXPIRES") && !up.starts_with("DATABASES") && up != "WITH"
})
.map(|s| s.trim_matches('\'').trim_end_matches(','))
.collect();
let mut key_scopes = Vec::new();
for scope_name in &scope_names {
let resolved = state.scope_defs.resolve(scope_name);
if resolved.is_empty() {
return Err(err(
"42704",
format!("scope '{scope_name}' not found or empty"),
));
}
for (perm, coll) in resolved {
key_scopes.push(crate::control::security::apikey::KeyScope {
permission: perm,
collection: coll,
});
}
}
Ok(key_scopes)
}
pub(super) fn parse_with_databases(
parts: &[&str],
state: &SharedState,
) -> Result<Option<Vec<DatabaseId>>, DdlError> {
let db_idx = parts.iter().position(|p| p.to_uppercase() == "DATABASES");
let Some(idx) = db_idx else {
return Ok(None);
};
if idx == 0 || parts[idx - 1].to_uppercase() != "WITH" {
return Ok(None);
}
let raw_names: Vec<&str> = parts[idx + 1..]
.iter()
.take_while(|p| !p.to_uppercase().starts_with("EXPIRES"))
.map(|s| {
s.trim_start_matches('(')
.trim_end_matches(')')
.trim_end_matches(',')
})
.filter(|s| !s.is_empty())
.collect();
if raw_names.is_empty() {
return Err(err(
"42601",
"WITH DATABASES requires at least one database name",
));
}
let catalog = state.credentials.catalog();
let mut ids = Vec::with_capacity(raw_names.len());
for name in raw_names {
let resolved: Option<DatabaseId> = catalog
.get_database_id_by_name(name)
.map_err(|e| err("XX000", e.to_string()))?;
match resolved {
Some(id) => ids.push(id),
None => {
return Err(err("42704", format!("database '{name}' not found")));
}
}
}
Ok(Some(ids))
}
pub(super) fn build_owner_database_set_for_user(
state: &SharedState,
user: &crate::control::security::credential::record::UserRecord,
) -> Result<DatabaseSet, DdlError> {
if user.is_superuser {
return Ok(DatabaseSet::All);
}
if user.is_service_account && !user.accessible_databases.is_empty() {
return Ok(DatabaseSet::Some(SmallVec::from_iter(
user.accessible_databases.iter().copied(),
)));
}
let db_ids = state
.credentials
.catalog()
.list_user_grant_databases(user.user_id)
.map_err(|e| err("XX000", e.to_string()))?;
Ok(DatabaseSet::Some(SmallVec::from_iter(db_ids)))
}