use crate::error::{Error, Result};
use crate::Value;
use super::Operation;
pub struct AssignAddOperation {
pub target_ops: Vec<Box<dyn Operation + Send + Sync>>,
pub value_ops: Vec<Box<dyn Operation + Send + Sync>>,
}
impl AssignAddOperation {
#[must_use]
pub fn new(
target_ops: Vec<Box<dyn Operation + Send + Sync>>,
value_ops: Vec<Box<dyn Operation + Send + Sync>>,
) -> Self {
Self {
target_ops,
value_ops,
}
}
fn get_target_field(&self) -> Option<String> {
self.target_ops.last().and_then(|last_op| {
let desc = last_op.description();
desc.strip_prefix("field access: ")
.map(std::string::ToString::to_string)
})
}
#[allow(clippy::unused_self)]
fn apply_to_dataframe(
&self,
df: &polars::prelude::DataFrame,
field_name: &str,
add_val: &Value,
) -> Result<Value> {
let current_column = df
.column(field_name)
.map_err(|_| Error::operation(format!("Field '{field_name}' not found")))?;
let new_column = match add_val {
Value::Int(i) => current_column + *i,
Value::Float(f) => current_column + *f,
_ => return Err(Error::operation("Can only add numeric values to columns")),
};
let new_series = new_column.as_materialized_series().clone();
let mut new_df = df.clone();
new_df
.replace(field_name, new_series)
.map_err(|e| Error::operation(format!("Failed to replace column: {e}")))?;
Ok(Value::DataFrame(new_df))
}
}
impl Operation for AssignAddOperation {
fn apply(&self, value: &Value) -> Result<Value> {
if let Value::DataFrame(df) = value {
if let Some(field_name) = self.get_target_field() {
let mut add_val = value.clone();
for op in &self.value_ops {
add_val = op.apply(&add_val)?;
}
self.apply_to_dataframe(df, &field_name, &add_val)
} else {
Err(Error::operation("Assignment target must be a field access"))
}
} else {
let mut target_val = value.clone();
for op in &self.target_ops {
target_val = op.apply(&target_val)?;
}
let mut add_val = value.clone();
for op in &self.value_ops {
add_val = op.apply(&add_val)?;
}
Ok(dsq_shared::ops::add_values(&target_val, &add_val)?)
}
}
fn description(&self) -> String {
"assign add".to_string()
}
}
pub struct AssignUpdateOperation {
pub target_ops: Vec<Box<dyn Operation + Send + Sync>>,
pub value_ops: Vec<Box<dyn Operation + Send + Sync>>,
}
impl AssignUpdateOperation {
#[must_use]
pub fn new(
target_ops: Vec<Box<dyn Operation + Send + Sync>>,
value_ops: Vec<Box<dyn Operation + Send + Sync>>,
) -> Self {
Self {
target_ops,
value_ops,
}
}
fn update_nested_field(
obj: &mut std::collections::HashMap<String, Value>,
fields: &[&str],
value: Value,
) -> Result<()> {
if fields.is_empty() {
return Err(crate::error::Error::operation("Empty field path"));
}
if fields.len() == 1 {
obj.insert(fields[0].to_string(), value);
return Ok(());
}
let mut current = obj;
for &field in &fields[..fields.len() - 1] {
match current.get_mut(field) {
Some(Value::Object(ref mut nested_obj)) => {
current = nested_obj;
}
Some(_) => {
return Err(crate::error::Error::operation(format!(
"Field '{field}' is not an object"
)));
}
None => {
return Err(crate::error::Error::operation(format!(
"Field '{field}' not found"
)));
}
}
}
let last_field = fields[fields.len() - 1];
current.insert(last_field.to_string(), value);
Ok(())
}
}
impl Operation for AssignUpdateOperation {
fn apply(&self, value: &Value) -> Result<Value> {
if let Value::Object(ref obj) = value {
if self.target_ops.len() == 1 {
if let Some(field_op) = self.target_ops.first() {
let desc = field_op.description();
if let Some(field_path) = desc.strip_prefix("field access: ") {
let fields: Vec<&str> = field_path.split('.').collect();
let mut value_val = value.clone();
for op in &self.value_ops {
value_val = op.apply(&value_val)?;
}
let mut new_obj = obj.clone();
if fields.len() == 1 {
new_obj.insert(field_path.to_string(), value_val);
} else {
Self::update_nested_field(&mut new_obj, &fields, value_val)?;
}
return Ok(Value::Object(new_obj));
}
}
}
}
let mut value_val = value.clone();
for op in &self.value_ops {
value_val = op.apply(&value_val)?;
}
Ok(value_val)
}
fn description(&self) -> String {
"assign update".to_string()
}
}