use std::sync::Arc;
use futures::StreamExt;
use reblessive::tree::TreeStack;
use crate::ctx::FrozenContext;
use crate::err::Error;
use crate::exec::context::{ContextLevel, ExecutionContext};
use crate::exec::planner::try_plan_expr;
use crate::exec::{FlowResult, ValueBatchStream};
use crate::expr::part::{Part, RecurseInstruction};
use crate::expr::statements::InfoStatement;
use crate::expr::{Base, Block, ControlFlow, ControlFlowExt, Expr, Literal};
use crate::val::Value;
pub(crate) fn get_legacy_context(
exec_ctx: &ExecutionContext,
) -> Result<(crate::dbs::Options, FrozenContext), Error> {
let options = exec_ctx
.options()
.ok_or_else(|| Error::Thrown("Options not available for legacy compute fallback".into()))?;
let options = legacy_fallback_options(exec_ctx, options);
Ok((options, Arc::clone(exec_ctx.ctx())))
}
fn legacy_fallback_options(
exec_ctx: &ExecutionContext,
options: &crate::dbs::Options,
) -> crate::dbs::Options {
if exec_ctx.root().skip_fetch_perms {
options.new_for_permission_predicate()
} else {
options.clone()
}
}
pub(crate) fn get_legacy_context_with_param(
exec_ctx: &ExecutionContext,
param_name: &str,
param_value: &Value,
) -> Result<(crate::dbs::Options, FrozenContext), Error> {
let options = exec_ctx
.options()
.ok_or_else(|| Error::Thrown("Options not available for legacy compute fallback".into()))?;
let options = legacy_fallback_options(exec_ctx, options);
let mut ctx = crate::ctx::Context::new_child(exec_ctx.ctx());
ctx.add_value(param_name.to_string(), std::sync::Arc::new(param_value.clone()));
Ok((options, ctx.freeze()))
}
pub(crate) async fn legacy_compute(
expr: &Expr,
frozen: &FrozenContext,
opt: &crate::dbs::Options,
doc: Option<&crate::doc::CursorDoc>,
) -> crate::expr::FlowResult<Value> {
let mut stack = TreeStack::new();
stack.enter(|stk| expr.compute(stk, frozen, opt, doc)).finish().await
}
pub(crate) async fn evaluate_expr_at_depth(
expr: &Expr,
ctx: &ExecutionContext,
depth: u32,
) -> crate::expr::FlowResult<Value> {
let auth = ctx.options().map(|o| Arc::clone(&o.auth));
match try_plan_expr!(expr, ctx.ctx(), ctx.txn(), auth, depth) {
Ok(plan) => {
let stream = plan.execute(ctx)?;
collect_single_value(stream).await
}
Err(e @ (Error::PlannerUnsupported(_) | Error::PlannerUnimplemented(_))) => {
match &e {
Error::PlannerUnimplemented(msg) => {
tracing::warn!("PlannerUnimplemented fallback in evaluate_expr: {msg}");
}
Error::PlannerUnsupported(msg) => {
tracing::debug!("PlannerUnsupported fallback in evaluate_expr: {msg}",);
}
_ => {}
}
let (opt, frozen) =
get_legacy_context(ctx).context("Legacy compute fallback context unavailable")?;
let opt = opt.with_dive_consumed(depth);
legacy_compute(expr, &frozen, &opt, None).await
}
Err(e) => Err(ControlFlow::Err(e.into())),
}
}
pub(crate) async fn evaluate_body_expr(
expr: &Expr,
ctx: &mut ExecutionContext,
param_name: &str,
param_value: &Value,
depth: u32,
) -> crate::expr::FlowResult<Value> {
let frozen_ctx = Arc::clone(ctx.ctx());
let auth = ctx.options().map(|o| Arc::clone(&o.auth));
match try_plan_expr!(expr, &frozen_ctx, ctx.txn(), auth, depth) {
Ok(plan) => {
if plan.mutates_context() {
*ctx = plan.output_context(ctx).await.map_err(|e| ControlFlow::Err(e.into()))?;
Ok(Value::None)
} else {
let stream = plan.execute(ctx)?;
collect_single_value(stream).await
}
}
Err(e @ (Error::PlannerUnsupported(_) | Error::PlannerUnimplemented(_))) => {
match &e {
Error::PlannerUnimplemented(msg) => {
tracing::warn!("PlannerUnimplemented fallback in evaluate_body_expr: {msg}");
}
Error::PlannerUnsupported(msg) => {
tracing::debug!("PlannerUnsupported fallback in evaluate_body_expr: {msg}",);
}
_ => {}
}
let (opt, frozen) = get_legacy_context_with_param(ctx, param_name, param_value)
.context("Legacy compute fallback context unavailable")?;
let opt = opt.with_dive_consumed(depth);
if let Expr::Let(set_stmt) = expr {
if set_stmt.is_protected_set() {
return Err(Error::InvalidParam {
name: set_stmt.name.to_string(),
}
.into());
}
let value = legacy_compute(&set_stmt.what, &frozen, &opt, None).await?;
let value = if let Some(kind) = &set_stmt.kind {
value.coerce_to_kind(kind).map_err(|e| Error::SetCoerce {
name: set_stmt.name.to_string(),
error: Box::new(e),
})?
} else {
value
};
*ctx = ctx.with_param(set_stmt.name.clone(), value);
Ok(Value::None)
} else {
legacy_compute(expr, &frozen, &opt, None).await
}
}
Err(e) => Err(ControlFlow::Err(e.into())),
}
}
pub(crate) async fn collect_single_value(
stream: ValueBatchStream,
) -> crate::expr::FlowResult<Value> {
let mut values = Vec::new();
futures::pin_mut!(stream);
while let Some(batch_result) = stream.next().await {
match batch_result {
Ok(batch) => values.extend(batch.values),
Err(ctrl) => return Err(ctrl),
}
}
if values.is_empty() {
Ok(Value::None)
} else if values.len() == 1 {
Ok(values.into_iter().next().expect("values verified non-empty"))
} else {
Ok(Value::Array(crate::val::Array(values)))
}
}
pub(crate) async fn collect_stream(stream: ValueBatchStream) -> FlowResult<Vec<Value>> {
let mut results = Vec::new();
futures::pin_mut!(stream);
while let Some(batch_result) = stream.next().await {
match batch_result {
Ok(batch) => results.extend(batch.values),
Err(ctrl) => return Err(ctrl),
}
}
Ok(results)
}
pub(crate) fn expr_required_context(expr: &Expr) -> ContextLevel {
match expr {
Expr::Param(_) | Expr::Constant(_) | Expr::Mock(_) | Expr::Break | Expr::Continue => {
ContextLevel::Root
}
Expr::Literal(lit) => literal_required_context(lit),
Expr::Closure(closure) => expr_required_context(&closure.body),
Expr::Sleep(_) => ContextLevel::Root,
Expr::Idiom(idiom) => idiom_required_context(idiom),
Expr::Table(_) => ContextLevel::Database,
Expr::Block(block) => block_required_context(block),
Expr::Prefix {
expr,
..
}
| Expr::Postfix {
expr,
..
}
| Expr::Throw(expr) => expr_required_context(expr),
Expr::Binary {
left,
right,
..
} => expr_required_context(left).max(expr_required_context(right)),
Expr::FunctionCall(_) => ContextLevel::Database,
Expr::Return(stmt) => expr_required_context(&stmt.what),
Expr::IfElse(stmt) => {
let branches_ctx = stmt
.exprs
.iter()
.flat_map(|(cond, body)| [expr_required_context(cond), expr_required_context(body)])
.max()
.unwrap_or(ContextLevel::Root);
let else_ctx =
stmt.close.as_ref().map(expr_required_context).unwrap_or(ContextLevel::Root);
branches_ctx.max(else_ctx)
}
Expr::Select(_)
| Expr::Create(_)
| Expr::Update(_)
| Expr::Upsert(_)
| Expr::Delete(_)
| Expr::Relate(_)
| Expr::Insert(_) => ContextLevel::Database,
Expr::Define(_) | Expr::Remove(_) | Expr::Alter(_) | Expr::Rebuild(_) => {
ContextLevel::Database
}
Expr::Info(info) => info_stmt_required_context(info),
Expr::Foreach(stmt) => {
expr_required_context(&stmt.range).max(block_required_context(&stmt.block))
}
Expr::Let(stmt) => expr_required_context(&stmt.what),
Expr::Explain {
statement,
..
} => expr_required_context(statement),
#[cfg(feature = "gql")]
Expr::Match(_) => ContextLevel::Database,
}
}
pub(crate) fn block_required_context(block: &Block) -> ContextLevel {
block.0.iter().map(expr_required_context).max().unwrap_or(ContextLevel::Root)
}
fn info_stmt_required_context(info: &InfoStatement) -> ContextLevel {
match info {
InfoStatement::Root(_, _) => ContextLevel::Root,
InfoStatement::Ns(_, _) => ContextLevel::Namespace,
InfoStatement::Db(_, _) | InfoStatement::Tb(_, _, _) | InfoStatement::Index(_, _, _) => {
ContextLevel::Database
}
InfoStatement::User(user_expr, base, _) => {
let base_ctx = match base {
Some(Base::Root) | None => ContextLevel::Root,
Some(Base::Ns) => ContextLevel::Namespace,
Some(Base::Db) => ContextLevel::Database,
};
base_ctx.max(expr_required_context(user_expr))
}
}
}
fn literal_required_context(lit: &Literal) -> ContextLevel {
match lit {
Literal::Array(exprs) | Literal::Set(exprs) => {
exprs.iter().map(expr_required_context).max().unwrap_or(ContextLevel::Root)
}
Literal::Object(entries) => entries
.iter()
.map(|e| expr_required_context(&e.value))
.max()
.unwrap_or(ContextLevel::Root),
Literal::RecordId(rid) => record_id_key_required_context(&rid.key),
_ => ContextLevel::Root,
}
}
fn record_id_key_required_context(key: &crate::expr::RecordIdKeyLit) -> ContextLevel {
use crate::expr::RecordIdKeyLit;
match key {
RecordIdKeyLit::Array(exprs) => {
exprs.iter().map(expr_required_context).max().unwrap_or(ContextLevel::Root)
}
RecordIdKeyLit::Object(entries) => entries
.iter()
.map(|e| expr_required_context(&e.value))
.max()
.unwrap_or(ContextLevel::Root),
RecordIdKeyLit::Range(range) => {
let start = match &range.start {
std::ops::Bound::Included(k) | std::ops::Bound::Excluded(k) => {
record_id_key_required_context(k)
}
std::ops::Bound::Unbounded => ContextLevel::Root,
};
let end = match &range.end {
std::ops::Bound::Included(k) | std::ops::Bound::Excluded(k) => {
record_id_key_required_context(k)
}
std::ops::Bound::Unbounded => ContextLevel::Root,
};
start.max(end)
}
_ => ContextLevel::Root,
}
}
fn idiom_required_context(idiom: &crate::expr::Idiom) -> ContextLevel {
idiom.0.iter().map(part_required_context).max().unwrap_or(ContextLevel::Root)
}
fn part_required_context(part: &Part) -> ContextLevel {
match part {
Part::Where(expr) | Part::Value(expr) | Part::Start(expr) => expr_required_context(expr),
Part::Method(_, args) => {
args.iter().map(expr_required_context).max().unwrap_or(ContextLevel::Root)
}
Part::Lookup(_) => ContextLevel::Database,
Part::Recurse(_, _, instruction) => match instruction {
Some(RecurseInstruction::Shortest {
expects,
..
}) => expr_required_context(expects),
_ => ContextLevel::Root,
},
Part::All
| Part::Flatten
| Part::Last
| Part::First
| Part::Field(_)
| Part::Destructure(_)
| Part::Optional
| Part::Doc
| Part::RepeatRecurse => ContextLevel::Root,
}
}