use super::utils::{ARG_ANY_ONE, coerce_num, criteria_match};
use crate::args::{ArgSchema, CriteriaPredicate, parse_criteria};
use crate::function::Function;
use crate::traits::{ArgumentHandle, CalcValue, FunctionContext};
use formualizer_common::{ExcelError, LiteralValue};
use formualizer_macros::func_caps;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DAggregate {
Sum,
Average,
Count,
Max,
Min,
Product,
}
fn resolve_field_index(
field: &LiteralValue,
headers: &[LiteralValue],
) -> Result<usize, ExcelError> {
match field {
LiteralValue::Text(name) => {
let name_lower = name.to_lowercase();
for (i, h) in headers.iter().enumerate() {
if let LiteralValue::Text(hdr) = h
&& hdr.to_lowercase() == name_lower
{
return Ok(i);
}
}
Err(ExcelError::new_value()
.with_message(format!("Field '{}' not found in database headers", name)))
}
LiteralValue::Number(n) => {
let idx = *n as i64;
if idx < 1 || idx as usize > headers.len() {
return Err(ExcelError::new_value().with_message(format!(
"Field index {} out of range (1-{})",
idx,
headers.len()
)));
}
Ok((idx - 1) as usize)
}
LiteralValue::Int(i) => {
if *i < 1 || *i as usize > headers.len() {
return Err(ExcelError::new_value().with_message(format!(
"Field index {} out of range (1-{})",
i,
headers.len()
)));
}
Ok((*i - 1) as usize)
}
_ => Err(ExcelError::new_value().with_message("Field must be text or number")),
}
}
fn parse_criteria_range(
criteria_view: &crate::engine::range_view::RangeView<'_>,
db_headers: &[LiteralValue],
) -> Result<Vec<Vec<(usize, CriteriaPredicate)>>, ExcelError> {
let (crit_rows, crit_cols) = criteria_view.dims();
if crit_rows < 1 || crit_cols < 1 {
return Ok(vec![]);
}
let mut crit_col_map: Vec<Option<usize>> = Vec::with_capacity(crit_cols);
for c in 0..crit_cols {
let crit_header = criteria_view.get_cell(0, c);
if let LiteralValue::Text(name) = &crit_header {
let name_lower = name.to_lowercase();
let mut found = None;
for (i, h) in db_headers.iter().enumerate() {
if let LiteralValue::Text(hdr) = h
&& hdr.to_lowercase() == name_lower
{
found = Some(i);
break;
}
}
crit_col_map.push(found);
} else if matches!(crit_header, LiteralValue::Empty) {
crit_col_map.push(None);
} else {
crit_col_map.push(None);
}
}
let mut criteria_rows = Vec::new();
for r in 1..crit_rows {
let mut row_criteria = Vec::new();
let mut has_any_criteria = false;
for (c, db_col) in crit_col_map.iter().enumerate() {
let crit_val = criteria_view.get_cell(r, c);
if matches!(crit_val, LiteralValue::Empty) {
continue;
}
if let Some(db_col) = db_col {
let pred = parse_criteria(&crit_val)?;
row_criteria.push((*db_col, pred));
has_any_criteria = true;
}
}
if has_any_criteria {
criteria_rows.push(row_criteria);
}
}
Ok(criteria_rows)
}
fn row_matches_criteria(
db_view: &crate::engine::range_view::RangeView<'_>,
row: usize,
criteria_rows: &[Vec<(usize, CriteriaPredicate)>],
) -> bool {
if criteria_rows.is_empty() {
return true;
}
for crit_row in criteria_rows {
let mut all_match = true;
for (col_idx, pred) in crit_row {
let cell_val = db_view.get_cell(row, *col_idx);
if !criteria_match(pred, &cell_val) {
all_match = false;
break;
}
}
if all_match {
return true;
}
}
false
}
fn eval_d_function<'a, 'b>(
args: &[ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
agg_type: DAggregate,
) -> Result<CalcValue<'b>, ExcelError> {
if args.len() != 3 {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value().with_message(format!(
"D-function expects 3 arguments, got {}",
args.len()
)),
)));
}
let db_view = match args[0].range_view() {
Ok(v) => v,
Err(_) => {
let val = args[0].value()?.into_literal();
if let LiteralValue::Array(arr) = val {
crate::engine::range_view::RangeView::from_owned_rows(
arr,
crate::engine::DateSystem::Excel1900,
)
} else {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value().with_message("Database must be a range or array"),
)));
}
}
};
let (db_rows, db_cols) = db_view.dims();
if db_rows < 2 || db_cols < 1 {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value()
.with_message("Database must have headers and at least one data row"),
)));
}
let headers: Vec<LiteralValue> = (0..db_cols).map(|c| db_view.get_cell(0, c)).collect();
let field_val = args[1].value()?.into_literal();
let field_idx = resolve_field_index(&field_val, &headers)?;
let crit_view = match args[2].range_view() {
Ok(v) => v,
Err(_) => {
let val = args[2].value()?.into_literal();
if let LiteralValue::Array(arr) = val {
crate::engine::range_view::RangeView::from_owned_rows(
arr,
crate::engine::DateSystem::Excel1900,
)
} else {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value().with_message("Criteria must be a range or array"),
)));
}
}
};
let criteria_rows = parse_criteria_range(&crit_view, &headers)?;
let mut values: Vec<f64> = Vec::new();
for row in 1..db_rows {
if row_matches_criteria(&db_view, row, &criteria_rows) {
let cell_val = db_view.get_cell(row, field_idx);
match &cell_val {
LiteralValue::Number(n) => values.push(*n),
LiteralValue::Int(i) => values.push(*i as f64),
LiteralValue::Boolean(b) => {
if agg_type != DAggregate::Count {
values.push(if *b { 1.0 } else { 0.0 });
}
}
LiteralValue::Empty => {
}
LiteralValue::Text(s) => {
if let Ok(n) = coerce_num(&cell_val) {
values.push(n);
}
}
LiteralValue::Error(e) => {
return Ok(CalcValue::Scalar(LiteralValue::Error(e.clone())));
}
_ => {}
}
}
}
let result = match agg_type {
DAggregate::Sum => {
let sum: f64 = values.iter().sum();
LiteralValue::Number(sum)
}
DAggregate::Average => {
if values.is_empty() {
LiteralValue::Error(ExcelError::new_div())
} else {
let sum: f64 = values.iter().sum();
LiteralValue::Number(sum / values.len() as f64)
}
}
DAggregate::Count => {
LiteralValue::Number(values.len() as f64)
}
DAggregate::Max => {
if values.is_empty() {
LiteralValue::Number(0.0)
} else {
let max = values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
LiteralValue::Number(max)
}
}
DAggregate::Min => {
if values.is_empty() {
LiteralValue::Number(0.0)
} else {
let min = values.iter().cloned().fold(f64::INFINITY, f64::min);
LiteralValue::Number(min)
}
}
DAggregate::Product => {
if values.is_empty() {
LiteralValue::Number(0.0)
} else {
let product: f64 = values.iter().product();
LiteralValue::Number(product)
}
}
};
Ok(CalcValue::Scalar(result))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DStatOp {
VarSample, VarPop, StdevSample, StdevPop, }
fn eval_d_stat_function<'a, 'b>(
args: &[ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
stat_op: DStatOp,
) -> Result<CalcValue<'b>, ExcelError> {
if args.len() != 3 {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value().with_message(format!(
"D-function expects 3 arguments, got {}",
args.len()
)),
)));
}
let db_view = match args[0].range_view() {
Ok(v) => v,
Err(_) => {
let val = args[0].value()?.into_literal();
if let LiteralValue::Array(arr) = val {
crate::engine::range_view::RangeView::from_owned_rows(
arr,
crate::engine::DateSystem::Excel1900,
)
} else {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value().with_message("Database must be a range or array"),
)));
}
}
};
let (db_rows, db_cols) = db_view.dims();
if db_rows < 2 || db_cols < 1 {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value()
.with_message("Database must have headers and at least one data row"),
)));
}
let headers: Vec<LiteralValue> = (0..db_cols).map(|c| db_view.get_cell(0, c)).collect();
let field_val = args[1].value()?.into_literal();
let field_idx = resolve_field_index(&field_val, &headers)?;
let crit_view = match args[2].range_view() {
Ok(v) => v,
Err(_) => {
let val = args[2].value()?.into_literal();
if let LiteralValue::Array(arr) = val {
crate::engine::range_view::RangeView::from_owned_rows(
arr,
crate::engine::DateSystem::Excel1900,
)
} else {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value().with_message("Criteria must be a range or array"),
)));
}
}
};
let criteria_rows = parse_criteria_range(&crit_view, &headers)?;
let mut values: Vec<f64> = Vec::new();
for row in 1..db_rows {
if row_matches_criteria(&db_view, row, &criteria_rows) {
let cell_val = db_view.get_cell(row, field_idx);
match &cell_val {
LiteralValue::Number(n) => values.push(*n),
LiteralValue::Int(i) => values.push(*i as f64),
LiteralValue::Boolean(b) => {
values.push(if *b { 1.0 } else { 0.0 });
}
LiteralValue::Text(s) => {
if let Ok(n) = coerce_num(&cell_val) {
values.push(n);
}
}
LiteralValue::Error(e) => {
return Ok(CalcValue::Scalar(LiteralValue::Error(e.clone())));
}
_ => {}
}
}
}
let result = match stat_op {
DStatOp::VarSample | DStatOp::StdevSample => {
if values.len() < 2 {
LiteralValue::Error(ExcelError::new_div())
} else {
let n = values.len() as f64;
let mean = values.iter().sum::<f64>() / n;
let variance = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / (n - 1.0);
if matches!(stat_op, DStatOp::VarSample) {
LiteralValue::Number(variance)
} else {
LiteralValue::Number(variance.sqrt())
}
}
}
DStatOp::VarPop | DStatOp::StdevPop => {
if values.is_empty() {
LiteralValue::Error(ExcelError::new_div())
} else {
let n = values.len() as f64;
let mean = values.iter().sum::<f64>() / n;
let variance = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / n;
if matches!(stat_op, DStatOp::VarPop) {
LiteralValue::Number(variance)
} else {
LiteralValue::Number(variance.sqrt())
}
}
}
};
Ok(CalcValue::Scalar(result))
}
fn eval_dget<'a, 'b>(
args: &[ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
if args.len() != 3 {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value()
.with_message(format!("DGET expects 3 arguments, got {}", args.len())),
)));
}
let db_view = match args[0].range_view() {
Ok(v) => v,
Err(_) => {
let val = args[0].value()?.into_literal();
if let LiteralValue::Array(arr) = val {
crate::engine::range_view::RangeView::from_owned_rows(
arr,
crate::engine::DateSystem::Excel1900,
)
} else {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value().with_message("Database must be a range or array"),
)));
}
}
};
let (db_rows, db_cols) = db_view.dims();
if db_rows < 2 || db_cols < 1 {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value()
.with_message("Database must have headers and at least one data row"),
)));
}
let headers: Vec<LiteralValue> = (0..db_cols).map(|c| db_view.get_cell(0, c)).collect();
let field_val = args[1].value()?.into_literal();
let field_idx = resolve_field_index(&field_val, &headers)?;
let crit_view = match args[2].range_view() {
Ok(v) => v,
Err(_) => {
let val = args[2].value()?.into_literal();
if let LiteralValue::Array(arr) = val {
crate::engine::range_view::RangeView::from_owned_rows(
arr,
crate::engine::DateSystem::Excel1900,
)
} else {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value().with_message("Criteria must be a range or array"),
)));
}
}
};
let criteria_rows = parse_criteria_range(&crit_view, &headers)?;
let mut matching_values: Vec<LiteralValue> = Vec::new();
for row in 1..db_rows {
if row_matches_criteria(&db_view, row, &criteria_rows) {
matching_values.push(db_view.get_cell(row, field_idx));
}
}
let result = if matching_values.is_empty() {
LiteralValue::Error(ExcelError::new_value().with_message("No record matches criteria"))
} else if matching_values.len() > 1 {
LiteralValue::Error(
ExcelError::new_num().with_message("More than one record matches criteria"),
)
} else {
matching_values.into_iter().next().unwrap()
};
Ok(CalcValue::Scalar(result))
}
fn eval_dcounta<'a, 'b>(
args: &[ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
if args.len() != 3 {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value()
.with_message(format!("DCOUNTA expects 3 arguments, got {}", args.len())),
)));
}
let db_view = match args[0].range_view() {
Ok(v) => v,
Err(_) => {
let val = args[0].value()?.into_literal();
if let LiteralValue::Array(arr) = val {
crate::engine::range_view::RangeView::from_owned_rows(
arr,
crate::engine::DateSystem::Excel1900,
)
} else {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value().with_message("Database must be a range or array"),
)));
}
}
};
let (db_rows, db_cols) = db_view.dims();
if db_rows < 2 || db_cols < 1 {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value()
.with_message("Database must have headers and at least one data row"),
)));
}
let headers: Vec<LiteralValue> = (0..db_cols).map(|c| db_view.get_cell(0, c)).collect();
let field_val = args[1].value()?.into_literal();
let field_idx = resolve_field_index(&field_val, &headers)?;
let crit_view = match args[2].range_view() {
Ok(v) => v,
Err(_) => {
let val = args[2].value()?.into_literal();
if let LiteralValue::Array(arr) = val {
crate::engine::range_view::RangeView::from_owned_rows(
arr,
crate::engine::DateSystem::Excel1900,
)
} else {
return Ok(CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value().with_message("Criteria must be a range or array"),
)));
}
}
};
let criteria_rows = parse_criteria_range(&crit_view, &headers)?;
let mut count = 0;
for row in 1..db_rows {
if row_matches_criteria(&db_view, row, &criteria_rows) {
let cell_val = db_view.get_cell(row, field_idx);
match &cell_val {
LiteralValue::Empty => {
}
LiteralValue::Text(s) if s.is_empty() => {
}
LiteralValue::Error(e) => {
return Ok(CalcValue::Scalar(LiteralValue::Error(e.clone())));
}
_ => {
count += 1;
}
}
}
}
Ok(CalcValue::Scalar(LiteralValue::Number(count as f64)))
}
#[derive(Debug)]
pub struct DSumFn;
impl Function for DSumFn {
func_caps!(PURE, REDUCTION);
fn name(&self) -> &'static str {
"DSUM"
}
fn min_args(&self) -> usize {
3
}
fn variadic(&self) -> bool {
false
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
eval_d_function(args, ctx, DAggregate::Sum)
}
}
#[derive(Debug)]
pub struct DAverageFn;
impl Function for DAverageFn {
func_caps!(PURE, REDUCTION);
fn name(&self) -> &'static str {
"DAVERAGE"
}
fn min_args(&self) -> usize {
3
}
fn variadic(&self) -> bool {
false
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
eval_d_function(args, ctx, DAggregate::Average)
}
}
#[derive(Debug)]
pub struct DCountFn;
impl Function for DCountFn {
func_caps!(PURE, REDUCTION);
fn name(&self) -> &'static str {
"DCOUNT"
}
fn min_args(&self) -> usize {
3
}
fn variadic(&self) -> bool {
false
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
eval_d_function(args, ctx, DAggregate::Count)
}
}
#[derive(Debug)]
pub struct DMaxFn;
impl Function for DMaxFn {
func_caps!(PURE, REDUCTION);
fn name(&self) -> &'static str {
"DMAX"
}
fn min_args(&self) -> usize {
3
}
fn variadic(&self) -> bool {
false
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
eval_d_function(args, ctx, DAggregate::Max)
}
}
#[derive(Debug)]
pub struct DMinFn;
impl Function for DMinFn {
func_caps!(PURE, REDUCTION);
fn name(&self) -> &'static str {
"DMIN"
}
fn min_args(&self) -> usize {
3
}
fn variadic(&self) -> bool {
false
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
eval_d_function(args, ctx, DAggregate::Min)
}
}
#[derive(Debug)]
pub struct DProductFn;
impl Function for DProductFn {
func_caps!(PURE, REDUCTION);
fn name(&self) -> &'static str {
"DPRODUCT"
}
fn min_args(&self) -> usize {
3
}
fn variadic(&self) -> bool {
false
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
eval_d_function(args, ctx, DAggregate::Product)
}
}
#[derive(Debug)]
pub struct DStdevFn;
impl Function for DStdevFn {
func_caps!(PURE, REDUCTION);
fn name(&self) -> &'static str {
"DSTDEV"
}
fn min_args(&self) -> usize {
3
}
fn variadic(&self) -> bool {
false
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
eval_d_stat_function(args, ctx, DStatOp::StdevSample)
}
}
#[derive(Debug)]
pub struct DStdevPFn;
impl Function for DStdevPFn {
func_caps!(PURE, REDUCTION);
fn name(&self) -> &'static str {
"DSTDEVP"
}
fn min_args(&self) -> usize {
3
}
fn variadic(&self) -> bool {
false
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
eval_d_stat_function(args, ctx, DStatOp::StdevPop)
}
}
#[derive(Debug)]
pub struct DVarFn;
impl Function for DVarFn {
func_caps!(PURE, REDUCTION);
fn name(&self) -> &'static str {
"DVAR"
}
fn min_args(&self) -> usize {
3
}
fn variadic(&self) -> bool {
false
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
eval_d_stat_function(args, ctx, DStatOp::VarSample)
}
}
#[derive(Debug)]
pub struct DVarPFn;
impl Function for DVarPFn {
func_caps!(PURE, REDUCTION);
fn name(&self) -> &'static str {
"DVARP"
}
fn min_args(&self) -> usize {
3
}
fn variadic(&self) -> bool {
false
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
eval_d_stat_function(args, ctx, DStatOp::VarPop)
}
}
#[derive(Debug)]
pub struct DGetFn;
impl Function for DGetFn {
func_caps!(PURE, REDUCTION);
fn name(&self) -> &'static str {
"DGET"
}
fn min_args(&self) -> usize {
3
}
fn variadic(&self) -> bool {
false
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
eval_dget(args, ctx)
}
}
#[derive(Debug)]
pub struct DCountAFn;
impl Function for DCountAFn {
func_caps!(PURE, REDUCTION);
fn name(&self) -> &'static str {
"DCOUNTA"
}
fn min_args(&self) -> usize {
3
}
fn variadic(&self) -> bool {
false
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
ctx: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, ExcelError> {
eval_dcounta(args, ctx)
}
}
pub fn register_builtins() {
use std::sync::Arc;
crate::function_registry::register_function(Arc::new(DSumFn));
crate::function_registry::register_function(Arc::new(DAverageFn));
crate::function_registry::register_function(Arc::new(DCountFn));
crate::function_registry::register_function(Arc::new(DMaxFn));
crate::function_registry::register_function(Arc::new(DMinFn));
crate::function_registry::register_function(Arc::new(DProductFn));
crate::function_registry::register_function(Arc::new(DStdevFn));
crate::function_registry::register_function(Arc::new(DStdevPFn));
crate::function_registry::register_function(Arc::new(DVarFn));
crate::function_registry::register_function(Arc::new(DVarPFn));
crate::function_registry::register_function(Arc::new(DGetFn));
crate::function_registry::register_function(Arc::new(DCountAFn));
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_workbook::TestWorkbook;
use formualizer_parse::parser::{ASTNode, ASTNodeType};
use std::sync::Arc;
fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
wb.interpreter()
}
fn lit(v: LiteralValue) -> ASTNode {
ASTNode::new(ASTNodeType::Literal(v), None)
}
fn make_database() -> LiteralValue {
LiteralValue::Array(vec![
vec![
LiteralValue::Text("Name".into()),
LiteralValue::Text("Age".into()),
LiteralValue::Text("Salary".into()),
],
vec![
LiteralValue::Text("Alice".into()),
LiteralValue::Int(30),
LiteralValue::Int(50000),
],
vec![
LiteralValue::Text("Bob".into()),
LiteralValue::Int(25),
LiteralValue::Int(45000),
],
vec![
LiteralValue::Text("Carol".into()),
LiteralValue::Int(35),
LiteralValue::Int(60000),
],
vec![
LiteralValue::Text("Dave".into()),
LiteralValue::Int(30),
LiteralValue::Int(55000),
],
])
}
fn make_criteria_all() -> LiteralValue {
LiteralValue::Array(vec![vec![LiteralValue::Text("Name".into())]])
}
fn make_criteria_age_30() -> LiteralValue {
LiteralValue::Array(vec![
vec![LiteralValue::Text("Age".into())],
vec![LiteralValue::Int(30)],
])
}
fn make_criteria_age_gt_25() -> LiteralValue {
LiteralValue::Array(vec![
vec![LiteralValue::Text("Age".into())],
vec![LiteralValue::Text(">25".into())],
])
}
fn make_unicode_database() -> LiteralValue {
LiteralValue::Array(vec![
vec![
LiteralValue::Text("Имя".into()),
LiteralValue::Text("Статус".into()),
LiteralValue::Text("Акт".into()),
],
vec![
LiteralValue::Text("Анна".into()),
LiteralValue::Text("ГОТОВО".into()),
LiteralValue::Int(10),
],
vec![
LiteralValue::Text("Борис".into()),
LiteralValue::Text("готово".into()),
LiteralValue::Int(20),
],
vec![
LiteralValue::Text("Вера".into()),
LiteralValue::Text("Черновик".into()),
LiteralValue::Int(30),
],
])
}
fn make_unicode_criteria_ready() -> LiteralValue {
LiteralValue::Array(vec![
vec![LiteralValue::Text("статус".into())],
vec![LiteralValue::Text("готово".into())],
])
}
#[test]
fn dsum_all_salaries() {
let wb = TestWorkbook::new().with_function(Arc::new(DSumFn));
let ctx = interp(&wb);
let db = lit(make_database());
let field = lit(LiteralValue::Text("Salary".into()));
let criteria = lit(make_criteria_all());
let args = vec![
crate::traits::ArgumentHandle::new(&db, &ctx),
crate::traits::ArgumentHandle::new(&field, &ctx),
crate::traits::ArgumentHandle::new(&criteria, &ctx),
];
let f = ctx.context.get_function("", "DSUM").unwrap();
let result = f.dispatch(&args, &ctx.function_context(None)).unwrap();
assert_eq!(result.into_literal(), LiteralValue::Number(210000.0));
}
#[test]
fn dsum_age_30() {
let wb = TestWorkbook::new().with_function(Arc::new(DSumFn));
let ctx = interp(&wb);
let db = lit(make_database());
let field = lit(LiteralValue::Text("Salary".into()));
let criteria = lit(make_criteria_age_30());
let args = vec![
crate::traits::ArgumentHandle::new(&db, &ctx),
crate::traits::ArgumentHandle::new(&field, &ctx),
crate::traits::ArgumentHandle::new(&criteria, &ctx),
];
let f = ctx.context.get_function("", "DSUM").unwrap();
let result = f.dispatch(&args, &ctx.function_context(None)).unwrap();
assert_eq!(result.into_literal(), LiteralValue::Number(105000.0));
}
#[test]
fn daverage_age_gt_25() {
let wb = TestWorkbook::new().with_function(Arc::new(DAverageFn));
let ctx = interp(&wb);
let db = lit(make_database());
let field = lit(LiteralValue::Text("Salary".into()));
let criteria = lit(make_criteria_age_gt_25());
let args = vec![
crate::traits::ArgumentHandle::new(&db, &ctx),
crate::traits::ArgumentHandle::new(&field, &ctx),
crate::traits::ArgumentHandle::new(&criteria, &ctx),
];
let f = ctx.context.get_function("", "DAVERAGE").unwrap();
let result = f.dispatch(&args, &ctx.function_context(None)).unwrap();
assert_eq!(result.into_literal(), LiteralValue::Number(55000.0));
}
#[test]
fn dcount_age_30() {
let wb = TestWorkbook::new().with_function(Arc::new(DCountFn));
let ctx = interp(&wb);
let db = lit(make_database());
let field = lit(LiteralValue::Text("Salary".into()));
let criteria = lit(make_criteria_age_30());
let args = vec![
crate::traits::ArgumentHandle::new(&db, &ctx),
crate::traits::ArgumentHandle::new(&field, &ctx),
crate::traits::ArgumentHandle::new(&criteria, &ctx),
];
let f = ctx.context.get_function("", "DCOUNT").unwrap();
let result = f.dispatch(&args, &ctx.function_context(None)).unwrap();
assert_eq!(result.into_literal(), LiteralValue::Number(2.0));
}
#[test]
fn dmax_all() {
let wb = TestWorkbook::new().with_function(Arc::new(DMaxFn));
let ctx = interp(&wb);
let db = lit(make_database());
let field = lit(LiteralValue::Text("Salary".into()));
let criteria = lit(make_criteria_all());
let args = vec![
crate::traits::ArgumentHandle::new(&db, &ctx),
crate::traits::ArgumentHandle::new(&field, &ctx),
crate::traits::ArgumentHandle::new(&criteria, &ctx),
];
let f = ctx.context.get_function("", "DMAX").unwrap();
let result = f.dispatch(&args, &ctx.function_context(None)).unwrap();
assert_eq!(result.into_literal(), LiteralValue::Number(60000.0));
}
#[test]
fn dmin_all() {
let wb = TestWorkbook::new().with_function(Arc::new(DMinFn));
let ctx = interp(&wb);
let db = lit(make_database());
let field = lit(LiteralValue::Text("Salary".into()));
let criteria = lit(make_criteria_all());
let args = vec![
crate::traits::ArgumentHandle::new(&db, &ctx),
crate::traits::ArgumentHandle::new(&field, &ctx),
crate::traits::ArgumentHandle::new(&criteria, &ctx),
];
let f = ctx.context.get_function("", "DMIN").unwrap();
let result = f.dispatch(&args, &ctx.function_context(None)).unwrap();
assert_eq!(result.into_literal(), LiteralValue::Number(45000.0));
}
#[test]
fn dsum_field_by_index() {
let wb = TestWorkbook::new().with_function(Arc::new(DSumFn));
let ctx = interp(&wb);
let db = lit(make_database());
let field = lit(LiteralValue::Int(3)); let criteria = lit(make_criteria_all());
let args = vec![
crate::traits::ArgumentHandle::new(&db, &ctx),
crate::traits::ArgumentHandle::new(&field, &ctx),
crate::traits::ArgumentHandle::new(&criteria, &ctx),
];
let f = ctx.context.get_function("", "DSUM").unwrap();
let result = f.dispatch(&args, &ctx.function_context(None)).unwrap();
assert_eq!(result.into_literal(), LiteralValue::Number(210000.0));
}
#[test]
fn dsum_unicode_headers_are_case_insensitive() {
let wb = TestWorkbook::new().with_function(Arc::new(DSumFn));
let ctx = interp(&wb);
let db = lit(make_unicode_database());
let field = lit(LiteralValue::Text("акт".into()));
let criteria = lit(make_unicode_criteria_ready());
let args = vec![
crate::traits::ArgumentHandle::new(&db, &ctx),
crate::traits::ArgumentHandle::new(&field, &ctx),
crate::traits::ArgumentHandle::new(&criteria, &ctx),
];
let f = ctx.context.get_function("", "DSUM").unwrap();
let result = f.dispatch(&args, &ctx.function_context(None)).unwrap();
assert_eq!(result.into_literal(), LiteralValue::Number(30.0));
}
}