Skip to content

Commit 362df83

Browse files
HF-85: Implement all database functions (#1652)
### Context Implements all 12 Excel database functions (D-functions family). Originally scoped to DCOUNT only, expanded to the full family since all share the same infrastructure (field resolution, criteria parsing, row matching). ### How did you test your changes? - 185 unit tests in hyperformula-tests (handsontable/hyperformula-tests#9) - 167-case Excel validation workbook (all PASS in Excel Desktop) - 147-test runtime integration suite + 30 edge case tests (booleans, negatives, zeros, wildcards, large DB, comparison operators) - Verified all error types match Excel precisely (#VALUE!, #DIV/0!, #NUM!) ### Types of changes - [x] New feature or improvement (a non-breaking change that adds functionality) - [x] Additional language file, or a change to an existing language file (translations) - [x] Change to the documentation ### Related issues: 1. Fixes HF-85 ### Checklist: - [x] I have reviewed the guidelines about Contributing to HyperFormula and I confirm that my code follows the code style of this project. - [x] My change is compatible with Microsoft Excel. - [x] My change is compatible with Google Sheets. - [x] I described my changes in the CHANGELOG.md file. - [x] My changes require a documentation update. --- ## Summary - 12 database functions: DCOUNT, DCOUNTA, DSUM, DAVERAGE, DMAX, DMIN, DGET, DPRODUCT, DSTDEV, DSTDEVP, DVAR, DVARP - New `DatabasePlugin` (533 lines) with shared infrastructure - i18n translations for all 17 languages (proper Excel-localized names) - Documentation: `built-in-functions.md` (Database section), `known-limitations.md` (Nuances) ## Implementation - `withDatabaseArgs()` helper eliminates boilerplate across all 12 functions - `resolveFieldIndex()` — string (case-insensitive header match) or 1-based numeric index with `Math.trunc()` - `buildDatabaseCriteria()` — OR across rows, AND within row, reuses `CriterionBuilder` - `rowMatchesCriteria()` — `.some()` (OR) + `.every()` (AND) - `collectNumericValues()` — shared by DSTDEV/DSTDEVP/DVAR/DVARP ## Excel behavior edge cases | Function | Edge case | Behavior | |---|---|---| | DMAX, DMIN, DPRODUCT | No matches | Returns 0 | | DGET | 0 matches / 2+ matches | #VALUE! / #NUM! | | DAVERAGE | No numeric values | #DIV/0! | | DSTDEV, DVAR | ≤1 value | #DIV/0! (sample, n-1) | | DSTDEVP, DVARP | 1 value / 0 values | 0 / #DIV/0! (population, n) | ## Linked - Tests PR: handsontable/hyperformula-tests#9 - ClickUp: HF-85 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adds new interpreter functionality that affects formula evaluation semantics (criteria parsing, error handling, and aggregation behavior), though changes are largely additive and isolated to a new plugin plus docs/i18n updates. > > **Overview** > Adds a new `DatabasePlugin` implementing the 12 Excel database functions (`DCOUNT`, `DCOUNTA`, `DSUM`, `DAVERAGE`, `DMAX`, `DMIN`, `DGET`, `DPRODUCT`, `DSTDEV`, `DSTDEVP`, `DVAR`, `DVARP`), including shared logic for field resolution, criteria parsing, row matching, and Excel-like error propagation. > > Updates the public surface by exporting the plugin, adding translations for these functions across language packs, and expanding docs/CHANGELOG to include a new **Database** functions category and function list. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 74c4397. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8b525cb commit 362df83

20 files changed

Lines changed: 808 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
99

1010
### Added
1111

12+
- Added 12 database functions: DCOUNT, DSUM, DAVERAGE, DMAX, DMIN, DGET, DPRODUCT, DCOUNTA, DSTDEV, DSTDEVP, DVAR, DVARP. [#1652](https://github.com/handsontable/hyperformula/pull/1652)
1213
- Added new functions: PERCENTILE, PERCENTILE.INC, PERCENTILE.EXC, QUARTILE, QUARTILE.INC, QUARTILE.EXC. [#1650](https://github.com/handsontable/hyperformula/pull/1650)
1314
- Added `maxPendingLazyTransformations` configuration option to control memory usage by limiting accumulated transformations before cleanup. [#1629](https://github.com/handsontable/hyperformula/issues/1629)
1415
- Added a new function: TEXTJOIN. [#1640](https://github.com/handsontable/hyperformula/pull/1640)

docs/guide/built-in-functions.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ The latest version of HyperFormula has an extensive collection of
2828
**{{ $page.functionsCount }}** functions grouped into categories:
2929

3030
- [Array manipulation](#array-manipulation)
31+
- [Database](#database)
3132
- [Date and time](#date-and-time)
3233
- [Engineering](#engineering)
3334
- [Information](#information)
@@ -40,8 +41,7 @@ The latest version of HyperFormula has an extensive collection of
4041
- [Statistical](#statistical)
4142
- [Text](#text)
4243

43-
_Some categories such as compatibility, cube, and database are yet to be
44-
supported._
44+
_Some categories such as compatibility and cube are yet to be supported._
4545

4646
::: tip
4747
You can modify the built-in functions or create your own, by adding a [custom function](custom-functions).
@@ -91,6 +91,23 @@ Total number of functions: **{{ $page.functionsCount }}**
9191
| YEAR | Returns the year as a number according to the internal calculation rules. | YEAR(Number) |
9292
| YEARFRAC | Computes the difference between two date values, in fraction of years. | YEARFRAC(Date2, Date1[, Format]) |
9393

94+
### Database
95+
96+
| Function ID | Description | Syntax |
97+
|:------------|:----------------------------------------------------------------------------------------------------------------|:----------------------------------|
98+
| DAVERAGE | Returns the average of all values in a database field that match the given criteria. | DAVERAGE(Database, Field, Criteria) |
99+
| DCOUNT | Counts the cells containing numbers in a database field that match the given criteria. | DCOUNT(Database, Field, Criteria) |
100+
| DCOUNTA | Counts the non-empty cells in a database field that match the given criteria. | DCOUNTA(Database, Field, Criteria) |
101+
| DGET | Returns the single value from a database field that matches the given criteria. Returns #VALUE! if no records match, and #NUM! if more than one record matches. | DGET(Database, Field, Criteria) |
102+
| DMAX | Returns the maximum value in a database field that matches the given criteria. | DMAX(Database, Field, Criteria) |
103+
| DMIN | Returns the minimum value in a database field that matches the given criteria. | DMIN(Database, Field, Criteria) |
104+
| DPRODUCT | Returns the product of all values in a database field that match the given criteria. | DPRODUCT(Database, Field, Criteria) |
105+
| DSTDEV | Returns the sample standard deviation of all values in a database field that match the given criteria. | DSTDEV(Database, Field, Criteria) |
106+
| DSTDEVP | Returns the population standard deviation of all values in a database field that match the given criteria. | DSTDEVP(Database, Field, Criteria) |
107+
| DSUM | Returns the sum of all values in a database field that match the given criteria. | DSUM(Database, Field, Criteria) |
108+
| DVAR | Returns the sample variance of all values in a database field that match the given criteria. | DVAR(Database, Field, Criteria) |
109+
| DVARP | Returns the population variance of all values in a database field that match the given criteria. | DVARP(Database, Field, Criteria) |
110+
94111
### Engineering
95112

96113
| Function ID | Description | Syntax |

src/i18n/languages/csCZ.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,18 @@ const dictionary: RawTranslationPackage = {
7575
DAYS360: 'ROK360',
7676
DAYS: 'DAYS',
7777
DB: 'ODPIS.ZRYCH',
78+
DAVERAGE: 'DPRŮMĚR',
79+
DCOUNT: 'DPOČET',
80+
DCOUNTA: 'DPOČET2',
81+
DGET: 'DZÍSKAT',
82+
DMAX: 'DMAX',
83+
DMIN: 'DMIN',
84+
DPRODUCT: 'DSOUČIN',
85+
DSTDEV: 'DSMODCH.VÝBĚR',
86+
DSTDEVP: 'DSMODCH',
87+
DSUM: 'DSUMA',
88+
DVAR: 'DVAR.VÝBĚR',
89+
DVARP: 'DVAR',
7890
DDB: 'ODPIS.ZRYCH2',
7991
DEC2BIN: 'DEC2BIN',
8092
DEC2HEX: 'DEC2HEX',

src/i18n/languages/daDK.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,18 @@ const dictionary: RawTranslationPackage = {
7575
DAYS360: 'DAGE360',
7676
DAYS: 'DAGE',
7777
DB: 'DB',
78+
DAVERAGE: 'DMIDDEL',
79+
DCOUNT: 'DTÆL',
80+
DCOUNTA: 'DTÆLV',
81+
DGET: 'DHENT',
82+
DMAX: 'DMAKS',
83+
DMIN: 'DMIN',
84+
DPRODUCT: 'DPRODUKT',
85+
DSTDEV: 'DSTDAFV',
86+
DSTDEVP: 'DSTDAFVP',
87+
DSUM: 'DSUM',
88+
DVAR: 'DVARIANS',
89+
DVARP: 'DVARIANSP',
7890
DDB: 'DSA',
7991
DEC2BIN: 'DEC.TIL.BIN',
8092
DEC2HEX: 'DEC.TIL.HEX',

src/i18n/languages/deDE.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,18 @@ const dictionary: RawTranslationPackage = {
7575
DAYS360: 'TAGE360',
7676
DAYS: 'TAGE',
7777
DB: 'GDA2',
78+
DAVERAGE: 'DBMITTELWERT',
79+
DCOUNT: 'DBANZAHL',
80+
DCOUNTA: 'DBANZAHL2',
81+
DGET: 'DBAUSZUG',
82+
DMAX: 'DBMAX',
83+
DMIN: 'DBMIN',
84+
DPRODUCT: 'DBPRODUKT',
85+
DSTDEV: 'DBSTDABW',
86+
DSTDEVP: 'DBSTDABWN',
87+
DSUM: 'DBSUMME',
88+
DVAR: 'DBVARIANZ',
89+
DVARP: 'DBVARIANZEN',
7890
DDB: 'GDA',
7991
DEC2BIN: 'DEZINBIN',
8092
DEC2HEX: 'DEZINHEX',

src/i18n/languages/enGB.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,18 @@ const dictionary: RawTranslationPackage = {
7676
DAYS360: 'DAYS360',
7777
DAYS: 'DAYS',
7878
DB: 'DB',
79+
DAVERAGE: 'DAVERAGE',
80+
DCOUNT: 'DCOUNT',
81+
DCOUNTA: 'DCOUNTA',
82+
DGET: 'DGET',
83+
DMAX: 'DMAX',
84+
DMIN: 'DMIN',
85+
DPRODUCT: 'DPRODUCT',
86+
DSTDEV: 'DSTDEV',
87+
DSTDEVP: 'DSTDEVP',
88+
DSUM: 'DSUM',
89+
DVAR: 'DVAR',
90+
DVARP: 'DVARP',
7991
DDB: 'DDB',
8092
DEC2BIN: 'DEC2BIN',
8193
DEC2HEX: 'DEC2HEX',

src/i18n/languages/esES.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,18 @@ export const dictionary: RawTranslationPackage = {
7575
DAYS360: 'DIAS360',
7676
DAYS: 'DÍAS',
7777
DB: 'DB',
78+
DAVERAGE: 'BDPROMEDIO',
79+
DCOUNT: 'BDCONTAR',
80+
DCOUNTA: 'BDCONTARA',
81+
DGET: 'BDEXTRAER',
82+
DMAX: 'BDMAX',
83+
DMIN: 'BDMIN',
84+
DPRODUCT: 'BDPRODUCTO',
85+
DSTDEV: 'BDDESVEST',
86+
DSTDEVP: 'BDDESVESTP',
87+
DSUM: 'BDSUMA',
88+
DVAR: 'BDVAR',
89+
DVARP: 'BDVARP',
7890
DDB: 'DDB',
7991
DEC2BIN: 'DEC.A.BIN',
8092
DEC2HEX: 'DEC.A.HEX',

src/i18n/languages/fiFI.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,18 @@ const dictionary: RawTranslationPackage = {
7575
DAYS360: 'PÄIVÄT360',
7676
DAYS: 'PV',
7777
DB: 'DB',
78+
DAVERAGE: 'TKESKIARVO',
79+
DCOUNT: 'TLASKE',
80+
DCOUNTA: 'TLASKE.A',
81+
DGET: 'TNOUDA',
82+
DMAX: 'TMAKS',
83+
DMIN: 'TMIN',
84+
DPRODUCT: 'TTULO',
85+
DSTDEV: 'TKESKIHAJONTA',
86+
DSTDEVP: 'TKESKIHAJONTAP',
87+
DSUM: 'TSUMMA',
88+
DVAR: 'TVARIANSSI',
89+
DVARP: 'TVARIANSSIP',
7890
DDB: 'DDB',
7991
DEC2BIN: 'DESBIN',
8092
DEC2HEX: 'DESHEKSA',

src/i18n/languages/frFR.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,18 @@ const dictionary: RawTranslationPackage = {
7575
DAYS360: 'JOURS360',
7676
DAYS: 'JOURS',
7777
DB: 'DB',
78+
DAVERAGE: 'BDMOYENNE',
79+
DCOUNT: 'BDNB',
80+
DCOUNTA: 'BDNBVAL',
81+
DGET: 'BDLIRE',
82+
DMAX: 'BDMAX',
83+
DMIN: 'BDMIN',
84+
DPRODUCT: 'BDPRODUIT',
85+
DSTDEV: 'BDECARTYPE',
86+
DSTDEVP: 'BDECARTYPEP',
87+
DSUM: 'BDSOMME',
88+
DVAR: 'BDVAR',
89+
DVARP: 'BDVARP',
7890
DDB: 'DDB',
7991
DEC2BIN: 'DECBIN',
8092
DEC2HEX: 'DECHEX',

src/i18n/languages/huHU.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,18 @@ const dictionary: RawTranslationPackage = {
7575
DAYS360: 'DAYS360',
7676
DAYS: 'NAPOK',
7777
DB: 'DB',
78+
DAVERAGE: 'AB.ÁTLAG',
79+
DCOUNT: 'AB.DARAB',
80+
DCOUNTA: 'AB.DARAB2',
81+
DGET: 'AB.MEZŐ',
82+
DMAX: 'AB.MAX',
83+
DMIN: 'AB.MIN',
84+
DPRODUCT: 'AB.SZORZAT',
85+
DSTDEV: 'AB.SZÓRÁS',
86+
DSTDEVP: 'AB.SZÓRÁS2',
87+
DSUM: 'AB.SZUM',
88+
DVAR: 'AB.VAR',
89+
DVARP: 'AB.VAR2',
7890
DDB: 'KCSA',
7991
DEC2BIN: 'DEC.BIN',
8092
DEC2HEX: 'DEC.HEX',

0 commit comments

Comments
 (0)