1use super::Evaluator;
5use super::super::compiled::CompiledLogic;
6use super::helpers::{self, is_truthy, loose_equal, to_f64 as to_number};
7use serde_json::Value;
8
9const OPTIMIZATION_MIN_SIZE: usize = 5;
12
13impl Evaluator {
14 pub(super) fn try_eval_valueat_combined(
17 &self,
18 table_expr: &CompiledLogic,
19 row_idx_expr: &CompiledLogic,
20 col_name_expr: &Option<Box<CompiledLogic>>,
21 user_data: &Value,
22 internal_context: &Value,
23 depth: usize
24 ) -> Result<Option<Value>, String> {
25 match row_idx_expr {
27 CompiledLogic::IndexAt(lookup_expr, idx_table_expr, field_expr, range_expr) => {
28 if self.tables_match_fast(table_expr, idx_table_expr) {
30 if self.should_use_combined_optimization(table_expr, user_data, internal_context) {
32 return Ok(Some(self.eval_valueat_indexat_combined(
33 table_expr, lookup_expr, field_expr, range_expr, col_name_expr,
34 user_data, internal_context, depth
35 )?));
36 }
37 }
38 }
39 CompiledLogic::FindIndex(fi_table_expr, conditions) => {
40 if !conditions.is_empty() && self.tables_match_fast(table_expr, fi_table_expr) {
42 if self.should_use_combined_optimization(table_expr, user_data, internal_context) {
43 return Ok(Some(self.eval_valueat_findindex_combined(
44 table_expr, conditions, col_name_expr, user_data, internal_context, depth
45 )?));
46 }
47 }
48 }
49 CompiledLogic::Match(m_table_expr, conditions) => {
50 if !conditions.is_empty() && self.tables_match_fast(table_expr, m_table_expr) {
52 if self.should_use_combined_optimization(table_expr, user_data, internal_context) {
53 return Ok(Some(self.eval_valueat_match_combined(
54 table_expr, conditions, col_name_expr, user_data, internal_context, depth
55 )?));
56 }
57 }
58 }
59 CompiledLogic::MatchRange(mr_table_expr, conditions) => {
60 if !conditions.is_empty() && self.tables_match_fast(table_expr, mr_table_expr) {
62 if self.should_use_combined_optimization(table_expr, user_data, internal_context) {
63 return Ok(Some(self.eval_valueat_matchrange_combined(
64 table_expr, conditions, col_name_expr, user_data, internal_context, depth
65 )?));
66 }
67 }
68 }
69 CompiledLogic::Choose(c_table_expr, conditions) => {
70 if !conditions.is_empty() && self.tables_match_fast(table_expr, c_table_expr) {
72 if self.should_use_combined_optimization(table_expr, user_data, internal_context) {
73 return Ok(Some(self.eval_valueat_choose_combined(
74 table_expr, conditions, col_name_expr, user_data, internal_context, depth
75 )?));
76 }
77 }
78 }
79 _ => {}
80 }
81 Ok(None)
82 }
83
84 #[inline]
86 pub(super) fn tables_match_fast(&self, table1: &CompiledLogic, table2: &CompiledLogic) -> bool {
87 if std::ptr::eq(table1, table2) {
89 return true;
90 }
91
92 match (table1, table2) {
94 (CompiledLogic::Var(name1, _), CompiledLogic::Var(name2, _)) => {
95 name1.len() == name2.len() && name1 == name2
97 }
98 (CompiledLogic::Ref(path1, _), CompiledLogic::Ref(path2, _)) => {
99 path1.len() == path2.len() && path1 == path2
101 }
102 _ => false,
103 }
104 }
105
106 #[inline]
109 pub(super) fn should_use_combined_optimization(
110 &self,
111 table_expr: &CompiledLogic,
112 user_data: &Value,
113 internal_context: &Value
114 ) -> bool {
115 match table_expr {
118 CompiledLogic::Var(name, _) => {
119 let value = if name.is_empty() {
121 helpers::get_var(user_data, name)
122 } else {
123 helpers::get_var(internal_context, name)
124 .or_else(|| helpers::get_var(user_data, name))
125 };
126
127 value
128 .and_then(|v| v.as_array())
129 .map(|arr| arr.len() >= OPTIMIZATION_MIN_SIZE)
130 .unwrap_or(false)
131 }
132 CompiledLogic::Ref(path, _) => {
133 let value = if path.is_empty() {
134 helpers::get_var(user_data, path)
135 } else {
136 helpers::get_var(internal_context, path)
137 .or_else(|| helpers::get_var(user_data, path))
138 };
139
140 value
141 .and_then(|v| v.as_array())
142 .map(|arr| arr.len() >= OPTIMIZATION_MIN_SIZE)
143 .unwrap_or(false)
144 }
145 _ => {
146 true }
150 }
151 }
152
153 pub(super) fn eval_valueat_indexat_combined(
155 &self,
156 table_expr: &CompiledLogic,
157 lookup_expr: &CompiledLogic,
158 field_expr: &CompiledLogic,
159 range_expr: &Option<Box<CompiledLogic>>,
160 col_name_expr: &Option<Box<CompiledLogic>>,
161 user_data: &Value,
162 internal_context: &Value,
163 depth: usize
164 ) -> Result<Value, String> {
165 let table_ref = self.resolve_table_ref(table_expr, user_data, internal_context, depth)?;
167 let lookup_val = self.evaluate_with_context(lookup_expr, user_data, internal_context, depth + 1)?;
168 let field_val = self.resolve_column_name(field_expr, user_data, internal_context, depth)?;
169 let col_val = if let Some(col_expr) = col_name_expr {
170 Some(self.resolve_column_name(col_expr, user_data, internal_context, depth)?)
171 } else {
172 None
173 };
174
175 let is_range = if let Some(r_expr) = range_expr {
176 let r_val = self.evaluate_with_context(r_expr, user_data, internal_context, depth + 1)?;
177 is_truthy(&r_val)
178 } else {
179 false
180 };
181
182 if let (Some(arr), Value::String(field)) = (table_ref.as_array(), &field_val) {
184 let lookup_num = to_number(&lookup_val);
185
186 if is_range {
187 for row in arr.iter() {
189 if let Value::Object(obj) = row {
190 if let Some(cell_val) = obj.get(field) {
191 let cell_num = to_number(cell_val);
192 if cell_num <= lookup_num {
193 if let Some(Value::String(col_name)) = &col_val {
195 return Ok(obj.get(col_name).cloned().unwrap_or(Value::Null));
196 } else {
197 return Ok(row.clone());
198 }
199 }
200 }
201 }
202 }
203 } else {
204 for row in arr.iter() {
206 if let Value::Object(obj) = row {
207 if let Some(cell_val) = obj.get(field) {
208 if loose_equal(&lookup_val, cell_val) {
209 if let Some(Value::String(col_name)) = &col_val {
210 return Ok(obj.get(col_name).cloned().unwrap_or(Value::Null));
211 } else {
212 return Ok(row.clone());
213 }
214 }
215 }
216 }
217 }
218 }
219 }
220 Ok(Value::Null)
221 }
222
223 pub(super) fn eval_valueat_findindex_combined(
225 &self,
226 table_expr: &CompiledLogic,
227 conditions: &[CompiledLogic],
228 col_name_expr: &Option<Box<CompiledLogic>>,
229 user_data: &Value,
230 internal_context: &Value,
231 depth: usize
232 ) -> Result<Value, String> {
233 let table_ref = self.resolve_table_ref(table_expr, user_data, internal_context, depth)?;
234 let col_val = if let Some(col_expr) = col_name_expr {
235 Some(self.resolve_column_name(col_expr, user_data, internal_context, depth)?)
236 } else {
237 None
238 };
239
240 if let Some(arr) = table_ref.as_array() {
242 for row in arr.iter() {
243 let mut all_match = true;
244
245 for condition in conditions {
246 let result = self.evaluate_with_context(condition, user_data, row, depth + 1)?;
248 if !is_truthy(&result) {
249 all_match = false;
250 break;
251 }
252 }
253
254 if all_match {
255 if let Some(Value::String(col_name)) = &col_val {
257 if let Value::Object(obj) = row {
258 return Ok(obj.get(col_name).cloned().unwrap_or(Value::Null));
259 }
260 } else {
261 return Ok(row.clone());
262 }
263 }
264 }
265 }
266 Ok(Value::Null)
267 }
268
269 pub(super) fn eval_valueat_match_combined(
271 &self,
272 table_expr: &CompiledLogic,
273 conditions: &[CompiledLogic],
274 col_name_expr: &Option<Box<CompiledLogic>>,
275 user_data: &Value,
276 internal_context: &Value,
277 depth: usize
278 ) -> Result<Value, String> {
279 let table_ref = self.resolve_table_ref(table_expr, user_data, internal_context, depth)?;
280 let col_val = if let Some(col_expr) = col_name_expr {
281 Some(self.resolve_column_name(col_expr, user_data, internal_context, depth)?)
282 } else {
283 None
284 };
285
286 let mut evaluated_conditions = Vec::with_capacity(conditions.len() / 2);
288 for chunk in conditions.chunks(2) {
289 if chunk.len() == 2 {
290 let value_val = self.evaluate_with_context(&chunk[0], user_data, internal_context, depth + 1)?;
291 let field_val = self.evaluate_with_context(&chunk[1], user_data, internal_context, depth + 1)?;
292 if let Value::String(field) = field_val {
293 evaluated_conditions.push((value_val, field));
294 }
295 }
296 }
297
298 if let Some(arr) = table_ref.as_array() {
300 for row in arr.iter() {
301 if let Value::Object(obj) = row {
302 let all_match = evaluated_conditions.iter().all(|(value_val, field)| {
303 obj.get(field)
304 .map(|cell_val| loose_equal(value_val, cell_val))
305 .unwrap_or(false)
306 });
307
308 if all_match {
309 if let Some(Value::String(col_name)) = &col_val {
311 return Ok(obj.get(col_name).cloned().unwrap_or(Value::Null));
312 } else {
313 return Ok(row.clone());
314 }
315 }
316 }
317 }
318 }
319 Ok(Value::Null)
320 }
321
322 pub(super) fn eval_valueat_matchrange_combined(
324 &self,
325 table_expr: &CompiledLogic,
326 conditions: &[CompiledLogic],
327 col_name_expr: &Option<Box<CompiledLogic>>,
328 user_data: &Value,
329 internal_context: &Value,
330 depth: usize
331 ) -> Result<Value, String> {
332 let table_ref = self.resolve_table_ref(table_expr, user_data, internal_context, depth)?;
333 let col_val = if let Some(col_expr) = col_name_expr {
334 Some(self.resolve_column_name(col_expr, user_data, internal_context, depth)?)
335 } else {
336 None
337 };
338
339 let mut evaluated_conditions = Vec::with_capacity(conditions.len() / 3);
341 for chunk in conditions.chunks(3) {
342 if chunk.len() == 3 {
343 let min_col_val = self.evaluate_with_context(&chunk[0], user_data, internal_context, depth + 1)?;
344 let max_col_val = self.evaluate_with_context(&chunk[1], user_data, internal_context, depth + 1)?;
345 let check_val = self.evaluate_with_context(&chunk[2], user_data, internal_context, depth + 1)?;
346
347 if let (Value::String(min_col), Value::String(max_col)) = (&min_col_val, &max_col_val) {
348 let check_num = to_number(&check_val);
349 evaluated_conditions.push((min_col.clone(), max_col.clone(), check_num));
350 }
351 }
352 }
353
354 if let Some(arr) = table_ref.as_array() {
356 for row in arr.iter() {
357 if let Value::Object(obj) = row {
358 let all_match = evaluated_conditions.iter().all(|(min_col, max_col, check_num)| {
359 let min_num = obj.get(min_col).map(|v| to_number(v)).unwrap_or(0.0);
360 let max_num = obj.get(max_col).map(|v| to_number(v)).unwrap_or(0.0);
361 *check_num >= min_num && *check_num <= max_num
362 });
363
364 if all_match {
365 if let Some(Value::String(col_name)) = &col_val {
367 return Ok(obj.get(col_name).cloned().unwrap_or(Value::Null));
368 } else {
369 return Ok(row.clone());
370 }
371 }
372 }
373 }
374 }
375 Ok(Value::Null)
376 }
377
378 pub(super) fn eval_valueat_choose_combined(
380 &self,
381 table_expr: &CompiledLogic,
382 conditions: &[CompiledLogic],
383 col_name_expr: &Option<Box<CompiledLogic>>,
384 user_data: &Value,
385 internal_context: &Value,
386 depth: usize
387 ) -> Result<Value, String> {
388 let table_ref = self.resolve_table_ref(table_expr, user_data, internal_context, depth)?;
389 let col_val = if let Some(col_expr) = col_name_expr {
390 Some(self.resolve_column_name(col_expr, user_data, internal_context, depth)?)
391 } else {
392 None
393 };
394
395 let mut evaluated_conditions = Vec::with_capacity(conditions.len() / 2);
397 for chunk in conditions.chunks(2) {
398 if chunk.len() == 2 {
399 let value_val = self.evaluate_with_context(&chunk[0], user_data, internal_context, depth + 1)?;
400 let field_val = self.evaluate_with_context(&chunk[1], user_data, internal_context, depth + 1)?;
401 if let Value::String(field) = field_val {
402 evaluated_conditions.push((value_val, field));
403 }
404 }
405 }
406
407 if let Some(arr) = table_ref.as_array() {
409 for row in arr.iter() {
410 if let Value::Object(obj) = row {
411 let any_match = evaluated_conditions.iter().any(|(value_val, field)| {
412 obj.get(field)
413 .map(|cell_val| loose_equal(value_val, cell_val))
414 .unwrap_or(false)
415 });
416
417 if any_match {
418 if let Some(Value::String(col_name)) = &col_val {
420 return Ok(obj.get(col_name).cloned().unwrap_or(Value::Null));
421 } else {
422 return Ok(row.clone());
423 }
424 }
425 }
426 }
427 }
428 Ok(Value::Null)
429 }
430}