use std::sync::Arc;
use futures::stream;
use surrealdb_types::ToSql;
use crate::catalog::providers::{DatabaseProvider, NamespaceProvider, UserProvider};
use crate::err::Error;
use crate::exec::context::{ContextLevel, ExecutionContext};
use crate::exec::physical_expr::{EvalContext, PhysicalExpr};
use crate::exec::{
AccessMode, CardinalityHint, ExecOperator, FlowResult, OperatorMetrics, ValueBatch,
ValueBatchStream,
};
use crate::expr::Base;
use crate::expr::statements::info::InfoStructure;
use crate::iam::{Action, ResourceKind};
use crate::val::Value;
#[derive(Debug)]
pub struct UserInfoPlan {
pub user: Arc<dyn PhysicalExpr>,
pub base: Option<Base>,
pub structured: bool,
pub(crate) metrics: Arc<OperatorMetrics>,
}
impl UserInfoPlan {
pub(crate) fn new(user: Arc<dyn PhysicalExpr>, base: Option<Base>, structured: bool) -> Self {
Self {
user,
base,
structured,
metrics: Arc::new(OperatorMetrics::new()),
}
}
fn context_level_for_base(&self) -> ContextLevel {
match self.base {
Some(Base::Root) | None => ContextLevel::Root,
Some(Base::Ns) => ContextLevel::Namespace,
Some(Base::Db) => ContextLevel::Database,
}
}
}
impl ExecOperator for UserInfoPlan {
fn name(&self) -> &'static str {
"InfoUser"
}
fn attrs(&self) -> Vec<(String, String)> {
let mut attrs = vec![("user".to_string(), self.user.to_sql())];
if let Some(ref base) = self.base {
attrs.push(("base".to_string(), format!("{:?}", base)));
}
attrs.push(("structured".to_string(), self.structured.to_string()));
attrs
}
fn required_context(&self) -> ContextLevel {
self.user.required_context().max(self.context_level_for_base())
}
fn access_mode(&self) -> AccessMode {
self.user.access_mode()
}
fn cardinality_hint(&self) -> CardinalityHint {
CardinalityHint::AtMostOne
}
fn metrics(&self) -> Option<&OperatorMetrics> {
Some(self.metrics.as_ref())
}
fn expressions(&self) -> Vec<(&str, &Arc<dyn PhysicalExpr>)> {
vec![("user", &self.user)]
}
fn execute(&self, ctx: &ExecutionContext) -> FlowResult<ValueBatchStream> {
let user = Arc::clone(&self.user);
let base = self.base;
let structured = self.structured;
let ctx = ctx.clone();
Ok(Box::pin(stream::once(async move {
let value = execute_user_info(&ctx, &*user, base, structured).await?;
Ok(ValueBatch {
values: vec![value],
})
})))
}
fn is_scalar(&self) -> bool {
true
}
}
async fn execute_user_info(
ctx: &ExecutionContext,
user_expr: &dyn PhysicalExpr,
base: Option<Base>,
structured: bool,
) -> crate::expr::FlowResult<Value> {
let root = ctx.root();
let opt = root
.options
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Options not available in execution context"))?;
let base = base.unwrap_or(opt.selected_base()?);
ctx.is_allowed(Action::View, ResourceKind::Actor, base)?;
let eval_ctx = EvalContext::from_exec_ctx(ctx);
let user_value = user_expr.evaluate(eval_ctx).await?;
let user = user_value.coerce_to::<String>().map_err(|e| anyhow::anyhow!("{e}"))?;
let txn = ctx.txn();
let res = match base {
Base::Root => txn.expect_root_user(&user).await?,
Base::Ns => {
let ns_name = opt.ns()?;
let ns = txn.expect_ns_by_name(ns_name).await?;
match txn.get_ns_user(ns.namespace_id, &user, None).await? {
Some(user_def) => user_def,
None => {
return Err(Error::UserNsNotFound {
name: user,
ns: ns.name.to_string(),
}
.into());
}
}
}
Base::Db => {
let (ns_name, db_name) = opt.ns_db()?;
let Some(db_def) = txn.get_db_by_name(ns_name, db_name, None).await? else {
return Err(Error::UserDbNotFound {
name: user,
ns: ns_name.to_string(),
db: db_name.to_string(),
}
.into());
};
txn.get_db_user(db_def.namespace_id, db_def.database_id, &user, None)
.await?
.ok_or_else(|| Error::UserDbNotFound {
name: user,
ns: ns_name.to_string(),
db: db_name.to_string(),
})?
}
};
Ok(if structured {
res.as_ref().clone().structure()
} else {
Value::from(res.as_ref().to_sql())
})
}