#![allow(dead_code)]
use std::collections::HashMap;
use std::sync::Arc;
use surrealdb_strand::Strand;
use tokio_util::sync::CancellationToken;
use uuid::Uuid;
use crate::catalog::{DatabaseDefinition, IndexDefinition, NamespaceDefinition, TableDefinition};
use crate::ctx::{Context, FrozenContext};
use crate::dbs::{Capabilities, Options};
use crate::err::Error;
use crate::exec::function::FunctionRegistry;
use crate::expr::Base;
use crate::iam::{Action, Auth, ResourceKind};
use crate::kvs::index::filter_online_indexes;
use crate::kvs::{Datastore, Transaction};
use crate::val::{Datetime, TableName, Value};
pub(crate) type Parameters = HashMap<Strand, Arc<Value>>;
pub(crate) type FieldStateCache = Arc<
tokio::sync::RwLock<
HashMap<(TableName, bool), Arc<crate::exec::operators::scan::pipeline::FieldState>>,
>,
>;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Default)]
pub enum ContextLevel {
#[default]
Root = 0,
Namespace = 1,
Database = 2,
}
impl ContextLevel {
pub fn short_name(self) -> &'static str {
match self {
Self::Root => "Rt",
Self::Namespace => "Ns",
Self::Database => "Db",
}
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct SessionInfo {
pub ns: Option<Strand>,
pub db: Option<Strand>,
pub id: Option<Uuid>,
pub ip: Option<Strand>,
pub origin: Option<Strand>,
pub ac: Option<Strand>,
pub rd: Option<Value>,
pub token: Option<Value>,
pub exp: Option<Datetime>,
}
#[derive(Clone)]
pub struct RootContext {
pub ctx: FrozenContext,
pub options: Option<Options>,
pub datastore: Option<Arc<Datastore>>,
pub cancellation: CancellationToken,
pub auth: Arc<Auth>,
pub(crate) session: Option<Arc<SessionInfo>>,
pub(crate) current_value: Option<Arc<Value>>,
pub(crate) skip_fetch_perms: bool,
pub(crate) version_stamp: Option<u64>,
}
impl std::fmt::Debug for RootContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RootContext")
.field("datastore", &self.datastore.as_ref().map(|_| "<Datastore>"))
.field("cancellation", &self.cancellation)
.field("auth", &self.auth)
.field("session", &self.session)
.field("current_value", &self.current_value.as_ref().map(|_| "<Value>"))
.field("skip_fetch_perms", &self.skip_fetch_perms)
.field("version_stamp", &self.version_stamp)
.field("ctx", &"<FrozenContext>")
.finish()
}
}
#[derive(Clone)]
pub struct NamespaceContext {
pub root: RootContext,
pub ns: Arc<NamespaceDefinition>,
}
impl std::fmt::Debug for NamespaceContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NamespaceContext").field("root", &self.root).field("ns", &self.ns).finish()
}
}
impl NamespaceContext {
pub fn ns_name(&self) -> &str {
&self.ns.name
}
pub fn txn(&self) -> Arc<Transaction> {
self.root.ctx.tx()
}
pub fn datastore(&self) -> Option<&Datastore> {
self.root.datastore.as_deref()
}
}
#[derive(Clone)]
pub struct DatabaseContext {
pub ns_ctx: NamespaceContext,
pub db: Arc<DatabaseDefinition>,
pub(crate) field_state_cache: FieldStateCache,
pub(crate) table_def_cache:
Arc<tokio::sync::RwLock<HashMap<TableName, Option<Arc<TableDefinition>>>>>,
pub(crate) index_def_cache:
Arc<tokio::sync::RwLock<HashMap<TableName, Arc<[IndexDefinition]>>>>,
}
impl std::fmt::Debug for DatabaseContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DatabaseContext")
.field("ns_ctx", &self.ns_ctx)
.field("db", &self.db)
.field("field_state_cache", &"<cache>")
.field("table_def_cache", &"<cache>")
.finish()
}
}
impl DatabaseContext {
pub fn ns_name(&self) -> &str {
self.ns_ctx.ns_name()
}
pub fn db_name(&self) -> &str {
&self.db.name
}
pub fn ns(&self) -> &NamespaceDefinition {
&self.ns_ctx.ns
}
pub fn txn(&self) -> Arc<Transaction> {
self.ns_ctx.txn()
}
pub fn datastore(&self) -> Option<&Datastore> {
self.ns_ctx.datastore()
}
pub(crate) async fn get_table_def(
&self,
table: &TableName,
version: Option<u64>,
) -> anyhow::Result<Option<Arc<TableDefinition>>> {
use crate::catalog::providers::TableProvider;
if version.is_none() {
let cache = self.table_def_cache.read().await;
if let Some(cached) = cache.get(table) {
return Ok(cached.clone());
}
}
let txn = self.txn();
let result =
txn.get_tb_by_name(&self.ns_ctx.ns.name, &self.db.name, table, version).await?;
if version.is_none() {
self.table_def_cache.write().await.insert(table.clone(), result.clone());
}
Ok(result)
}
pub(crate) async fn get_table_indexes(
&self,
table: &TableName,
version: Option<u64>,
) -> anyhow::Result<Arc<[IndexDefinition]>> {
use crate::catalog::providers::TableProvider;
if version.is_none() {
let cached = {
let cache = self.index_def_cache.read().await;
cache.get(table).cloned()
};
if let Some(cached) = cached {
let txn = self.txn();
return filter_online_indexes(
&txn,
self.ns_ctx.ns.namespace_id,
self.db.database_id,
cached,
)
.await;
}
}
let txn = self.txn();
let result = txn
.all_tb_indexes(self.ns_ctx.ns.namespace_id, self.db.database_id, table, version)
.await?;
if version.is_none() {
self.index_def_cache.write().await.insert(table.clone(), Arc::clone(&result));
}
if version.is_none() {
filter_online_indexes(&txn, self.ns_ctx.ns.namespace_id, self.db.database_id, result)
.await
} else {
Ok(result)
}
}
}
#[derive(Debug, Clone)]
pub enum ExecutionContext {
Root(RootContext),
Namespace(NamespaceContext),
Database(DatabaseContext),
}
impl ExecutionContext {
pub fn root(&self) -> &RootContext {
match self {
Self::Root(r) => r,
Self::Namespace(n) => &n.root,
Self::Database(d) => &d.ns_ctx.root,
}
}
pub fn namespace(&self) -> Result<&NamespaceContext, Error> {
match self {
Self::Root(_) => Err(Error::NsEmpty),
Self::Namespace(n) => Ok(n),
Self::Database(d) => Ok(&d.ns_ctx),
}
}
pub fn database(&self) -> Result<&DatabaseContext, Error> {
match self {
Self::Root(_) | Self::Namespace(_) => Err(Error::DbEmpty),
Self::Database(d) => Ok(d),
}
}
pub fn level(&self) -> ContextLevel {
match self {
Self::Root(_) => ContextLevel::Root,
Self::Namespace(_) => ContextLevel::Namespace,
Self::Database(_) => ContextLevel::Database,
}
}
pub fn ctx(&self) -> &FrozenContext {
&self.root().ctx
}
pub fn txn(&self) -> Arc<Transaction> {
self.root().ctx.tx()
}
pub fn value(&self, key: &str) -> Option<&Value> {
self.root().ctx.value(key)
}
pub fn collect_params(&self) -> Parameters {
self.root().ctx.collect_values(HashMap::new())
}
pub fn datastore(&self) -> Option<&Datastore> {
self.root().datastore.as_deref()
}
pub fn auth(&self) -> &Auth {
&self.root().auth
}
pub fn auth_enabled(&self) -> bool {
self.root().ctx.auth_enabled()
}
pub fn should_check_perms(&self, action: Action) -> Result<bool, Error> {
let root = self.root();
if !root.ctx.auth_enabled() && root.auth.is_anon() {
return Ok(false);
}
if let Ok(db_ctx) = self.database() {
let ns = db_ctx.ns_name();
let db = db_ctx.db_name();
match action {
Action::Edit => {
let allowed = root.auth.has_editor_role();
let db_in_actor_level = root.auth.is_root()
|| root.auth.is_ns_check(ns)
|| root.auth.is_db_check(ns, db);
Ok(!allowed || !db_in_actor_level)
}
Action::View => {
let allowed = root.auth.has_viewer_role();
let db_in_actor_level = root.auth.is_root()
|| root.auth.is_ns_check(ns)
|| root.auth.is_db_check(ns, db);
Ok(!allowed || !db_in_actor_level)
}
}
} else {
let has_role = match action {
Action::Edit => root.auth.has_editor_role(),
Action::View => root.auth.has_viewer_role(),
};
Ok(!has_role || !root.auth.is_root())
}
}
pub(crate) fn with_new_ctx(&self, ctx: FrozenContext) -> Self {
match self {
Self::Root(r) => Self::Root(RootContext {
ctx,
options: r.options.clone(),
datastore: r.datastore.clone(),
cancellation: r.cancellation.clone(),
auth: Arc::clone(&r.auth),
session: r.session.clone(),
current_value: r.current_value.clone(),
skip_fetch_perms: r.skip_fetch_perms,
version_stamp: r.version_stamp,
}),
Self::Namespace(n) => Self::Namespace(NamespaceContext {
root: RootContext {
ctx,
options: n.root.options.clone(),
datastore: n.root.datastore.clone(),
cancellation: n.root.cancellation.clone(),
auth: Arc::clone(&n.root.auth),
session: n.root.session.clone(),
current_value: n.root.current_value.clone(),
skip_fetch_perms: n.root.skip_fetch_perms,
version_stamp: n.root.version_stamp,
},
ns: Arc::clone(&n.ns),
}),
Self::Database(d) => Self::Database(DatabaseContext {
ns_ctx: NamespaceContext {
root: RootContext {
ctx,
options: d.ns_ctx.root.options.clone(),
datastore: d.ns_ctx.root.datastore.clone(),
cancellation: d.ns_ctx.root.cancellation.clone(),
auth: Arc::clone(&d.ns_ctx.root.auth),
session: d.ns_ctx.root.session.clone(),
current_value: d.ns_ctx.root.current_value.clone(),
skip_fetch_perms: d.ns_ctx.root.skip_fetch_perms,
version_stamp: d.ns_ctx.root.version_stamp,
},
ns: Arc::clone(&d.ns_ctx.ns),
},
db: Arc::clone(&d.db),
field_state_cache: Arc::clone(&d.field_state_cache),
table_def_cache: Arc::clone(&d.table_def_cache),
index_def_cache: Arc::clone(&d.index_def_cache),
}),
}
}
pub fn with_current_value(&self, value: Value) -> Self {
let mut new = self.clone();
let root = match &mut new {
Self::Root(r) => r,
Self::Namespace(n) => &mut n.root,
Self::Database(d) => &mut d.ns_ctx.root,
};
root.current_value = Some(Arc::new(value));
new
}
pub fn current_value(&self) -> Option<&Value> {
self.root().current_value.as_deref()
}
pub fn with_skip_fetch_perms(self, skip: bool) -> Self {
if skip == self.root().skip_fetch_perms {
return self;
}
let mut new = self;
let root = match &mut new {
Self::Root(r) => r,
Self::Namespace(n) => &mut n.root,
Self::Database(d) => &mut d.ns_ctx.root,
};
root.skip_fetch_perms = skip;
new
}
pub fn with_version_stamp(self, version: Option<u64>) -> Self {
if version == self.root().version_stamp {
return self;
}
let mut new = self;
let root = match &mut new {
Self::Root(r) => r,
Self::Namespace(n) => &mut n.root,
Self::Database(d) => &mut d.ns_ctx.root,
};
root.version_stamp = version;
new
}
pub fn version_stamp(&self) -> Option<u64> {
self.root().version_stamp
}
pub fn with_param(&self, name: impl Into<Strand>, value: Value) -> Self {
let mut child = Context::new_child(self.ctx());
child.add_value(name, Arc::new(value));
self.with_new_ctx(child.freeze())
}
pub fn with_limited_auth(&self, limit: &crate::iam::AuthLimit) -> Self {
let mut new = self.clone();
let root = match &mut new {
Self::Root(r) => r,
Self::Namespace(n) => &mut n.root,
Self::Database(d) => &mut d.ns_ctx.root,
};
root.auth = Arc::new(root.auth.new_limited(limit));
if let Some(ref opts) = root.options {
root.options = Some(opts.clone().with_auth(Arc::clone(&root.auth)));
}
new
}
pub fn with_namespace(&self, ns: Arc<NamespaceDefinition>) -> Self {
Self::Namespace(NamespaceContext {
root: self.root().clone(),
ns,
})
}
pub fn with_database(&self, ns: Arc<NamespaceDefinition>, db: Arc<DatabaseDefinition>) -> Self {
Self::Database(DatabaseContext {
ns_ctx: NamespaceContext {
root: self.root().clone(),
ns,
},
db,
field_state_cache: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
table_def_cache: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
index_def_cache: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
})
}
pub fn with_transaction(&self, txn: Arc<Transaction>) -> Result<Self, Error> {
let mut child = Context::new_child(self.ctx());
child.set_transaction(txn);
Ok(self.with_new_ctx(child.freeze()))
}
pub fn function_registry(&self) -> &Arc<FunctionRegistry> {
self.root().ctx.function_registry()
}
pub fn session(&self) -> Option<&SessionInfo> {
self.root().session.as_deref()
}
pub fn capabilities(&self) -> Arc<Capabilities> {
self.root().ctx.get_capabilities()
}
pub fn cancellation(&self) -> &CancellationToken {
&self.root().cancellation
}
pub fn options(&self) -> Option<&Options> {
self.root().options.as_ref()
}
pub fn is_allowed(&self, action: Action, res: ResourceKind, base: Base) -> anyhow::Result<()> {
if let Some(options) = self.options() {
self.ctx().is_allowed(options, action, res, base)
} else {
Ok(())
}
}
}