use anyhow::{Result, bail};
use reblessive::tree::Stk;
use crate::ctx::{Context, FrozenContext};
use crate::dbs::capabilities::{ArbitraryQueryTarget, Error as CapabilitiesError, EvalQueryTarget};
use crate::dbs::{Capabilities, Force, Options, Variables};
use crate::doc::CursorDoc;
use crate::exe::FlowResultExt as _;
use crate::exec::Error as ExecError;
use crate::expr::{Block, LogicalPlan, TopLevelExpr};
use crate::fnc::args::Optional;
use crate::iam::Auth;
use crate::kvs::Dialect;
use crate::val::{Object, Value};
fn func_name(dialect: Dialect) -> &'static str {
match dialect {
Dialect::SurrealQl => "eval::surql",
Dialect::Gql => "eval::gql",
}
}
pub(crate) fn prepare(
caps: &Capabilities,
auth: &Auth,
dialect: Dialect,
query: &str,
) -> Result<Block> {
let name = func_name(dialect);
if !caps.allows_query(&ArbitraryQueryTarget::from(auth))
|| !caps.allows_eval_query(&EvalQueryTarget::from(auth))
{
bail!(CapabilitiesError::FunctionNotAllowed(name.to_string()));
}
let config = crate::syn::ParserConfig::default();
let plan: LogicalPlan = match dialect {
Dialect::SurrealQl => crate::syn::parse_with_capabilities(query, caps, &config)
.map_err(|e| ExecError::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| ExecError::InvalidFunction {
name: name.to_string(),
message: e.to_string(),
})?
.0
}
#[cfg(not(feature = "gql"))]
Dialect::Gql => bail!(surrealdb_types::Error::configuration(
"This build does not support GQL queries".to_string(),
surrealdb_types::ConfigurationError::DialectNotSupported,
)),
};
let mut statements = Vec::with_capacity(plan.expressions.len());
for expr in plan.expressions {
match expr {
TopLevelExpr::Expr(expr) => statements.push(expr),
_ => bail!(ExecError::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::SurrealQl) && statements.len() > 1 {
bail!(ExecError::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::SurrealQl, 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);
crate::legacy::block_compute(&block, stk, &child, &opt, doc).await.catch_return()
}