dsq_core/ops/
assignment_ops.rs1use crate::error::{Error, Result};
2use crate::Value;
3
4use super::Operation;
5
6pub struct AssignAddOperation {
7 pub target_ops: Vec<Box<dyn Operation + Send + Sync>>,
8 pub value_ops: Vec<Box<dyn Operation + Send + Sync>>,
9}
10
11impl AssignAddOperation {
12 #[must_use]
13 pub fn new(
14 target_ops: Vec<Box<dyn Operation + Send + Sync>>,
15 value_ops: Vec<Box<dyn Operation + Send + Sync>>,
16 ) -> Self {
17 Self {
18 target_ops,
19 value_ops,
20 }
21 }
22
23 fn get_target_field(&self) -> Option<String> {
24 self.target_ops.last().and_then(|last_op| {
26 let desc = last_op.description();
30 desc.strip_prefix("field access: ")
31 .map(std::string::ToString::to_string)
32 })
33 }
34
35 #[allow(clippy::unused_self)]
36 fn apply_to_dataframe(
37 &self,
38 df: &polars::prelude::DataFrame,
39 field_name: &str,
40 add_val: &Value,
41 ) -> Result<Value> {
42 let current_column = df
44 .column(field_name)
45 .map_err(|_| Error::operation(format!("Field '{field_name}' not found")))?;
46
47 let new_column = match add_val {
49 Value::Int(i) => current_column + *i,
50 Value::Float(f) => current_column + *f,
51 _ => return Err(Error::operation("Can only add numeric values to columns")),
52 };
53
54 let new_series = new_column.as_materialized_series().clone();
56
57 let mut new_df = df.clone();
59 new_df
60 .replace(field_name, new_series)
61 .map_err(|e| Error::operation(format!("Failed to replace column: {e}")))?;
62
63 Ok(Value::DataFrame(new_df))
64 }
65}
66
67impl Operation for AssignAddOperation {
68 fn apply(&self, value: &Value) -> Result<Value> {
69 if let Value::DataFrame(df) = value {
70 if let Some(field_name) = self.get_target_field() {
72 let mut add_val = value.clone();
74 for op in &self.value_ops {
75 add_val = op.apply(&add_val)?;
76 }
77
78 self.apply_to_dataframe(df, &field_name, &add_val)
80 } else {
81 Err(Error::operation("Assignment target must be a field access"))
82 }
83 } else {
84 let mut target_val = value.clone();
86 for op in &self.target_ops {
87 target_val = op.apply(&target_val)?;
88 }
89
90 let mut add_val = value.clone();
91 for op in &self.value_ops {
92 add_val = op.apply(&add_val)?;
93 }
94
95 Ok(dsq_shared::ops::add_values(&target_val, &add_val)?)
96 }
97 }
98
99 fn description(&self) -> String {
100 "assign add".to_string()
101 }
102}
103
104pub struct AssignUpdateOperation {
105 pub target_ops: Vec<Box<dyn Operation + Send + Sync>>,
106 pub value_ops: Vec<Box<dyn Operation + Send + Sync>>,
107}
108
109impl AssignUpdateOperation {
110 #[must_use]
111 pub fn new(
112 target_ops: Vec<Box<dyn Operation + Send + Sync>>,
113 value_ops: Vec<Box<dyn Operation + Send + Sync>>,
114 ) -> Self {
115 Self {
116 target_ops,
117 value_ops,
118 }
119 }
120
121 fn update_nested_field(
123 obj: &mut std::collections::HashMap<String, Value>,
124 fields: &[&str],
125 value: Value,
126 ) -> Result<()> {
127 if fields.is_empty() {
128 return Err(crate::error::Error::operation("Empty field path"));
129 }
130
131 if fields.len() == 1 {
132 obj.insert(fields[0].to_string(), value);
133 return Ok(());
134 }
135
136 let mut current = obj;
138 for &field in &fields[..fields.len() - 1] {
139 match current.get_mut(field) {
140 Some(Value::Object(ref mut nested_obj)) => {
141 current = nested_obj;
142 }
143 Some(_) => {
144 return Err(crate::error::Error::operation(format!(
145 "Field '{field}' is not an object"
146 )));
147 }
148 None => {
149 return Err(crate::error::Error::operation(format!(
150 "Field '{field}' not found"
151 )));
152 }
153 }
154 }
155
156 let last_field = fields[fields.len() - 1];
158 current.insert(last_field.to_string(), value);
159 Ok(())
160 }
161}
162
163impl Operation for AssignUpdateOperation {
164 fn apply(&self, value: &Value) -> Result<Value> {
165 if let Value::Object(ref obj) = value {
167 if self.target_ops.len() == 1 {
169 if let Some(field_op) = self.target_ops.first() {
170 let desc = field_op.description();
173 if let Some(field_path) = desc.strip_prefix("field access: ") {
174 let fields: Vec<&str> = field_path.split('.').collect();
176
177 let mut value_val = value.clone();
179 for op in &self.value_ops {
180 value_val = op.apply(&value_val)?;
181 }
182
183 let mut new_obj = obj.clone();
185 if fields.len() == 1 {
186 new_obj.insert(field_path.to_string(), value_val);
188 } else {
189 Self::update_nested_field(&mut new_obj, &fields, value_val)?;
191 }
192 return Ok(Value::Object(new_obj));
193 }
194 }
195 }
196 }
197
198 let mut value_val = value.clone();
200 for op in &self.value_ops {
201 value_val = op.apply(&value_val)?;
202 }
203
204 Ok(value_val)
205 }
206
207 fn description(&self) -> String {
208 "assign update".to_string()
209 }
210}