use serde_json::{Map, Value as JsonValue};
use crate::control::security::identity::AuthenticatedIdentity;
use crate::control::server::response_shape::types::ShapedRows;
use crate::control::state::SharedState;
use super::super::result::{DdlError, DdlResult};
fn ddl_err(sqlstate: &str, message: impl Into<String>) -> DdlError {
DdlError {
sqlstate: sqlstate.to_string(),
message: message.into(),
}
}
pub fn define_dimension(
_state: &SharedState,
identity: &AuthenticatedIdentity,
parts: &[&str],
) -> Result<Vec<DdlResult>, DdlError> {
if !identity.is_superuser {
return Err(ddl_err("42501", "permission denied: requires superuser"));
}
if parts.len() < 5 {
return Err(ddl_err(
"42601",
"syntax: DEFINE METERING DIMENSION '<name>' UNIT '<unit>'",
));
}
let _name = parts[3].trim_matches('\'');
let _unit = parts
.iter()
.position(|p| p.to_uppercase() == "UNIT")
.and_then(|i| parts.get(i + 1))
.map(|s| s.trim_matches('\''))
.unwrap_or("tokens");
Ok(vec![DdlResult::Status {
command: "DEFINE METERING DIMENSION".to_string(),
rows_affected: None,
}])
}
pub fn show_usage(
state: &SharedState,
_identity: &AuthenticatedIdentity,
parts: &[&str],
) -> Result<Vec<DdlResult>, DdlError> {
let (user_filter, org_filter) = parse_for_clause(parts);
let events = state.usage_store.query(
user_filter.as_deref(),
org_filter.as_deref(),
0, );
let columns = vec![
"auth_user_id".to_string(),
"org_id".to_string(),
"collection".to_string(),
"operation".to_string(),
"tokens".to_string(),
"timestamp".to_string(),
];
let rows: Vec<Map<String, JsonValue>> = events
.iter()
.map(|e| {
let mut row = Map::new();
row.insert(
"auth_user_id".to_string(),
JsonValue::String(e.auth_user_id.clone()),
);
row.insert("org_id".to_string(), JsonValue::String(e.org_id.clone()));
row.insert(
"collection".to_string(),
JsonValue::String(e.collection.clone()),
);
row.insert(
"operation".to_string(),
JsonValue::String(e.operation.clone()),
);
row.insert(
"tokens".to_string(),
JsonValue::String(e.tokens.to_string()),
);
row.insert(
"timestamp".to_string(),
JsonValue::String(e.timestamp_secs.to_string()),
);
row
})
.collect();
Ok(vec![DdlResult::Rows(ShapedRows {
columns,
column_types: ShapedRows::text_types(6),
rows,
notice: None,
})])
}
pub fn show_quota(
state: &SharedState,
_identity: &AuthenticatedIdentity,
parts: &[&str],
) -> Result<Vec<DdlResult>, DdlError> {
let (user_filter, _org_filter) = parse_for_clause(parts);
let grantee_id = user_filter.as_deref().unwrap_or("");
let quotas = state.quota_manager.list_quotas();
let columns = vec![
"scope".to_string(),
"max_tokens".to_string(),
"used_tokens".to_string(),
"remaining".to_string(),
"pct_used".to_string(),
"enforcement".to_string(),
"exceeded".to_string(),
];
let rows: Vec<Map<String, JsonValue>> = quotas
.iter()
.filter_map(|q| state.quota_manager.get_status(&q.scope_name, grantee_id))
.map(|s| {
let mut row = Map::new();
row.insert("scope".to_string(), JsonValue::String(s.scope_name.clone()));
row.insert(
"max_tokens".to_string(),
JsonValue::String(s.max_tokens.to_string()),
);
row.insert(
"used_tokens".to_string(),
JsonValue::String(s.used_tokens.to_string()),
);
row.insert(
"remaining".to_string(),
JsonValue::String(s.remaining.to_string()),
);
row.insert(
"pct_used".to_string(),
JsonValue::String(format!("{:.1}%", s.pct_used * 100.0)),
);
row.insert(
"enforcement".to_string(),
JsonValue::String(format!("{:?}", s.enforcement)),
);
row.insert(
"exceeded".to_string(),
JsonValue::String(s.exceeded.to_string()),
);
row
})
.collect();
Ok(vec![DdlResult::Rows(ShapedRows {
columns,
column_types: ShapedRows::text_types(7),
rows,
notice: None,
})])
}
pub fn show_usage_for_tenant(
state: &SharedState,
identity: &AuthenticatedIdentity,
parts: &[&str],
) -> Result<Vec<DdlResult>, DdlError> {
if !identity.is_superuser {
return Err(ddl_err("42501", "permission denied: requires superuser"));
}
let tid: u64 = parts
.iter()
.position(|p| p.eq_ignore_ascii_case("TENANT"))
.and_then(|i| parts.get(i + 1))
.and_then(|s| s.parse().ok())
.ok_or_else(|| ddl_err("42601", "syntax: SHOW USAGE FOR TENANT <id>"))?;
let columns = vec!["metric".to_string(), "value".to_string()];
let tenants = match state.tenants.lock() {
Ok(t) => t,
Err(p) => p.into_inner(),
};
let mut rows = Vec::new();
if let Some(usage) = tenants.usage(crate::types::TenantId::new(tid)) {
let metrics: &[(&str, u64)] = &[
("memory_bytes", usage.memory_bytes),
("storage_bytes", usage.storage_bytes),
("active_requests", usage.active_requests as u64),
("qps_current", usage.requests_this_second as u64),
("total_requests", usage.total_requests),
("rejected_requests", usage.rejected_requests),
("active_connections", usage.active_connections as u64),
];
for &(name, val) in metrics {
let mut row = Map::new();
row.insert("metric".to_string(), JsonValue::String(name.to_string()));
row.insert("value".to_string(), JsonValue::String(val.to_string()));
rows.push(row);
}
}
Ok(vec![DdlResult::Rows(ShapedRows {
columns,
column_types: ShapedRows::text_types(2),
rows,
notice: None,
})])
}
pub fn export_usage(
state: &SharedState,
identity: &AuthenticatedIdentity,
parts: &[&str],
) -> Result<Vec<DdlResult>, DdlError> {
if !identity.is_superuser {
return Err(ddl_err("42501", "permission denied: requires superuser"));
}
let tid: u64 = parts
.iter()
.position(|p| p.eq_ignore_ascii_case("TENANT"))
.and_then(|i| parts.get(i + 1))
.and_then(|s| s.parse().ok())
.ok_or_else(|| {
ddl_err(
"42601",
"syntax: EXPORT USAGE FOR TENANT <id> [PERIOD '<month>'] FORMAT 'json'",
)
})?;
let since_secs = parts
.iter()
.position(|p| p.eq_ignore_ascii_case("PERIOD"))
.and_then(|i| parts.get(i + 1))
.and_then(|s| {
let s = s.trim_matches('\'');
let mut iter = s.split('-');
let year: i32 = iter.next()?.parse().ok()?;
let month: u32 = iter.next()?.parse().ok()?;
let days_since_epoch =
(year as i64 - 1970) * 365 + (year as i64 - 1969) / 4 + (month as i64 - 1) * 30;
Some(days_since_epoch.max(0) as u64 * 86400)
})
.unwrap_or(0);
let json = state.usage_store.export_tenant_json(tid, since_secs);
let mut row = Map::new();
row.insert("usage_json".to_string(), JsonValue::String(json));
Ok(vec![DdlResult::Rows(ShapedRows {
columns: vec!["usage_json".to_string()],
column_types: ShapedRows::text_types(1),
rows: vec![row],
notice: None,
})])
}
fn parse_for_clause(parts: &[&str]) -> (Option<String>, Option<String>) {
let for_idx = parts.iter().position(|p| p.to_uppercase() == "FOR");
let Some(idx) = for_idx else {
return (None, None);
};
let grantee_type = parts
.get(idx + 1)
.map(|s| s.to_uppercase())
.unwrap_or_default();
match grantee_type.as_str() {
"AUTH" => {
let id = parts.get(idx + 3).map(|s| s.trim_matches('\'').to_string());
(id, None)
}
"ORG" => {
let id = parts.get(idx + 2).map(|s| s.trim_matches('\'').to_string());
(None, id)
}
"USER" => {
let id = parts.get(idx + 2).map(|s| s.trim_matches('\'').to_string());
(id, None)
}
_ => (None, None),
}
}