use std::sync::Arc;
use futures::StreamExt;
use crate::exec::{
AccessMode, ContextLevel, ExecOperator, ExecutionContext, FlowResult, OperatorMetrics,
ValueBatch, ValueBatchStream, buffer_stream, monitor_stream,
};
use crate::expr::idiom::Idiom;
use crate::val::Value;
#[derive(Debug, Clone)]
pub struct Split {
pub(crate) input: Arc<dyn ExecOperator>,
pub(crate) idioms: Vec<Idiom>,
pub(crate) metrics: Arc<OperatorMetrics>,
}
impl Split {
pub(crate) fn new(input: Arc<dyn ExecOperator>, idioms: Vec<Idiom>) -> Self {
Self {
input,
idioms,
metrics: Arc::new(OperatorMetrics::new()),
}
}
}
impl ExecOperator for Split {
fn name(&self) -> &'static str {
"Split"
}
fn attrs(&self) -> Vec<(String, String)> {
use surrealdb_types::ToSql;
vec![(
"on".to_string(),
self.idioms.iter().map(|i| i.to_sql()).collect::<Vec<_>>().join(", "),
)]
}
fn required_context(&self) -> ContextLevel {
self.input.required_context()
}
fn access_mode(&self) -> AccessMode {
self.input.access_mode()
}
fn children(&self) -> Vec<&Arc<dyn ExecOperator>> {
vec![&self.input]
}
fn metrics(&self) -> Option<&OperatorMetrics> {
Some(&self.metrics)
}
fn execute(&self, ctx: &ExecutionContext) -> FlowResult<ValueBatchStream> {
let input_stream = buffer_stream(
self.input.execute(ctx)?,
self.input.access_mode(),
self.input.cardinality_hint(),
ctx.root().ctx.config.operator_buffer_size,
);
let idioms = self.idioms.clone();
let split_stream = input_stream.map(move |batch_result| {
let idioms = idioms.clone();
match batch_result {
Ok(batch) => {
let mut expanded_values = Vec::with_capacity(batch.values.len() * idioms.len());
for value in batch.values {
let mut current_values = vec![value];
for idiom in &idioms {
let mut next_values = Vec::new();
for val in current_values {
split_value_on_idiom(val, idiom, &mut next_values);
}
current_values = next_values;
}
expanded_values.extend(current_values);
}
Ok(ValueBatch {
values: expanded_values,
})
}
Err(e) => Err(e),
}
});
Ok(monitor_stream(Box::pin(split_stream), "Split", &self.metrics))
}
}
fn split_value_on_idiom(value: Value, idiom: &Idiom, output: &mut Vec<Value>) {
let field_value = value.pick(idiom);
match field_value {
Value::Array(arr) => {
if arr.is_empty() {
output.push(value);
} else {
let len = arr.len();
for (i, element) in arr.into_iter().enumerate() {
if i == len - 1 {
let mut val = value;
val.put(idiom, element);
output.push(val);
return;
}
let mut cloned = value.clone();
cloned.put(idiom, element);
output.push(cloned);
}
}
}
Value::Set(set) => {
if set.is_empty() {
output.push(value);
} else {
let len = set.len();
for (i, element) in set.into_iter().enumerate() {
if i == len - 1 {
let mut val = value;
val.put(idiom, element);
output.push(val);
return;
}
let mut cloned = value.clone();
cloned.put(idiom, element);
output.push(cloned);
}
}
}
_ => {
output.push(value);
}
}
}
#[cfg(test)]
mod tests {
use surrealdb_strand::Strand;
use super::*;
use crate::expr::part::Part;
use crate::val::{Array, Object, Set};
#[test]
fn test_split_on_array() {
let idiom = Idiom(vec![Part::Field(Strand::new_static("tags"))]);
let value = Value::Object(Object::from_iter([
("id".to_string(), Value::from("t:1")),
("tags".to_string(), Value::Array(Array::from(vec![1, 2, 3]))),
]));
let mut output = Vec::new();
split_value_on_idiom(value, &idiom, &mut output);
assert_eq!(output.len(), 3);
}
#[test]
fn test_split_on_set() {
let idiom = Idiom(vec![Part::Field(Strand::new_static("tags"))]);
let value = Value::Object(Object::from_iter([
("id".to_string(), Value::from("t:1")),
("tags".to_string(), Value::Set(Set::from(vec![1, 2, 3]))),
]));
let mut output = Vec::new();
split_value_on_idiom(value, &idiom, &mut output);
assert_eq!(output.len(), 3);
}
#[test]
fn test_split_on_non_array() {
let idiom = Idiom(vec![Part::Field(Strand::new_static("name"))]);
let value = Value::Object(Object::from_iter([
("id".to_string(), Value::from("t:1")),
("name".to_string(), Value::from("test")),
]));
let mut output = Vec::new();
split_value_on_idiom(value.clone(), &idiom, &mut output);
assert_eq!(output.len(), 1);
assert_eq!(output[0], value);
}
#[test]
fn test_split_empty_array() {
let idiom = Idiom(vec![Part::Field(Strand::new_static("tags"))]);
let value = Value::Object(Object::from_iter([
("id".to_string(), Value::from("t:1")),
("tags".to_string(), Value::Array(Array::from(Vec::<Value>::new()))),
]));
let mut output = Vec::new();
split_value_on_idiom(value, &idiom, &mut output);
assert_eq!(output.len(), 1);
}
#[test]
fn test_split_empty_set() {
let idiom = Idiom(vec![Part::Field(Strand::new_static("tags"))]);
let value = Value::Object(Object::from_iter([
("id".to_string(), Value::from("t:1")),
("tags".to_string(), Value::Set(Set::new())),
]));
let mut output = Vec::new();
split_value_on_idiom(value, &idiom, &mut output);
assert_eq!(output.len(), 1);
}
}