use anyhow::{Result, bail};
use reblessive::tree::Stk;
use crate::ctx::{Context, FrozenContext};
use crate::dbs::capabilities::{ArbitraryQueryTarget, EvalQueryTarget};
use crate::dbs::{Capabilities, Force, Options, Variables};
use crate::doc::CursorDoc;
use crate::err::Error;
use crate::expr::{Block, FlowResultExt as _, LogicalPlan, TopLevelExpr};
use crate::fnc::args::Optional;
use crate::iam::Auth;
use crate::val::{Object, Value};
#[derive(Clone, Copy)]
pub(crate) enum Dialect {
Surql,
Gql,
}
impl Dialect {
pub(crate) fn func_name(self) -> &'static str {
match self {
Dialect::Surql => "eval::surql",
Dialect::Gql => "eval::gql",
}
}
}
pub(crate) fn prepare(
caps: &Capabilities,
auth: &Auth,
dialect: Dialect,
query: &str,
) -> Result<Block> {
let name = dialect.func_name();
if !caps.allows_query(&ArbitraryQueryTarget::from(auth))
|| !caps.allows_eval_query(&EvalQueryTarget::from(auth))
{
bail!(Error::FunctionNotAllowed(name.to_string()));
}
let config = crate::cnf::CommonConfig::default();
let plan: LogicalPlan = match dialect {
Dialect::Surql => crate::syn::parse_with_capabilities(query, caps, &config)
.map_err(|e| Error::InvalidFunction {
name: name.to_string(),
message: e.to_string(),
})?
.into(),
#[cfg(feature = "gql")]
Dialect::Gql => {
crate::gql::parse_with_capabilities(query, caps, &config)
.map_err(|e| Error::InvalidFunction {
name: name.to_string(),
message: e.to_string(),
})?
.0
}
#[cfg(not(feature = "gql"))]
Dialect::Gql => bail!(Error::InvalidFunction {
name: name.to_string(),
message: "GQL support was not enabled at compile time".to_string(),
}),
};
let mut statements = Vec::with_capacity(plan.expressions.len());
for expr in plan.expressions {
match expr {
TopLevelExpr::Expr(expr) => statements.push(expr),
_ => bail!(Error::InvalidFunction {
name: name.to_string(),
message: "only query statements may be evaluated; transaction-control and \
session statements (BEGIN, CANCEL, COMMIT, USE, LIVE, KILL, OPTION, \
SHOW, and access statements) are not allowed inside eval"
.to_string(),
}),
}
}
if matches!(dialect, Dialect::Surql) && statements.len() > 1 {
bail!(Error::InvalidFunction {
name: name.to_string(),
message: "eval::surql evaluates a single statement; wrap multiple statements in a \
block, e.g. eval::surql(\"{ ... }\")"
.to_string(),
});
}
Ok(Block(statements))
}
pub async fn surql(
(stk, ctx, opt, doc): (&mut Stk, &FrozenContext, &Options, Option<&CursorDoc>),
(query, Optional(bindings)): (String, Optional<Object>),
) -> Result<Value> {
run_eval(stk, ctx, opt, doc, Dialect::Surql, query, bindings).await
}
pub async fn gql(
(stk, ctx, opt, doc): (&mut Stk, &FrozenContext, &Options, Option<&CursorDoc>),
(query, Optional(bindings)): (String, Optional<Object>),
) -> Result<Value> {
run_eval(stk, ctx, opt, doc, Dialect::Gql, query, bindings).await
}
pub(crate) async fn run_eval(
stk: &mut Stk,
ctx: &FrozenContext,
opt: &Options,
doc: Option<&CursorDoc>,
dialect: Dialect,
query: String,
bindings: Option<Object>,
) -> Result<Value> {
let block = prepare(&ctx.get_capabilities(), opt.auth.as_ref(), dialect, &query)?;
let mut child = Context::new_isolated(ctx);
if let Some(bindings) = bindings {
child.attach_variables(Variables::from(bindings))?;
}
let child = child.freeze();
let opt = opt.new_with_force(Force::None);
block.compute(stk, &child, &opt, doc).await.catch_return()
}