use std::any::Any;
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::Arc;
use surrealdb_strand::Strand;
use surrealdb_types::ToSql;
use crate::dbs::Capabilities;
use crate::exec::context::SessionInfo;
use crate::exec::{
AccessMode, BoxFut, ContextLevel, ExecOperator, ExecutionContext, SendSyncRequirement,
};
use crate::expr::FlowResult;
use crate::expr::idiom::Idiom;
use crate::kvs::Transaction;
use crate::val::{RecordId, Value};
mod block;
mod collections;
mod conditional;
mod control_flow;
pub(crate) mod function;
mod idiom;
mod literal;
mod matches;
mod ops;
pub(crate) mod record_id;
mod subquery;
pub(crate) use block::BlockPhysicalExpr;
pub(crate) use collections::{ArrayLiteral, ObjectLiteral, SetLiteral};
pub(crate) use conditional::IfElseExpr;
pub(crate) use control_flow::{ControlFlowExpr, ControlFlowKind};
pub(crate) use function::{
BuiltinFunctionExec, ClosureCallExec, ClosureExec, JsFunctionExec, ModelFunctionExec,
ProjectionFunctionExec, SiloModuleExec, SurrealismModuleExec, UserDefinedFunctionExec,
};
pub(crate) use idiom::IdiomExpr;
pub(crate) use literal::{Literal, MockExpr, Param};
pub(crate) use matches::MatchesOp;
pub(crate) use ops::{BinaryOp, PostfixOp, SimpleBinaryOp, UnaryOp};
pub(crate) use record_id::RecordIdExpr;
pub(crate) use subquery::ScalarSubquery;
#[derive(Clone)]
pub struct RecursionCtx {
pub min_depth: u32,
pub depth: u32,
pub discovery_sink: Option<Arc<parking_lot::Mutex<Vec<Value>>>>,
pub assembly_cache: Option<Arc<HashMap<u64, Value>>>,
}
#[derive(Clone)]
pub struct EvalContext<'a> {
pub exec_ctx: &'a ExecutionContext,
pub current_value: Option<&'a Value>,
pub local_params: Option<&'a HashMap<Strand, Value>>,
pub recursion_ctx: Option<RecursionCtx>,
pub document_root: Option<&'a Value>,
pub skip_fetch_perms: bool,
pub computing_record: Option<RecordId>,
pub plan_depth: u32,
}
impl<'a> EvalContext<'a> {
pub(crate) fn from_exec_ctx(exec_ctx: &'a ExecutionContext) -> Self {
Self {
exec_ctx,
current_value: None,
local_params: None,
recursion_ctx: None,
document_root: None,
skip_fetch_perms: exec_ctx.root().skip_fetch_perms,
computing_record: None,
plan_depth: 0,
}
}
pub fn with_value(&self, value: &'a Value) -> Self {
Self {
exec_ctx: self.exec_ctx,
current_value: Some(value),
local_params: self.local_params,
recursion_ctx: self.recursion_ctx.clone(),
document_root: self.document_root,
skip_fetch_perms: self.skip_fetch_perms,
computing_record: self.computing_record.clone(),
plan_depth: self.plan_depth,
}
}
pub fn with_value_and_doc(&self, value: &'a Value) -> Self {
Self {
exec_ctx: self.exec_ctx,
current_value: Some(value),
local_params: self.local_params,
recursion_ctx: self.recursion_ctx.clone(),
document_root: Some(value),
skip_fetch_perms: self.skip_fetch_perms,
computing_record: self.computing_record.clone(),
plan_depth: self.plan_depth,
}
}
pub fn with_recursion_ctx(&self, ctx: RecursionCtx) -> Self {
Self {
exec_ctx: self.exec_ctx,
current_value: self.current_value,
local_params: self.local_params,
recursion_ctx: Some(ctx),
document_root: self.document_root,
skip_fetch_perms: self.skip_fetch_perms,
computing_record: self.computing_record.clone(),
plan_depth: self.plan_depth,
}
}
pub fn session(&self) -> Option<&SessionInfo> {
self.exec_ctx.session()
}
pub fn txn(&self) -> Arc<Transaction> {
self.exec_ctx.txn()
}
pub fn capabilities(&self) -> Arc<Capabilities> {
self.exec_ctx.capabilities()
}
#[cfg(feature = "http")]
pub async fn check_allowed_net(&self, url: &url::Url) -> anyhow::Result<()> {
use crate::dbs::capabilities::NetTarget;
use crate::err::Error;
let capabilities = self.capabilities();
let host = url.host().ok_or_else(|| Error::InvalidUrl(url.to_string()))?;
let target = NetTarget::Host(host.to_owned(), url.port_or_known_default());
if !capabilities.matches_any_allow_net(&target) {
return Err(Error::NetTargetNotAllowed(target.to_string()).into());
}
if capabilities.matches_any_deny_net(&target) {
return Err(Error::NetTargetNotAllowed(target.to_string()).into());
}
#[cfg(not(target_family = "wasm"))]
let resolved = target.resolve().await?;
#[cfg(target_family = "wasm")]
let resolved = target.resolve()?;
for t in &resolved {
if capabilities.matches_any_deny_net(t) {
return Err(Error::NetTargetNotAllowed(t.to_string()).into());
}
}
Ok(())
}
pub fn check_allowed_function(&self, name: &str) -> anyhow::Result<()> {
use crate::err::Error;
if !self.capabilities().allows_function_name(name) {
return Err(Error::FunctionNotAllowed(name.to_string()).into());
}
Ok(())
}
#[allow(dead_code)]
pub fn get_capabilities(&self) -> Arc<Capabilities> {
self.exec_ctx.ctx().get_capabilities()
}
}
type ProjectionEvalFut<'a> = BoxFut<'a, FlowResult<Option<Vec<(Idiom, Value)>>>>;
pub trait PhysicalExpr: ToSql + SendSyncRequirement + Debug + 'static {
fn name(&self) -> &'static str;
fn required_context(&self) -> ContextLevel;
fn evaluate<'a>(&'a self, ctx: EvalContext<'a>) -> BoxFut<'a, FlowResult<Value>>;
fn access_mode(&self) -> AccessMode;
fn is_projection_function(&self) -> bool {
false
}
fn evaluate_batch<'a>(
&'a self,
ctx: EvalContext<'a>,
values: &'a [Value],
) -> BoxFut<'a, FlowResult<Vec<Value>>> {
Box::pin(async move {
let mut results = Vec::with_capacity(values.len());
for value in values {
results.push(self.evaluate(ctx.with_value_and_doc(value)).await?);
}
Ok(results)
})
}
fn evaluate_projection<'a>(&'a self, _ctx: EvalContext<'a>) -> ProjectionEvalFut<'a> {
Box::pin(async move { Ok(None) })
}
#[allow(dead_code)]
fn expr_children(&self) -> Vec<(&str, &Arc<dyn PhysicalExpr>)> {
vec![]
}
fn embedded_operators(&self) -> Vec<(&str, &Arc<dyn ExecOperator>)> {
vec![]
}
fn is_fused_lookup(&self) -> bool {
false
}
fn try_literal(&self) -> Option<&Value> {
None
}
fn try_simple_field(&self) -> Option<&str> {
None
}
fn as_any(&self) -> &dyn Any;
}
impl dyn PhysicalExpr {
pub(crate) fn downcast_ref<T: PhysicalExpr>(&self) -> Option<&T> {
self.as_any().downcast_ref::<T>()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::val::{Array, Number, Object};
#[test]
fn test_evaluate_index_on_array() {
use crate::exec::parts::index::evaluate_index;
let arr = Value::Array(Array::from(vec![
Value::Number(Number::Int(1)),
Value::Number(Number::Int(2)),
Value::Number(Number::Int(3)),
]));
let result = evaluate_index(&arr, &Value::Number(Number::Int(0))).unwrap();
assert_eq!(result, Value::Number(Number::Int(1)));
let result = evaluate_index(&arr, &Value::Number(Number::Int(2))).unwrap();
assert_eq!(result, Value::Number(Number::Int(3)));
let result = evaluate_index(&arr, &Value::Number(Number::Int(5))).unwrap();
assert_eq!(result, Value::None);
}
#[test]
fn test_evaluate_index_on_object() {
use crate::exec::parts::index::evaluate_index;
let obj = Value::Object(Object::from_iter([(
"key1".to_string(),
Value::String(Strand::new_static("value1")),
)]));
let result = evaluate_index(&obj, &Value::String(Strand::new_static("key1"))).unwrap();
assert_eq!(result, Value::String(Strand::new_static("value1")));
}
#[test]
fn test_evaluate_flatten() {
use crate::exec::parts::array_ops::evaluate_flatten;
let nested = Value::Array(Array::from(vec![
Value::Array(Array::from(vec![
Value::Number(Number::Int(1)),
Value::Number(Number::Int(2)),
])),
Value::Array(Array::from(vec![Value::Number(Number::Int(3))])),
]));
let result = evaluate_flatten(&nested).unwrap();
assert_eq!(
result,
Value::Array(Array::from(vec![
Value::Number(Number::Int(1)),
Value::Number(Number::Int(2)),
Value::Number(Number::Int(3)),
]))
);
}
#[test]
fn test_evaluate_first_and_last() {
let arr = Value::Array(Array::from(vec![
Value::Number(Number::Int(1)),
Value::Number(Number::Int(2)),
Value::Number(Number::Int(3)),
]));
let first = match &arr {
Value::Array(a) => a.first().cloned().unwrap_or(Value::None),
_ => arr.clone(),
};
assert_eq!(first, Value::Number(Number::Int(1)));
let last = match &arr {
Value::Array(a) => a.last().cloned().unwrap_or(Value::None),
_ => arr.clone(),
};
assert_eq!(last, Value::Number(Number::Int(3)));
let empty = Value::Array(Array::from(Vec::<Value>::new()));
let first_empty = match &empty {
Value::Array(a) => a.first().cloned().unwrap_or(Value::None),
_ => empty.clone(),
};
let last_empty = match &empty {
Value::Array(a) => a.last().cloned().unwrap_or(Value::None),
_ => empty.clone(),
};
assert_eq!(first_empty, Value::None);
assert_eq!(last_empty, Value::None);
}
#[test]
fn test_value_hash_consistency() {
use crate::exec::parts::recurse::value_hash;
let v1 = Value::Number(Number::Int(42));
let v2 = Value::Number(Number::Int(42));
let v3 = Value::Number(Number::Int(43));
assert_eq!(value_hash(&v1), value_hash(&v2));
assert_ne!(value_hash(&v1), value_hash(&v3));
}
}