1use super::utils::{ARG_ANY_ONE, coerce_num, criteria_match};
28use crate::args::{ArgSchema, CriteriaPredicate, parse_criteria};
29use crate::function::Function;
30use crate::traits::{ArgumentHandle, CalcValue, FunctionContext};
31use formualizer_common::{ExcelError, LiteralValue};
32use formualizer_macros::func_caps;
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36enum DAggregate {
37 Sum,
38 Average,
39 Count,
40 Max,
41 Min,
42 Product,
43}
44
45fn resolve_field_index(
50 field: &LiteralValue,
51 headers: &[LiteralValue],
52) -> Result<usize, ExcelError> {
53 match field {
54 LiteralValue::Text(name) => {
55 let name_lower = name.to_ascii_lowercase();
56 for (i, h) in headers.iter().enumerate() {
57 if let LiteralValue::Text(hdr) = h
58 && hdr.to_ascii_lowercase() == name_lower
59 {
60 return Ok(i);
61 }
62 }
63 Err(ExcelError::new_value()
64 .with_message(format!("Field '{}' not found in database headers", name)))
65 }
66 LiteralValue::Number(n) => {
67 let idx = *n as i64;
68 if idx < 1 || idx as usize > headers.len() {
69 return Err(ExcelError::new_value().with_message(format!(
70 "Field index {} out of range (1-{})",
71 idx,
72 headers.len()
73 )));
74 }
75 Ok((idx - 1) as usize)
76 }
77 LiteralValue::Int(i) => {
78 if *i < 1 || *i as usize > headers.len() {
79 return Err(ExcelError::new_value().with_message(format!(
80 "Field index {} out of range (1-{})",
81 i,
82 headers.len()
83 )));
84 }
85 Ok((*i - 1) as usize)
86 }
87 _ => Err(ExcelError::new_value().with_message("Field must be text or number")),
88 }
89}
90
91fn parse_criteria_range(
95 criteria_view: &crate::engine::range_view::RangeView<'_>,
96 db_headers: &[LiteralValue],
97) -> Result<Vec<Vec<(usize, CriteriaPredicate)>>, ExcelError> {
98 let (crit_rows, crit_cols) = criteria_view.dims();
99 if crit_rows < 1 || crit_cols < 1 {
100 return Ok(vec![]);
101 }
102
103 let mut crit_col_map: Vec<Option<usize>> = Vec::with_capacity(crit_cols);
105 for c in 0..crit_cols {
106 let crit_header = criteria_view.get_cell(0, c);
107 if let LiteralValue::Text(name) = &crit_header {
108 let name_lower = name.to_ascii_lowercase();
109 let mut found = None;
110 for (i, h) in db_headers.iter().enumerate() {
111 if let LiteralValue::Text(hdr) = h
112 && hdr.to_ascii_lowercase() == name_lower
113 {
114 found = Some(i);
115 break;
116 }
117 }
118 crit_col_map.push(found);
119 } else if matches!(crit_header, LiteralValue::Empty) {
120 crit_col_map.push(None);
121 } else {
122 crit_col_map.push(None);
124 }
125 }
126
127 let mut criteria_rows = Vec::new();
129 for r in 1..crit_rows {
130 let mut row_criteria = Vec::new();
131 let mut has_any_criteria = false;
132
133 for (c, db_col) in crit_col_map.iter().enumerate() {
134 let crit_val = criteria_view.get_cell(r, c);
135 if matches!(crit_val, LiteralValue::Empty) {
136 continue;
137 }
138
139 if let Some(db_col) = db_col {
140 let pred = parse_criteria(&crit_val)?;
141 row_criteria.push((*db_col, pred));
142 has_any_criteria = true;
143 }
144 }
145
146 if has_any_criteria {
147 criteria_rows.push(row_criteria);
148 }
149 }
150
151 Ok(criteria_rows)
152}
153
154fn row_matches_criteria(
157 db_view: &crate::engine::range_view::RangeView<'_>,
158 row: usize,
159 criteria_rows: &[Vec<(usize, CriteriaPredicate)>],
160) -> bool {
161 if criteria_rows.is_empty() {
163 return true;
164 }
165
166 for crit_row in criteria_rows {
168 let mut all_match = true;
169 for (col_idx, pred) in crit_row {
171 let cell_val = db_view.get_cell(row, *col_idx);
172 if !criteria_match(pred, &cell_val) {
173 all_match = false;
174 break;
175 }
176 }
177 if all_match {
178 return true;
179 }
180 }
181
182 false
183}
184
185fn eval_d_function<'a, 'b>(
187 args: &[ArgumentHandle<'a, 'b>],
188 _ctx: &dyn FunctionContext<'b>,
189 agg_type: DAggregate,
190) -> Result<CalcValue<'b>, ExcelError> {
191 if args.len() != 3 {
192 return Ok(CalcValue::Scalar(LiteralValue::Error(
193 ExcelError::new_value().with_message(format!(
194 "D-function expects 3 arguments, got {}",
195 args.len()
196 )),
197 )));
198 }
199
200 let db_view = match args[0].range_view() {
202 Ok(v) => v,
203 Err(_) => {
204 let val = args[0].value()?.into_literal();
206 if let LiteralValue::Array(arr) = val {
207 crate::engine::range_view::RangeView::from_owned_rows(
208 arr,
209 crate::engine::DateSystem::Excel1900,
210 )
211 } else {
212 return Ok(CalcValue::Scalar(LiteralValue::Error(
213 ExcelError::new_value().with_message("Database must be a range or array"),
214 )));
215 }
216 }
217 };
218
219 let (db_rows, db_cols) = db_view.dims();
220 if db_rows < 2 || db_cols < 1 {
221 return Ok(CalcValue::Scalar(LiteralValue::Error(
222 ExcelError::new_value()
223 .with_message("Database must have headers and at least one data row"),
224 )));
225 }
226
227 let headers: Vec<LiteralValue> = (0..db_cols).map(|c| db_view.get_cell(0, c)).collect();
229
230 let field_val = args[1].value()?.into_literal();
232 let field_idx = resolve_field_index(&field_val, &headers)?;
233
234 let crit_view = match args[2].range_view() {
236 Ok(v) => v,
237 Err(_) => {
238 let val = args[2].value()?.into_literal();
239 if let LiteralValue::Array(arr) = val {
240 crate::engine::range_view::RangeView::from_owned_rows(
241 arr,
242 crate::engine::DateSystem::Excel1900,
243 )
244 } else {
245 return Ok(CalcValue::Scalar(LiteralValue::Error(
246 ExcelError::new_value().with_message("Criteria must be a range or array"),
247 )));
248 }
249 }
250 };
251
252 let criteria_rows = parse_criteria_range(&crit_view, &headers)?;
254
255 let mut values: Vec<f64> = Vec::new();
257
258 for row in 1..db_rows {
260 if row_matches_criteria(&db_view, row, &criteria_rows) {
261 let cell_val = db_view.get_cell(row, field_idx);
262
263 match &cell_val {
266 LiteralValue::Number(n) => values.push(*n),
267 LiteralValue::Int(i) => values.push(*i as f64),
268 LiteralValue::Boolean(b) => {
269 if agg_type != DAggregate::Count {
271 values.push(if *b { 1.0 } else { 0.0 });
272 }
273 }
274 LiteralValue::Empty => {
275 }
277 LiteralValue::Text(s) => {
278 if let Ok(n) = coerce_num(&cell_val) {
280 values.push(n);
281 }
282 }
284 LiteralValue::Error(e) => {
285 return Ok(CalcValue::Scalar(LiteralValue::Error(e.clone())));
287 }
288 _ => {}
289 }
290 }
291 }
292
293 let result = match agg_type {
295 DAggregate::Sum => {
296 let sum: f64 = values.iter().sum();
297 LiteralValue::Number(sum)
298 }
299 DAggregate::Average => {
300 if values.is_empty() {
301 LiteralValue::Error(ExcelError::new_div())
302 } else {
303 let sum: f64 = values.iter().sum();
304 LiteralValue::Number(sum / values.len() as f64)
305 }
306 }
307 DAggregate::Count => {
308 LiteralValue::Number(values.len() as f64)
310 }
311 DAggregate::Max => {
312 if values.is_empty() {
313 LiteralValue::Number(0.0)
314 } else {
315 let max = values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
316 LiteralValue::Number(max)
317 }
318 }
319 DAggregate::Min => {
320 if values.is_empty() {
321 LiteralValue::Number(0.0)
322 } else {
323 let min = values.iter().cloned().fold(f64::INFINITY, f64::min);
324 LiteralValue::Number(min)
325 }
326 }
327 DAggregate::Product => {
328 if values.is_empty() {
329 LiteralValue::Number(0.0)
330 } else {
331 let product: f64 = values.iter().product();
332 LiteralValue::Number(product)
333 }
334 }
335 };
336
337 Ok(CalcValue::Scalar(result))
338}
339
340#[derive(Debug, Clone, Copy, PartialEq, Eq)]
342enum DStatOp {
343 VarSample, VarPop, StdevSample, StdevPop, }
348
349fn eval_d_stat_function<'a, 'b>(
351 args: &[ArgumentHandle<'a, 'b>],
352 _ctx: &dyn FunctionContext<'b>,
353 stat_op: DStatOp,
354) -> Result<CalcValue<'b>, ExcelError> {
355 if args.len() != 3 {
356 return Ok(CalcValue::Scalar(LiteralValue::Error(
357 ExcelError::new_value().with_message(format!(
358 "D-function expects 3 arguments, got {}",
359 args.len()
360 )),
361 )));
362 }
363
364 let db_view = match args[0].range_view() {
366 Ok(v) => v,
367 Err(_) => {
368 let val = args[0].value()?.into_literal();
369 if let LiteralValue::Array(arr) = val {
370 crate::engine::range_view::RangeView::from_owned_rows(
371 arr,
372 crate::engine::DateSystem::Excel1900,
373 )
374 } else {
375 return Ok(CalcValue::Scalar(LiteralValue::Error(
376 ExcelError::new_value().with_message("Database must be a range or array"),
377 )));
378 }
379 }
380 };
381
382 let (db_rows, db_cols) = db_view.dims();
383 if db_rows < 2 || db_cols < 1 {
384 return Ok(CalcValue::Scalar(LiteralValue::Error(
385 ExcelError::new_value()
386 .with_message("Database must have headers and at least one data row"),
387 )));
388 }
389
390 let headers: Vec<LiteralValue> = (0..db_cols).map(|c| db_view.get_cell(0, c)).collect();
392
393 let field_val = args[1].value()?.into_literal();
395 let field_idx = resolve_field_index(&field_val, &headers)?;
396
397 let crit_view = match args[2].range_view() {
399 Ok(v) => v,
400 Err(_) => {
401 let val = args[2].value()?.into_literal();
402 if let LiteralValue::Array(arr) = val {
403 crate::engine::range_view::RangeView::from_owned_rows(
404 arr,
405 crate::engine::DateSystem::Excel1900,
406 )
407 } else {
408 return Ok(CalcValue::Scalar(LiteralValue::Error(
409 ExcelError::new_value().with_message("Criteria must be a range or array"),
410 )));
411 }
412 }
413 };
414
415 let criteria_rows = parse_criteria_range(&crit_view, &headers)?;
417
418 let mut values: Vec<f64> = Vec::new();
420
421 for row in 1..db_rows {
422 if row_matches_criteria(&db_view, row, &criteria_rows) {
423 let cell_val = db_view.get_cell(row, field_idx);
424
425 match &cell_val {
426 LiteralValue::Number(n) => values.push(*n),
427 LiteralValue::Int(i) => values.push(*i as f64),
428 LiteralValue::Boolean(b) => {
429 values.push(if *b { 1.0 } else { 0.0 });
430 }
431 LiteralValue::Text(s) => {
432 if let Ok(n) = coerce_num(&cell_val) {
433 values.push(n);
434 }
435 }
436 LiteralValue::Error(e) => {
437 return Ok(CalcValue::Scalar(LiteralValue::Error(e.clone())));
438 }
439 _ => {}
440 }
441 }
442 }
443
444 let result = match stat_op {
446 DStatOp::VarSample | DStatOp::StdevSample => {
447 if values.len() < 2 {
449 LiteralValue::Error(ExcelError::new_div())
450 } else {
451 let n = values.len() as f64;
452 let mean = values.iter().sum::<f64>() / n;
453 let variance = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / (n - 1.0);
454 if matches!(stat_op, DStatOp::VarSample) {
455 LiteralValue::Number(variance)
456 } else {
457 LiteralValue::Number(variance.sqrt())
458 }
459 }
460 }
461 DStatOp::VarPop | DStatOp::StdevPop => {
462 if values.is_empty() {
464 LiteralValue::Error(ExcelError::new_div())
465 } else {
466 let n = values.len() as f64;
467 let mean = values.iter().sum::<f64>() / n;
468 let variance = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / n;
469 if matches!(stat_op, DStatOp::VarPop) {
470 LiteralValue::Number(variance)
471 } else {
472 LiteralValue::Number(variance.sqrt())
473 }
474 }
475 }
476 };
477
478 Ok(CalcValue::Scalar(result))
479}
480
481fn eval_dget<'a, 'b>(
483 args: &[ArgumentHandle<'a, 'b>],
484 _ctx: &dyn FunctionContext<'b>,
485) -> Result<CalcValue<'b>, ExcelError> {
486 if args.len() != 3 {
487 return Ok(CalcValue::Scalar(LiteralValue::Error(
488 ExcelError::new_value()
489 .with_message(format!("DGET expects 3 arguments, got {}", args.len())),
490 )));
491 }
492
493 let db_view = match args[0].range_view() {
495 Ok(v) => v,
496 Err(_) => {
497 let val = args[0].value()?.into_literal();
498 if let LiteralValue::Array(arr) = val {
499 crate::engine::range_view::RangeView::from_owned_rows(
500 arr,
501 crate::engine::DateSystem::Excel1900,
502 )
503 } else {
504 return Ok(CalcValue::Scalar(LiteralValue::Error(
505 ExcelError::new_value().with_message("Database must be a range or array"),
506 )));
507 }
508 }
509 };
510
511 let (db_rows, db_cols) = db_view.dims();
512 if db_rows < 2 || db_cols < 1 {
513 return Ok(CalcValue::Scalar(LiteralValue::Error(
514 ExcelError::new_value()
515 .with_message("Database must have headers and at least one data row"),
516 )));
517 }
518
519 let headers: Vec<LiteralValue> = (0..db_cols).map(|c| db_view.get_cell(0, c)).collect();
521
522 let field_val = args[1].value()?.into_literal();
524 let field_idx = resolve_field_index(&field_val, &headers)?;
525
526 let crit_view = match args[2].range_view() {
528 Ok(v) => v,
529 Err(_) => {
530 let val = args[2].value()?.into_literal();
531 if let LiteralValue::Array(arr) = val {
532 crate::engine::range_view::RangeView::from_owned_rows(
533 arr,
534 crate::engine::DateSystem::Excel1900,
535 )
536 } else {
537 return Ok(CalcValue::Scalar(LiteralValue::Error(
538 ExcelError::new_value().with_message("Criteria must be a range or array"),
539 )));
540 }
541 }
542 };
543
544 let criteria_rows = parse_criteria_range(&crit_view, &headers)?;
546
547 let mut matching_values: Vec<LiteralValue> = Vec::new();
549
550 for row in 1..db_rows {
551 if row_matches_criteria(&db_view, row, &criteria_rows) {
552 matching_values.push(db_view.get_cell(row, field_idx));
553 }
554 }
555
556 let result = if matching_values.is_empty() {
561 LiteralValue::Error(ExcelError::new_value().with_message("No record matches criteria"))
562 } else if matching_values.len() > 1 {
563 LiteralValue::Error(
564 ExcelError::new_num().with_message("More than one record matches criteria"),
565 )
566 } else {
567 matching_values.into_iter().next().unwrap()
568 };
569
570 Ok(CalcValue::Scalar(result))
571}
572
573fn eval_dcounta<'a, 'b>(
575 args: &[ArgumentHandle<'a, 'b>],
576 _ctx: &dyn FunctionContext<'b>,
577) -> Result<CalcValue<'b>, ExcelError> {
578 if args.len() != 3 {
579 return Ok(CalcValue::Scalar(LiteralValue::Error(
580 ExcelError::new_value()
581 .with_message(format!("DCOUNTA expects 3 arguments, got {}", args.len())),
582 )));
583 }
584
585 let db_view = match args[0].range_view() {
587 Ok(v) => v,
588 Err(_) => {
589 let val = args[0].value()?.into_literal();
590 if let LiteralValue::Array(arr) = val {
591 crate::engine::range_view::RangeView::from_owned_rows(
592 arr,
593 crate::engine::DateSystem::Excel1900,
594 )
595 } else {
596 return Ok(CalcValue::Scalar(LiteralValue::Error(
597 ExcelError::new_value().with_message("Database must be a range or array"),
598 )));
599 }
600 }
601 };
602
603 let (db_rows, db_cols) = db_view.dims();
604 if db_rows < 2 || db_cols < 1 {
605 return Ok(CalcValue::Scalar(LiteralValue::Error(
606 ExcelError::new_value()
607 .with_message("Database must have headers and at least one data row"),
608 )));
609 }
610
611 let headers: Vec<LiteralValue> = (0..db_cols).map(|c| db_view.get_cell(0, c)).collect();
613
614 let field_val = args[1].value()?.into_literal();
616 let field_idx = resolve_field_index(&field_val, &headers)?;
617
618 let crit_view = match args[2].range_view() {
620 Ok(v) => v,
621 Err(_) => {
622 let val = args[2].value()?.into_literal();
623 if let LiteralValue::Array(arr) = val {
624 crate::engine::range_view::RangeView::from_owned_rows(
625 arr,
626 crate::engine::DateSystem::Excel1900,
627 )
628 } else {
629 return Ok(CalcValue::Scalar(LiteralValue::Error(
630 ExcelError::new_value().with_message("Criteria must be a range or array"),
631 )));
632 }
633 }
634 };
635
636 let criteria_rows = parse_criteria_range(&crit_view, &headers)?;
638
639 let mut count = 0;
641
642 for row in 1..db_rows {
643 if row_matches_criteria(&db_view, row, &criteria_rows) {
644 let cell_val = db_view.get_cell(row, field_idx);
645
646 match &cell_val {
648 LiteralValue::Empty => {
649 }
651 LiteralValue::Text(s) if s.is_empty() => {
652 }
654 LiteralValue::Error(e) => {
655 return Ok(CalcValue::Scalar(LiteralValue::Error(e.clone())));
657 }
658 _ => {
659 count += 1;
661 }
662 }
663 }
664 }
665
666 Ok(CalcValue::Scalar(LiteralValue::Number(count as f64)))
667}
668
669#[derive(Debug)]
671pub struct DSumFn;
672
673impl Function for DSumFn {
674 func_caps!(PURE, REDUCTION);
675
676 fn name(&self) -> &'static str {
677 "DSUM"
678 }
679
680 fn min_args(&self) -> usize {
681 3
682 }
683
684 fn variadic(&self) -> bool {
685 false
686 }
687
688 fn arg_schema(&self) -> &'static [ArgSchema] {
689 &ARG_ANY_ONE[..]
690 }
691
692 fn eval<'a, 'b, 'c>(
693 &self,
694 args: &'c [ArgumentHandle<'a, 'b>],
695 ctx: &dyn FunctionContext<'b>,
696 ) -> Result<CalcValue<'b>, ExcelError> {
697 eval_d_function(args, ctx, DAggregate::Sum)
698 }
699}
700
701#[derive(Debug)]
703pub struct DAverageFn;
704
705impl Function for DAverageFn {
706 func_caps!(PURE, REDUCTION);
707
708 fn name(&self) -> &'static str {
709 "DAVERAGE"
710 }
711
712 fn min_args(&self) -> usize {
713 3
714 }
715
716 fn variadic(&self) -> bool {
717 false
718 }
719
720 fn arg_schema(&self) -> &'static [ArgSchema] {
721 &ARG_ANY_ONE[..]
722 }
723
724 fn eval<'a, 'b, 'c>(
725 &self,
726 args: &'c [ArgumentHandle<'a, 'b>],
727 ctx: &dyn FunctionContext<'b>,
728 ) -> Result<CalcValue<'b>, ExcelError> {
729 eval_d_function(args, ctx, DAggregate::Average)
730 }
731}
732
733#[derive(Debug)]
735pub struct DCountFn;
736
737impl Function for DCountFn {
738 func_caps!(PURE, REDUCTION);
739
740 fn name(&self) -> &'static str {
741 "DCOUNT"
742 }
743
744 fn min_args(&self) -> usize {
745 3
746 }
747
748 fn variadic(&self) -> bool {
749 false
750 }
751
752 fn arg_schema(&self) -> &'static [ArgSchema] {
753 &ARG_ANY_ONE[..]
754 }
755
756 fn eval<'a, 'b, 'c>(
757 &self,
758 args: &'c [ArgumentHandle<'a, 'b>],
759 ctx: &dyn FunctionContext<'b>,
760 ) -> Result<CalcValue<'b>, ExcelError> {
761 eval_d_function(args, ctx, DAggregate::Count)
762 }
763}
764
765#[derive(Debug)]
767pub struct DMaxFn;
768
769impl Function for DMaxFn {
770 func_caps!(PURE, REDUCTION);
771
772 fn name(&self) -> &'static str {
773 "DMAX"
774 }
775
776 fn min_args(&self) -> usize {
777 3
778 }
779
780 fn variadic(&self) -> bool {
781 false
782 }
783
784 fn arg_schema(&self) -> &'static [ArgSchema] {
785 &ARG_ANY_ONE[..]
786 }
787
788 fn eval<'a, 'b, 'c>(
789 &self,
790 args: &'c [ArgumentHandle<'a, 'b>],
791 ctx: &dyn FunctionContext<'b>,
792 ) -> Result<CalcValue<'b>, ExcelError> {
793 eval_d_function(args, ctx, DAggregate::Max)
794 }
795}
796
797#[derive(Debug)]
799pub struct DMinFn;
800
801impl Function for DMinFn {
802 func_caps!(PURE, REDUCTION);
803
804 fn name(&self) -> &'static str {
805 "DMIN"
806 }
807
808 fn min_args(&self) -> usize {
809 3
810 }
811
812 fn variadic(&self) -> bool {
813 false
814 }
815
816 fn arg_schema(&self) -> &'static [ArgSchema] {
817 &ARG_ANY_ONE[..]
818 }
819
820 fn eval<'a, 'b, 'c>(
821 &self,
822 args: &'c [ArgumentHandle<'a, 'b>],
823 ctx: &dyn FunctionContext<'b>,
824 ) -> Result<CalcValue<'b>, ExcelError> {
825 eval_d_function(args, ctx, DAggregate::Min)
826 }
827}
828
829#[derive(Debug)]
831pub struct DProductFn;
832
833impl Function for DProductFn {
834 func_caps!(PURE, REDUCTION);
835
836 fn name(&self) -> &'static str {
837 "DPRODUCT"
838 }
839
840 fn min_args(&self) -> usize {
841 3
842 }
843
844 fn variadic(&self) -> bool {
845 false
846 }
847
848 fn arg_schema(&self) -> &'static [ArgSchema] {
849 &ARG_ANY_ONE[..]
850 }
851
852 fn eval<'a, 'b, 'c>(
853 &self,
854 args: &'c [ArgumentHandle<'a, 'b>],
855 ctx: &dyn FunctionContext<'b>,
856 ) -> Result<CalcValue<'b>, ExcelError> {
857 eval_d_function(args, ctx, DAggregate::Product)
858 }
859}
860
861#[derive(Debug)]
863pub struct DStdevFn;
864
865impl Function for DStdevFn {
866 func_caps!(PURE, REDUCTION);
867
868 fn name(&self) -> &'static str {
869 "DSTDEV"
870 }
871
872 fn min_args(&self) -> usize {
873 3
874 }
875
876 fn variadic(&self) -> bool {
877 false
878 }
879
880 fn arg_schema(&self) -> &'static [ArgSchema] {
881 &ARG_ANY_ONE[..]
882 }
883
884 fn eval<'a, 'b, 'c>(
885 &self,
886 args: &'c [ArgumentHandle<'a, 'b>],
887 ctx: &dyn FunctionContext<'b>,
888 ) -> Result<CalcValue<'b>, ExcelError> {
889 eval_d_stat_function(args, ctx, DStatOp::StdevSample)
890 }
891}
892
893#[derive(Debug)]
895pub struct DStdevPFn;
896
897impl Function for DStdevPFn {
898 func_caps!(PURE, REDUCTION);
899
900 fn name(&self) -> &'static str {
901 "DSTDEVP"
902 }
903
904 fn min_args(&self) -> usize {
905 3
906 }
907
908 fn variadic(&self) -> bool {
909 false
910 }
911
912 fn arg_schema(&self) -> &'static [ArgSchema] {
913 &ARG_ANY_ONE[..]
914 }
915
916 fn eval<'a, 'b, 'c>(
917 &self,
918 args: &'c [ArgumentHandle<'a, 'b>],
919 ctx: &dyn FunctionContext<'b>,
920 ) -> Result<CalcValue<'b>, ExcelError> {
921 eval_d_stat_function(args, ctx, DStatOp::StdevPop)
922 }
923}
924
925#[derive(Debug)]
927pub struct DVarFn;
928
929impl Function for DVarFn {
930 func_caps!(PURE, REDUCTION);
931
932 fn name(&self) -> &'static str {
933 "DVAR"
934 }
935
936 fn min_args(&self) -> usize {
937 3
938 }
939
940 fn variadic(&self) -> bool {
941 false
942 }
943
944 fn arg_schema(&self) -> &'static [ArgSchema] {
945 &ARG_ANY_ONE[..]
946 }
947
948 fn eval<'a, 'b, 'c>(
949 &self,
950 args: &'c [ArgumentHandle<'a, 'b>],
951 ctx: &dyn FunctionContext<'b>,
952 ) -> Result<CalcValue<'b>, ExcelError> {
953 eval_d_stat_function(args, ctx, DStatOp::VarSample)
954 }
955}
956
957#[derive(Debug)]
959pub struct DVarPFn;
960
961impl Function for DVarPFn {
962 func_caps!(PURE, REDUCTION);
963
964 fn name(&self) -> &'static str {
965 "DVARP"
966 }
967
968 fn min_args(&self) -> usize {
969 3
970 }
971
972 fn variadic(&self) -> bool {
973 false
974 }
975
976 fn arg_schema(&self) -> &'static [ArgSchema] {
977 &ARG_ANY_ONE[..]
978 }
979
980 fn eval<'a, 'b, 'c>(
981 &self,
982 args: &'c [ArgumentHandle<'a, 'b>],
983 ctx: &dyn FunctionContext<'b>,
984 ) -> Result<CalcValue<'b>, ExcelError> {
985 eval_d_stat_function(args, ctx, DStatOp::VarPop)
986 }
987}
988
989#[derive(Debug)]
991pub struct DGetFn;
992
993impl Function for DGetFn {
994 func_caps!(PURE, REDUCTION);
995
996 fn name(&self) -> &'static str {
997 "DGET"
998 }
999
1000 fn min_args(&self) -> usize {
1001 3
1002 }
1003
1004 fn variadic(&self) -> bool {
1005 false
1006 }
1007
1008 fn arg_schema(&self) -> &'static [ArgSchema] {
1009 &ARG_ANY_ONE[..]
1010 }
1011
1012 fn eval<'a, 'b, 'c>(
1013 &self,
1014 args: &'c [ArgumentHandle<'a, 'b>],
1015 ctx: &dyn FunctionContext<'b>,
1016 ) -> Result<CalcValue<'b>, ExcelError> {
1017 eval_dget(args, ctx)
1018 }
1019}
1020
1021#[derive(Debug)]
1023pub struct DCountAFn;
1024
1025impl Function for DCountAFn {
1026 func_caps!(PURE, REDUCTION);
1027
1028 fn name(&self) -> &'static str {
1029 "DCOUNTA"
1030 }
1031
1032 fn min_args(&self) -> usize {
1033 3
1034 }
1035
1036 fn variadic(&self) -> bool {
1037 false
1038 }
1039
1040 fn arg_schema(&self) -> &'static [ArgSchema] {
1041 &ARG_ANY_ONE[..]
1042 }
1043
1044 fn eval<'a, 'b, 'c>(
1045 &self,
1046 args: &'c [ArgumentHandle<'a, 'b>],
1047 ctx: &dyn FunctionContext<'b>,
1048 ) -> Result<CalcValue<'b>, ExcelError> {
1049 eval_dcounta(args, ctx)
1050 }
1051}
1052
1053pub fn register_builtins() {
1055 use std::sync::Arc;
1056 crate::function_registry::register_function(Arc::new(DSumFn));
1057 crate::function_registry::register_function(Arc::new(DAverageFn));
1058 crate::function_registry::register_function(Arc::new(DCountFn));
1059 crate::function_registry::register_function(Arc::new(DMaxFn));
1060 crate::function_registry::register_function(Arc::new(DMinFn));
1061 crate::function_registry::register_function(Arc::new(DProductFn));
1062 crate::function_registry::register_function(Arc::new(DStdevFn));
1063 crate::function_registry::register_function(Arc::new(DStdevPFn));
1064 crate::function_registry::register_function(Arc::new(DVarFn));
1065 crate::function_registry::register_function(Arc::new(DVarPFn));
1066 crate::function_registry::register_function(Arc::new(DGetFn));
1067 crate::function_registry::register_function(Arc::new(DCountAFn));
1068}
1069
1070#[cfg(test)]
1071mod tests {
1072 use super::*;
1073 use crate::test_workbook::TestWorkbook;
1074 use formualizer_parse::parser::{ASTNode, ASTNodeType};
1075 use std::sync::Arc;
1076
1077 fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
1078 wb.interpreter()
1079 }
1080
1081 fn lit(v: LiteralValue) -> ASTNode {
1082 ASTNode::new(ASTNodeType::Literal(v), None)
1083 }
1084
1085 fn make_database() -> LiteralValue {
1086 LiteralValue::Array(vec![
1088 vec![
1089 LiteralValue::Text("Name".into()),
1090 LiteralValue::Text("Age".into()),
1091 LiteralValue::Text("Salary".into()),
1092 ],
1093 vec![
1094 LiteralValue::Text("Alice".into()),
1095 LiteralValue::Int(30),
1096 LiteralValue::Int(50000),
1097 ],
1098 vec![
1099 LiteralValue::Text("Bob".into()),
1100 LiteralValue::Int(25),
1101 LiteralValue::Int(45000),
1102 ],
1103 vec![
1104 LiteralValue::Text("Carol".into()),
1105 LiteralValue::Int(35),
1106 LiteralValue::Int(60000),
1107 ],
1108 vec![
1109 LiteralValue::Text("Dave".into()),
1110 LiteralValue::Int(30),
1111 LiteralValue::Int(55000),
1112 ],
1113 ])
1114 }
1115
1116 fn make_criteria_all() -> LiteralValue {
1117 LiteralValue::Array(vec![vec![LiteralValue::Text("Name".into())]])
1119 }
1120
1121 fn make_criteria_age_30() -> LiteralValue {
1122 LiteralValue::Array(vec![
1124 vec![LiteralValue::Text("Age".into())],
1125 vec![LiteralValue::Int(30)],
1126 ])
1127 }
1128
1129 fn make_criteria_age_gt_25() -> LiteralValue {
1130 LiteralValue::Array(vec![
1132 vec![LiteralValue::Text("Age".into())],
1133 vec![LiteralValue::Text(">25".into())],
1134 ])
1135 }
1136
1137 #[test]
1138 fn dsum_all_salaries() {
1139 let wb = TestWorkbook::new().with_function(Arc::new(DSumFn));
1140 let ctx = interp(&wb);
1141
1142 let db = lit(make_database());
1143 let field = lit(LiteralValue::Text("Salary".into()));
1144 let criteria = lit(make_criteria_all());
1145
1146 let args = vec![
1147 crate::traits::ArgumentHandle::new(&db, &ctx),
1148 crate::traits::ArgumentHandle::new(&field, &ctx),
1149 crate::traits::ArgumentHandle::new(&criteria, &ctx),
1150 ];
1151
1152 let f = ctx.context.get_function("", "DSUM").unwrap();
1153 let result = f.dispatch(&args, &ctx.function_context(None)).unwrap();
1154
1155 assert_eq!(result.into_literal(), LiteralValue::Number(210000.0));
1157 }
1158
1159 #[test]
1160 fn dsum_age_30() {
1161 let wb = TestWorkbook::new().with_function(Arc::new(DSumFn));
1162 let ctx = interp(&wb);
1163
1164 let db = lit(make_database());
1165 let field = lit(LiteralValue::Text("Salary".into()));
1166 let criteria = lit(make_criteria_age_30());
1167
1168 let args = vec![
1169 crate::traits::ArgumentHandle::new(&db, &ctx),
1170 crate::traits::ArgumentHandle::new(&field, &ctx),
1171 crate::traits::ArgumentHandle::new(&criteria, &ctx),
1172 ];
1173
1174 let f = ctx.context.get_function("", "DSUM").unwrap();
1175 let result = f.dispatch(&args, &ctx.function_context(None)).unwrap();
1176
1177 assert_eq!(result.into_literal(), LiteralValue::Number(105000.0));
1179 }
1180
1181 #[test]
1182 fn daverage_age_gt_25() {
1183 let wb = TestWorkbook::new().with_function(Arc::new(DAverageFn));
1184 let ctx = interp(&wb);
1185
1186 let db = lit(make_database());
1187 let field = lit(LiteralValue::Text("Salary".into()));
1188 let criteria = lit(make_criteria_age_gt_25());
1189
1190 let args = vec![
1191 crate::traits::ArgumentHandle::new(&db, &ctx),
1192 crate::traits::ArgumentHandle::new(&field, &ctx),
1193 crate::traits::ArgumentHandle::new(&criteria, &ctx),
1194 ];
1195
1196 let f = ctx.context.get_function("", "DAVERAGE").unwrap();
1197 let result = f.dispatch(&args, &ctx.function_context(None)).unwrap();
1198
1199 assert_eq!(result.into_literal(), LiteralValue::Number(55000.0));
1201 }
1202
1203 #[test]
1204 fn dcount_age_30() {
1205 let wb = TestWorkbook::new().with_function(Arc::new(DCountFn));
1206 let ctx = interp(&wb);
1207
1208 let db = lit(make_database());
1209 let field = lit(LiteralValue::Text("Salary".into()));
1210 let criteria = lit(make_criteria_age_30());
1211
1212 let args = vec![
1213 crate::traits::ArgumentHandle::new(&db, &ctx),
1214 crate::traits::ArgumentHandle::new(&field, &ctx),
1215 crate::traits::ArgumentHandle::new(&criteria, &ctx),
1216 ];
1217
1218 let f = ctx.context.get_function("", "DCOUNT").unwrap();
1219 let result = f.dispatch(&args, &ctx.function_context(None)).unwrap();
1220
1221 assert_eq!(result.into_literal(), LiteralValue::Number(2.0));
1223 }
1224
1225 #[test]
1226 fn dmax_all() {
1227 let wb = TestWorkbook::new().with_function(Arc::new(DMaxFn));
1228 let ctx = interp(&wb);
1229
1230 let db = lit(make_database());
1231 let field = lit(LiteralValue::Text("Salary".into()));
1232 let criteria = lit(make_criteria_all());
1233
1234 let args = vec![
1235 crate::traits::ArgumentHandle::new(&db, &ctx),
1236 crate::traits::ArgumentHandle::new(&field, &ctx),
1237 crate::traits::ArgumentHandle::new(&criteria, &ctx),
1238 ];
1239
1240 let f = ctx.context.get_function("", "DMAX").unwrap();
1241 let result = f.dispatch(&args, &ctx.function_context(None)).unwrap();
1242
1243 assert_eq!(result.into_literal(), LiteralValue::Number(60000.0));
1245 }
1246
1247 #[test]
1248 fn dmin_all() {
1249 let wb = TestWorkbook::new().with_function(Arc::new(DMinFn));
1250 let ctx = interp(&wb);
1251
1252 let db = lit(make_database());
1253 let field = lit(LiteralValue::Text("Salary".into()));
1254 let criteria = lit(make_criteria_all());
1255
1256 let args = vec![
1257 crate::traits::ArgumentHandle::new(&db, &ctx),
1258 crate::traits::ArgumentHandle::new(&field, &ctx),
1259 crate::traits::ArgumentHandle::new(&criteria, &ctx),
1260 ];
1261
1262 let f = ctx.context.get_function("", "DMIN").unwrap();
1263 let result = f.dispatch(&args, &ctx.function_context(None)).unwrap();
1264
1265 assert_eq!(result.into_literal(), LiteralValue::Number(45000.0));
1267 }
1268
1269 #[test]
1270 fn dsum_field_by_index() {
1271 let wb = TestWorkbook::new().with_function(Arc::new(DSumFn));
1272 let ctx = interp(&wb);
1273
1274 let db = lit(make_database());
1275 let field = lit(LiteralValue::Int(3)); let criteria = lit(make_criteria_all());
1277
1278 let args = vec![
1279 crate::traits::ArgumentHandle::new(&db, &ctx),
1280 crate::traits::ArgumentHandle::new(&field, &ctx),
1281 crate::traits::ArgumentHandle::new(&criteria, &ctx),
1282 ];
1283
1284 let f = ctx.context.get_function("", "DSUM").unwrap();
1285 let result = f.dispatch(&args, &ctx.function_context(None)).unwrap();
1286
1287 assert_eq!(result.into_literal(), LiteralValue::Number(210000.0));
1289 }
1290}