use std::sync::Arc;
use surrealdb_strand::Strand;
use surrealdb_types::{SqlFormat, ToSql};
use super::evaluate_physical_path;
use crate::exec::physical_expr::{EvalContext, PhysicalExpr};
use crate::exec::{AccessMode, BoxFut, CombineAccessModes, ContextLevel};
use crate::expr::FlowResult;
use crate::val::Value;
#[derive(Debug, Clone)]
pub struct DestructurePart {
pub fields: Vec<DestructureField>,
}
#[derive(Debug, Clone)]
pub enum DestructureField {
All(Strand),
Field(Strand),
Aliased {
field: Strand,
path: Vec<Arc<dyn PhysicalExpr>>,
},
Nested {
field: Strand,
parts: Vec<DestructureField>,
},
}
impl PhysicalExpr for DestructurePart {
fn name(&self) -> &'static str {
"Destructure"
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn required_context(&self) -> ContextLevel {
fn field_context(fields: &[DestructureField]) -> ContextLevel {
fields
.iter()
.map(|f| match f {
DestructureField::All(_) | DestructureField::Field(_) => ContextLevel::Root,
DestructureField::Aliased {
path,
..
} => path
.iter()
.map(|p| p.required_context())
.max()
.unwrap_or(ContextLevel::Root),
DestructureField::Nested {
parts,
..
} => field_context(parts),
})
.max()
.unwrap_or(ContextLevel::Root)
}
ContextLevel::Database.max(field_context(&self.fields))
}
fn evaluate<'a>(&'a self, ctx: EvalContext<'a>) -> BoxFut<'a, FlowResult<Value>> {
Box::pin(async move {
let value = ctx.current_value.unwrap_or(&Value::NONE);
evaluate_destructure(value, &self.fields, ctx).await
})
}
fn access_mode(&self) -> AccessMode {
fn field_access(fields: &[DestructureField]) -> AccessMode {
fields
.iter()
.map(|f| match f {
DestructureField::All(_) | DestructureField::Field(_) => AccessMode::ReadOnly,
DestructureField::Aliased {
path,
..
} => path.iter().map(|p| p.access_mode()).combine_all(),
DestructureField::Nested {
parts,
..
} => field_access(parts),
})
.combine_all()
}
field_access(&self.fields)
}
}
impl ToSql for DestructurePart {
fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
f.push('{');
for (i, field) in self.fields.iter().enumerate() {
if i > 0 {
f.push_str(", ");
}
field.fmt_sql(f, fmt);
}
f.push('}');
}
}
impl ToSql for DestructureField {
fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
match self {
DestructureField::All(name) => {
f.push_str(name);
f.push_str(".*");
}
DestructureField::Field(name) => {
f.push_str(name);
}
DestructureField::Aliased {
field,
path,
} => {
f.push_str(field);
f.push_str(": ");
for part in path {
part.fmt_sql(f, fmt);
}
}
DestructureField::Nested {
field,
parts,
} => {
f.push_str(field);
f.push_str(": {");
for (i, part) in parts.iter().enumerate() {
if i > 0 {
f.push_str(", ");
}
part.fmt_sql(f, fmt);
}
f.push('}');
}
}
}
}
async fn evaluate_destructure(
value: &Value,
fields: &[DestructureField],
ctx: EvalContext<'_>,
) -> FlowResult<Value> {
match value {
Value::Object(obj) => {
let mut result = std::collections::BTreeMap::new();
for field in fields {
match field {
DestructureField::All(name) => {
let field_val = obj.get(name.as_str()).cloned().unwrap_or(Value::None);
let resolved = match field_val {
Value::RecordId(rid) => {
if ctx.skip_fetch_perms {
crate::exec::operators::fetch::fetch_record_no_perms(
ctx.exec_ctx,
&rid,
)
.await?
} else {
crate::exec::operators::fetch::fetch_record(ctx.exec_ctx, &rid)
.await?
}
}
other => other,
};
if let Value::Object(nested) = &resolved {
result.insert(name.clone(), Value::Object(nested.clone()));
}
}
DestructureField::Field(name) => {
let v = obj.get(name.as_str()).cloned().unwrap_or(Value::None);
result.insert(name.clone(), v);
}
DestructureField::Aliased {
field: name,
path,
} => {
let v = evaluate_physical_path(value, path, ctx.clone()).await?;
result.insert(name.clone(), v);
}
DestructureField::Nested {
field: name,
parts,
} => {
let nested_value = obj.get(name.as_str()).cloned().unwrap_or(Value::None);
let v = Box::pin(evaluate_destructure(&nested_value, parts, ctx.clone()))
.await?;
result.insert(name.clone(), v);
}
}
}
Ok(Value::Object(crate::val::Object::from(result)))
}
Value::RecordId(rid) => {
let fetched = if ctx.skip_fetch_perms {
crate::exec::operators::fetch::fetch_record_no_perms(ctx.exec_ctx, rid).await?
} else {
crate::exec::operators::fetch::fetch_record(ctx.exec_ctx, rid).await?
};
if fetched.is_none() {
return Ok(Value::None);
}
Box::pin(evaluate_destructure(&fetched, fields, ctx)).await
}
Value::Array(arr) => {
let mut results = Vec::with_capacity(arr.len());
for item in arr.iter() {
let v = Box::pin(evaluate_destructure(item, fields, ctx.clone())).await?;
results.push(v);
}
Ok(Value::Array(results.into()))
}
_ => Ok(Value::None),
}
}