From c52c05aa8e5901516dfd8bd155cc623a44283e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hatcher?= Date: Fri, 14 Nov 2025 23:41:43 +0100 Subject: [PATCH] FIX: Fixes several issues with DATABASE functions Fixes #547 --- base/src/functions/database.rs | 338 ++++++++++++------ .../DMIN_DMAX_DAVERAGE_DSUM_DCOUNT_DGET.xlsx | Bin 0 -> 24106 bytes 2 files changed, 233 insertions(+), 105 deletions(-) create mode 100644 xlsx/tests/calc_tests/DMIN_DMAX_DAVERAGE_DSUM_DCOUNT_DGET.xlsx diff --git a/base/src/functions/database.rs b/base/src/functions/database.rs index 3e47854..d38727a 100644 --- a/base/src/functions/database.rs +++ b/base/src/functions/database.rs @@ -1,6 +1,9 @@ +use chrono::Datelike; + use crate::{ calc_result::CalcResult, expressions::{parser::Node, token::Error, types::CellReferenceIndex}, + formatter::dates::date_to_serial_number, Model, }; @@ -28,6 +31,15 @@ impl Model { Err(e) => return e, }; + if db_right.row <= db_left.row { + // no data rows + return CalcResult::Error { + error: Error::VALUE, + origin: cell, + message: "No data rows in database".to_string(), + }; + } + let mut sum = 0.0f64; let mut count = 0usize; @@ -82,6 +94,15 @@ impl Model { Err(e) => return e, }; + if db_right.row <= db_left.row { + // no data rows + return CalcResult::Error { + error: Error::VALUE, + origin: cell, + message: "No data rows in database".to_string(), + }; + } + let mut count = 0usize; let mut row = db_left.row + 1; // skip header while row <= db_right.row { @@ -123,10 +144,19 @@ impl Model { Err(e) => return e, }; + if db_right.row <= db_left.row { + // no data rows + return CalcResult::Error { + error: Error::VALUE, + origin: cell, + message: "No data rows in database".to_string(), + }; + } + let mut result: Option = None; let mut matches = 0usize; - let mut row = db_left.row + 1; // skip header + let mut row = db_left.row + 1; while row <= db_right.row { if self.db_row_matches_criteria(db_left, db_right, row, criteria) { matches += 1; @@ -187,7 +217,16 @@ impl Model { Err(e) => return e, }; - let mut sum = 0.0f64; + if db_right.row <= db_left.row { + // no data rows + return CalcResult::Error { + error: Error::VALUE, + origin: cell, + message: "No data rows in database".to_string(), + }; + } + + let mut sum = 0.0; // skip header let mut row = db_left.row + 1; @@ -220,42 +259,80 @@ impl Model { field_arg: &Node, cell: CellReferenceIndex, ) -> Result { - // If numeric -> index - if let Ok(n) = self.get_number(field_arg, cell) { - let idx = if n < 1.0 { - n.ceil() as i32 - } else { - n.floor() as i32 - }; - if idx < 1 || db_left.column + idx - 1 > db_right.column { - return Err(CalcResult::Error { - error: Error::VALUE, - origin: cell, - message: "Field index out of range".to_string(), - }); + let field_column_name = match self.evaluate_node_in_context(field_arg, cell) { + CalcResult::String(s) => s.to_lowercase(), + CalcResult::Number(index) => { + let index = index.floor() as i32; + if index < 1 || db_left.column + index - 1 > db_right.column { + return Err(CalcResult::Error { + error: Error::VALUE, + origin: cell, + message: "Field index out of range".to_string(), + }); + } + return Ok(db_left.column + index - 1); + } + CalcResult::Boolean(b) => { + return if b { + Ok(1) + } else { + // Index 0 is out of range + Err(CalcResult::Error { + error: Error::VALUE, + origin: cell, + message: "Invalid field specifier".to_string(), + }) + }; + } + error @ CalcResult::Error { .. } => { + return Err(error); + } + CalcResult::Range { .. } => { + return Err(CalcResult::Error { + error: Error::NIMPL, + origin: cell, + message: "Arrays not supported yet".to_string(), + }) + } + CalcResult::EmptyCell | CalcResult::EmptyArg => "".to_string(), + CalcResult::Array(_) => { + return Err(CalcResult::Error { + error: Error::NIMPL, + origin: cell, + message: "Arrays not supported yet".to_string(), + }) } - return Ok(db_left.column + idx - 1); - } - - // Otherwise treat as header name - let wanted = match self.get_string(field_arg, cell) { - Ok(s) => s.to_lowercase(), - Err(e) => return Err(e), }; - let mut col = db_left.column; - while col <= db_right.column { + // We search in the database a column whose header matches field_column_name + for column in db_left.column..=db_right.column { let v = self.evaluate_cell(CellReferenceIndex { sheet: db_left.sheet, row: db_left.row, - column: col, + column, }); - if let CalcResult::String(s) = v { - if s.to_lowercase() == wanted { - return Ok(col); + match &v { + CalcResult::String(s) => { + if s.to_lowercase() == field_column_name { + return Ok(column); + } } + CalcResult::Number(n) => { + if field_column_name == n.to_string() { + return Ok(column); + } + } + CalcResult::Boolean(b) => { + if field_column_name == b.to_string() { + return Ok(column); + } + } + CalcResult::Error { .. } + | CalcResult::Range { .. } + | CalcResult::EmptyCell + | CalcResult::EmptyArg + | CalcResult::Array(_) => {} } - col += 1; } Err(CalcResult::Error { @@ -278,53 +355,84 @@ impl Model { // Read criteria headers (first row of criteria range) // Map header name (lowercased) -> db column (if exists) - let mut crit_cols: Vec> = Vec::new(); - let mut col = c_left.column; - while col <= c_right.column { - let h = self.evaluate_cell(CellReferenceIndex { + let mut crit_cols: Vec = Vec::new(); + let mut header_count = 0; + // We cover the criteria table: + // headerA | headerB | ... + // critA1 | critA2 | ... + // critB1 | critB2 | ... + // ... + for column in c_left.column..=c_right.column { + let cell = CellReferenceIndex { sheet: c_left.sheet, row: c_left.row, - column: col, - }); - let db_col = if let CalcResult::String(s) = h { + column, + }; + let criteria_header = self.evaluate_cell(cell); + if let Ok(s) = self.cast_to_string(criteria_header, cell) { + // Non-empty string header. If the header is non string we skip it + header_count += 1; let wanted = s.to_lowercase(); - // Find corresponding DB column - let mut db_c = db_left.column; - let mut found: Option = None; - while db_c <= db_right.column { - let hdr = self.evaluate_cell(CellReferenceIndex { + + // Find corresponding Database column + let mut found = false; + for db_column in db_left.column..=db_right.column { + let db_header = self.evaluate_cell(CellReferenceIndex { sheet: db_left.sheet, row: db_left.row, - column: db_c, + column: db_column, }); - if let CalcResult::String(hs) = hdr { + if let Ok(hs) = self.cast_to_string(db_header, cell) { if hs.to_lowercase() == wanted { - found = Some(db_c); + crit_cols.push(db_column); + found = true; break; } } - db_c += 1; } - found - } else { - None + if !found { + // that means the criteria column has no matching DB column + // If the criteria condition is empty then we remove this condition + // otherwise this condition can never be satisfied + // We evaluate all criteria rows to see if any is non-empty + let mut has_non_empty = false; + for r in (c_left.row + 1)..=c_right.row { + let ccell = self.evaluate_cell(CellReferenceIndex { + sheet: c_left.sheet, + row: r, + column, + }); + if !matches!(ccell, CalcResult::EmptyCell | CalcResult::EmptyArg) { + has_non_empty = true; + break; + } + } + if has_non_empty { + // This criteria column can never be satisfied + header_count -= 1; + } + } }; - crit_cols.push(db_col); - col += 1; } - // If no criteria rows (only headers), everything matches if c_right.row <= c_left.row { + // If no criteria rows (only headers), everything matches return true; } + if header_count == 0 { + // If there are not "String" headers, nothing matches + // NB: There might be String headers that do not match any DB columns, + // in that case everything matches. + return false; + } + // Evaluate each criteria row (OR) - let mut r = c_left.row + 1; - while r <= c_right.row { + for r in (c_left.row + 1)..=c_right.row { // AND across columns for this criteria row let mut and_ok = true; - for (offset, maybe_db_col) in crit_cols.iter().enumerate() { + for (offset, db_col) in crit_cols.iter().enumerate() { // Criteria cell let ccell = self.evaluate_cell(CellReferenceIndex { sheet: c_left.sheet, @@ -337,17 +445,11 @@ impl Model { continue; } - // Header without mapping -> ignore this criteria column (Excel ignores unknown headers) - let db_col = match maybe_db_col { - Some(c) => *c, - None => continue, - }; - // Database value for this row/column let db_val = self.evaluate_cell(CellReferenceIndex { sheet: db_left.sheet, row, - column: db_col, + column: *db_col, }); if !self.criteria_cell_matches(&db_val, &ccell) { @@ -360,8 +462,6 @@ impl Model { // This criteria row satisfied (OR) return true; } - - r += 1; } // none matched @@ -373,44 +473,67 @@ impl Model { fn criteria_cell_matches(&self, db_val: &CalcResult, crit_cell: &CalcResult) -> bool { // Convert the criteria cell to a string for operator parsing if possible, // otherwise fall back to equality via compare_values. - let crit_str_opt = match crit_cell { - CalcResult::String(s) => Some(s.clone()), - CalcResult::Number(n) => Some(n.to_string()), - CalcResult::Boolean(b) => Some(if *b { - "TRUE".to_string() - } else { - "FALSE".to_string() - }), - CalcResult::EmptyCell | CalcResult::EmptyArg => return true, + + let mut criteria = match crit_cell { + CalcResult::String(s) => s.trim().to_string(), + CalcResult::Number(n) => { + // treat as equality with number + return match db_val { + CalcResult::Number(v) => (*v - *n).abs() <= f64::EPSILON, + _ => false, + }; + } + CalcResult::Boolean(b) => { + // check equality with boolean + return match db_val { + CalcResult::Boolean(v) => *v == *b, + _ => false, + }; + } + CalcResult::EmptyCell | CalcResult::EmptyArg => "".to_string(), CalcResult::Error { .. } => return false, CalcResult::Range { .. } | CalcResult::Array(_) => return false, }; - if crit_str_opt.is_none() { - return compare_values(db_val, crit_cell) == 0; - } - let mut crit = match crit_str_opt { - Some(s) => s.trim().to_string(), - None => return false, - }; - // Detect operator prefix let mut op = "="; // default equality (with wildcard semantics for strings) let prefixes = ["<>", ">=", "<=", ">", "<", "="]; for p in prefixes.iter() { - if crit.starts_with(p) { + if criteria.starts_with(p) { op = p; - crit = crit[p.len()..].trim().to_string(); + criteria = criteria[p.len()..].trim().to_string(); break; } } - // Try to parse numeric RHS - let rhs_num = crit.parse::().ok(); + // Is it a number? + let rhs_num = criteria.parse::().ok(); + + // Is it a date? + // FIXME: We should parse dates according to locale settings + let rhs_date = criteria.parse::().ok(); match op { ">" | ">=" | "<" | "<=" => { - if let Some(t) = rhs_num { + if let Some(d) = rhs_date { + // date comparison + let serial = match date_to_serial_number(d.day(), d.month(), d.year()) { + Ok(sn) => sn as f64, + Err(_) => return false, + }; + + if let CalcResult::Number(n) = db_val { + match op { + ">" => *n > serial, + ">=" => *n >= serial, + "<" => *n < serial, + "<=" => *n <= serial, + _ => false, + } + } else { + false + } + } else if let Some(t) = rhs_num { // numeric comparison if let CalcResult::Number(n) = db_val { match op { @@ -421,7 +544,6 @@ impl Model { _ => false, } } else { - // For non-numbers, use compare_values with a number token to emulate Excel ordering let rhs = CalcResult::Number(t); let c = compare_values(db_val, &rhs); match op { @@ -434,7 +556,7 @@ impl Model { } } else { // string comparison (case-insensitive) using compare_values semantics - let rhs = CalcResult::String(crit.to_lowercase()); + let rhs = CalcResult::String(criteria.to_lowercase()); let lhs = match db_val { CalcResult::String(s) => CalcResult::String(s.to_lowercase()), x => x.clone(), @@ -453,8 +575,8 @@ impl Model { // not equal (with wildcard semantics for strings) // If rhs has wildcards and db_val is string, do regex; else use compare_values != 0 if let CalcResult::String(s) = db_val { - if crit.contains('*') || crit.contains('?') { - if let Ok(re) = from_wildcard_to_regex(&crit.to_lowercase(), true) { + if criteria.contains('*') || criteria.contains('?') { + if let Ok(re) = from_wildcard_to_regex(&criteria.to_lowercase(), true) { return !result_matches_regex( &CalcResult::String(s.to_lowercase()), &re, @@ -465,7 +587,7 @@ impl Model { let rhs = if let Some(n) = rhs_num { CalcResult::Number(n) } else { - CalcResult::String(crit.to_lowercase()) + CalcResult::String(criteria.to_lowercase()) }; let lhs = match db_val { CalcResult::String(s) => CalcResult::String(s.to_lowercase()), @@ -485,18 +607,19 @@ impl Model { } else { // textual/boolean equals (case-insensitive), wildcard-enabled for strings if let CalcResult::String(s) = db_val { - if crit.contains('*') || crit.contains('?') { - if let Ok(re) = from_wildcard_to_regex(&crit.to_lowercase(), true) { + if criteria.contains('*') || criteria.contains('?') { + if let Ok(re) = from_wildcard_to_regex(&criteria.to_lowercase(), true) { return result_matches_regex( &CalcResult::String(s.to_lowercase()), &re, ); } } - return s.to_lowercase() == crit.to_lowercase(); + // This is weird but we only need to check if "starts with" for equality + return s.to_lowercase().starts_with(&criteria.to_lowercase()); } // Fallback: compare_values equality - compare_values(db_val, &CalcResult::String(crit.to_lowercase())) == 0 + compare_values(db_val, &CalcResult::String(criteria.to_lowercase())) == 0 } } } @@ -528,9 +651,18 @@ impl Model { Err(e) => return e, }; + if db_right.row <= db_left.row { + // no data rows + return CalcResult::Error { + error: Error::VALUE, + origin: cell, + message: "No data rows in database".to_string(), + }; + } + let mut best: Option = None; - let mut row = db_left.row + 1; // skip header + let mut row = db_left.row + 1; while row <= db_right.row { if self.db_row_matches_criteria(db_left, db_right, row, criteria) { let v = self.evaluate_cell(CellReferenceIndex { @@ -538,15 +670,15 @@ impl Model { row, column: field_col, }); - if let CalcResult::Number(n) = v { - if n.is_finite() { + if let CalcResult::Number(value) = v { + if value.is_finite() { best = Some(match best { - None => n, + None => value, Some(cur) => { if want_max { - n.max(cur) + value.max(cur) } else { - n.min(cur) + value.min(cur) } } }); @@ -558,11 +690,7 @@ impl Model { match best { Some(v) => CalcResult::Number(v), - None => CalcResult::Error { - error: Error::VALUE, - origin: cell, - message: "No numeric values matched criteria".to_string(), - }, + None => CalcResult::Number(0.0), } } } diff --git a/xlsx/tests/calc_tests/DMIN_DMAX_DAVERAGE_DSUM_DCOUNT_DGET.xlsx b/xlsx/tests/calc_tests/DMIN_DMAX_DAVERAGE_DSUM_DCOUNT_DGET.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..79a07da4d281d7fc0a8c5eff19862f946f5728d1 GIT binary patch literal 24106 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 A$p8QV literal 0 HcmV?d00001