use std::sync::Arc;
use surrealdb_types::{SqlFormat, ToSql};
use crate::exec::physical_expr::{EvalContext, PhysicalExpr};
use crate::exec::{AccessMode, BoxFut, CombineAccessModes, ContextLevel, ExecOperator};
use crate::expr::FlowResult;
use crate::val::Value;
#[derive(Debug, Clone)]
pub struct IdiomExpr {
pub(crate) display: String,
pub(crate) start_expr: Option<Arc<dyn PhysicalExpr>>,
pub(crate) parts: Vec<Arc<dyn PhysicalExpr>>,
}
impl IdiomExpr {
pub fn new(
display: String,
start_expr: Option<Arc<dyn PhysicalExpr>>,
parts: Vec<Arc<dyn PhysicalExpr>>,
) -> Self {
Self {
display,
start_expr,
parts,
}
}
pub(crate) fn try_static_object_field_path(&self) -> Option<Vec<String>> {
if self.start_expr.is_some() || self.parts.is_empty() {
return None;
}
let mut path = Vec::with_capacity(self.parts.len());
for p in &self.parts {
path.push(p.try_simple_field()?.to_owned());
}
Some(path)
}
}
impl PhysicalExpr for IdiomExpr {
fn name(&self) -> &'static str {
"IdiomExpr"
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn required_context(&self) -> ContextLevel {
let start_ctx =
self.start_expr.as_ref().map_or(ContextLevel::Root, |e| e.required_context());
let parts_ctx =
self.parts.iter().map(|p| p.required_context()).max().unwrap_or(ContextLevel::Root);
start_ctx.max(parts_ctx)
}
fn evaluate<'a>(&'a self, ctx: EvalContext<'a>) -> BoxFut<'a, FlowResult<Value>> {
Box::pin(async move {
if self.start_expr.is_none() && self.parts.len() == 1 {
return self.parts[0].evaluate(ctx).await;
}
let value = if let Some(ref start) = self.start_expr {
start.evaluate(ctx.clone()).await?
} else {
ctx.current_value.cloned().unwrap_or(Value::None)
};
evaluate_parts_with_continuation(&self.parts, value, ctx).await
})
}
fn access_mode(&self) -> AccessMode {
let parts_mode = self.parts.iter().map(|p| p.access_mode()).combine_all();
if let Some(ref start) = self.start_expr {
parts_mode.combine(start.access_mode())
} else {
parts_mode
}
}
fn try_simple_field(&self) -> Option<&str> {
if self.start_expr.is_none() && self.parts.len() == 1 {
self.parts[0].try_simple_field()
} else {
None
}
}
fn embedded_operators(&self) -> Vec<(&str, &Arc<dyn ExecOperator>)> {
let mut ops = Vec::new();
if let Some(ref start) = self.start_expr {
ops.extend(start.embedded_operators());
}
for part in &self.parts {
ops.extend(part.embedded_operators());
}
ops
}
}
impl ToSql for IdiomExpr {
fn fmt_sql(&self, f: &mut String, _fmt: SqlFormat) {
f.push_str(&self.display);
}
}
fn is_mapping_part(part: &dyn PhysicalExpr) -> bool {
matches!(part.name(), "Field" | "All" | "Destructure")
}
pub(crate) async fn evaluate_parts_with_continuation(
parts: &[Arc<dyn PhysicalExpr>],
mut value: Value,
ctx: EvalContext<'_>,
) -> crate::expr::FlowResult<Value> {
let mut i = 0;
let mut prev_part_name: &str = "";
let mut array_from_lookup = false;
while i < parts.len() {
let part = &parts[i];
if matches!(&value, Value::Array(_))
&& is_mapping_part(part.as_ref())
&& i + 1 < parts.len()
{
let arr = match value {
Value::Array(a) => a,
_ => unreachable!(),
};
let is_all = part.name() == "All";
let remaining_with_current = &parts[i..];
let remaining_after_current = &parts[i + 1..];
let mut results = Vec::with_capacity(arr.len());
for elem in arr.iter() {
let remaining = if is_all && !matches!(elem, Value::RecordId(_)) {
remaining_after_current
} else {
remaining_with_current
};
let mut v = elem.clone();
for rp in remaining {
v = rp.evaluate(ctx.with_value(&v)).await?;
}
results.push(v);
}
return Ok(Value::Array(results.into()));
}
if matches!(&value, Value::Array(_))
&& part.name() == "Lookup"
&& (i + 1 < parts.len() || part.is_fused_lookup())
&& !matches!(prev_part_name, "Lookup" | "Flatten")
{
let arr = match value {
Value::Array(a) => a,
_ => unreachable!(),
};
let remaining = &parts[i..];
let mut results = Vec::with_capacity(arr.len());
for elem in arr.iter() {
let mut v = elem.clone();
for rp in remaining {
v = rp.evaluate(ctx.with_value(&v)).await?;
}
results.push(v);
}
let result = Value::Array(results.into());
return if array_from_lookup {
Ok(result.flatten())
} else {
Ok(result)
};
}
prev_part_name = part.name();
value = part.evaluate(ctx.with_value(&value)).await?;
match prev_part_name {
"Lookup" | "Flatten" => {
if matches!(&value, Value::Array(_)) {
array_from_lookup = true;
}
}
"Where" => {
}
_ => {
array_from_lookup = false;
}
}
i += 1;
}
Ok(value)
}