Skip to content

HF-85: Implement all database functions#1652

Open
marcin-kordas-hoc wants to merge 27 commits intodevelopfrom
feature/HF-85-implement-function-dcount
Open

HF-85: Implement all database functions#1652
marcin-kordas-hoc wants to merge 27 commits intodevelopfrom
feature/HF-85-implement-function-dcount

Conversation

@marcin-kordas-hoc
Copy link
Copy Markdown
Collaborator

@marcin-kordas-hoc marcin-kordas-hoc commented Apr 10, 2026

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

  • New feature or improvement (a non-breaking change that adds functionality)
  • Additional language file, or a change to an existing language file (translations)
  • Change to the documentation

Related issues:

  1. Fixes HF-85

Checklist:

  • I have reviewed the guidelines about Contributing to HyperFormula and I confirm that my code follows the code style of this project.
  • My change is compatible with Microsoft Excel.
  • My change is compatible with Google Sheets.
  • I described my changes in the CHANGELOG.md file.
  • 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

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.

Reviewed by Cursor Bugbot for commit 74c4397. Bugbot is set up for automated code reviews on this repo. Configure here.

@qunabu
Copy link
Copy Markdown

qunabu commented Apr 10, 2026

Task linked: HF-85 Implement function DCOUNT

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 10, 2026

Performance comparison of head (74c4397) vs base (8b525cb)

                                     testName |   base |   head |  change
-------------------------------------------------------------------------
                                      Sheet A | 505.32 | 503.23 |  -0.41%
                                      Sheet B | 156.85 | 163.36 |  +4.15%
                                      Sheet T | 139.19 | 139.82 |  +0.45%
                                Column ranges | 485.49 | 478.82 |  -1.37%
Sheet A:  change value, add/remove row/column |  15.36 |  16.92 | +10.16%
 Sheet B: change value, add/remove row/column | 130.88 | 136.41 |  +4.23%
                   Column ranges - add column |  145.6 | 152.17 |  +4.51%
                Column ranges - without batch | 452.49 | 463.36 |  +2.40%
                        Column ranges - batch | 118.39 | 115.27 |  -2.64%

@marcin-kordas-hoc marcin-kordas-hoc force-pushed the feature/HF-85-implement-function-dcount branch from c7dd75e to 18906bc Compare April 16, 2026 12:00
@marcin-kordas-hoc marcin-kordas-hoc marked this pull request as ready for review April 17, 2026 07:53
Comment thread src/i18n/languages/csCZ.ts Outdated
Comment thread src/interpreter/plugin/DatabasePlugin.ts
marcin-kordas-hoc added a commit that referenced this pull request Apr 17, 2026
Addresses Cursor Bugbot findings on PR #1652:

- csCZ.ts: DCOUNT was English 'DCOUNT' instead of localized 'DPOCET'
  (consistent with DCOUNTA = 'DPOCET2')
- resolveFieldIndex: coerce boolean field arg to number (TRUE -> 1,
  FALSE -> 0) per Excel convention, matching ArithmeticHelper semantics

Regression test added in hyperformula-tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread src/interpreter/plugin/DatabasePlugin.ts
Comment thread src/interpreter/plugin/DatabasePlugin.ts
Comment thread src/i18n/languages/nlNL.ts Outdated
Comment thread src/interpreter/plugin/DatabasePlugin.ts
Comment thread src/interpreter/plugin/DatabasePlugin.ts
Comment thread src/interpreter/plugin/DatabasePlugin.ts
Comment thread src/i18n/languages/plPL.ts Outdated
Comment thread src/interpreter/plugin/DatabasePlugin.ts Outdated
@sequba sequba changed the title HF-85: Implement DCOUNT database function HF-85: Implement all database functions Apr 20, 2026
Comment thread docs/guide/integration-with-angular.md Outdated

For more details, see the [client-side installation](client-side-installation.md) section.

## Basic usage
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The framework integration guides should not be modified in this PR

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted in commit 0c3decd04 — framework integration guide changes were out of scope for HF-85 (D-functions). The corresponding HF-122 PR #1653 ships the actual framework guides.

Comment thread docs/guide/known-limitations.md Outdated
Comment on lines +41 to +42
* DGET returns `#VALUE!` when no records match the criteria, and `#NUM!` when more than one record matches.
* DMAX, DMIN, and DPRODUCT return `0` when no records match the criteria.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is it listed as limitations? Is it different to what Excel does?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — DGET / DMAX / DMIN / DPRODUCT behaviors match Excel exactly (DGET returns #VALUE! for no match, #NUM! for multiple matches; DMAX/DMIN/DPRODUCT return 0 when no match). They are not HyperFormula deviations and do not belong in known-limitations.md. Removed in commit 465321fd7.

DB: 'DB',
DAVERAGE: 'TKESKIARVO',
DCOUNT: 'TLASKE',
DCOUNTA: 'TLASKE.A',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finnish DCOUNTA translation has incorrect dot separator

Medium Severity

The Finnish translation for DCOUNTA is 'TLASKE.A' (with a dot), but the correct Excel Finnish function name is 'TLASKEA' (without a dot). Users typing the correct Finnish name won't get the function recognized, and the registered name doesn't match Excel Desktop.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 465321f. Configure here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

False positive — Finnish Excel uses dot syntax in function names (e.g., LASKE.A for COUNTA, KESKIARVO.JOS for AVERAGEIF, LASKE.TYHJÄT for COUNTBLANK). TLASKE.A for DCOUNTA is consistent with this convention and matches Microsoft's official Finnish translation. The dot is intentional, not a separator typo.

Comment thread src/i18n/languages/ruRU.ts Outdated
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 29, 2026

Deploy Preview for hyperformula-docs ready!

Name Link
🔨 Latest commit 74c4397
🔍 Latest deploy log https://app.netlify.com/projects/hyperformula-docs/deploys/69f1d49732cdd400087f836c
😎 Deploy Preview https://deploy-preview-1652--hyperformula-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit fae8844. Configure here.

Comment thread src/interpreter/plugin/DatabasePlugin.ts
marcin-kordas-hoc and others added 12 commits April 29, 2026 09:48
Add idiomatic code examples to React, Angular, Vue, and Svelte
integration pages showing HyperFormula initialization and reading
calculated values. Each guide uses framework-specific patterns
(React hooks, Angular service, Vue ref/markRaw, Svelte reactivity).

Closes HF-122.
Add new DatabasePlugin with DCOUNT(database, field, criteria) that counts
numeric values in a specified field column for rows matching criteria.
Supports field resolution by name (case-insensitive) and 1-based index,
OR logic across criteria rows, AND logic within rows, and comparison
operators in criteria values. Includes translations for all 17 languages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add three new database functions to DatabasePlugin with i18n
translations for all 17 languages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove unnecessary type assertions in DAVERAGE, DMAX, DMIN methods.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New DatabasePlugin with shared helpers for the complete D-function family:
DCOUNT, DSUM, DAVERAGE, DMAX, DMIN, DGET, DPRODUCT, DCOUNTA,
DSTDEV, DSTDEVP, DVAR, DVARP.

169 tests across 12 test suites. All pass.
17 language translations per function.
Excel validation: 152/152 PASS (verified in Excel Desktop).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…own-limitations.md

- Add ### Database section with all 12 D-functions (DAVERAGE, DCOUNT, DCOUNTA,
  DGET, DMAX, DMIN, DPRODUCT, DSTDEV, DSTDEVP, DSUM, DVAR, DVARP) to
  built-in-functions.md in alphabetical order between Date and Engineering
- Add Database to TOC and remove "database" from "yet to be supported" intro
- Add nuance notes for DGET (#VALUE!/#NUM! error semantics) and
  DMAX/DMIN/DPRODUCT (return 0 when no records match)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduce databaseFunctionParameters constant and withDatabaseArgs()
helper to eliminate repeated field/criteria resolution across all 12
database functions. Reduces file from 725 to 533 lines with no
behavioral changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove "(consistent with Excel behavior)" from known-limitations.md
  per review guideline: describe HF behavior only, no Excel references
- Remove trailing space in collectNumericValues

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
marcin-kordas-hoc and others added 15 commits April 29, 2026 09:48
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Addresses Cursor Bugbot findings on PR #1652:

- csCZ.ts: DCOUNT was English 'DCOUNT' instead of localized 'DPOCET'
  (consistent with DCOUNTA = 'DPOCET2')
- resolveFieldIndex: coerce boolean field arg to number (TRUE -> 1,
  FALSE -> 0) per Excel convention, matching ArithmeticHelper semantics

Regression test added in hyperformula-tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use Number.isFinite to reject NaN and Infinity before the bounds
check — the previous `index < 1 || index > headers.length` comparison
silently returns false for NaN, which would let a NaN index through
and produce incorrect results. Addresses Cursor Bugbot finding.

Regression test added in hyperformula-tests.
SCALAR argument types do not auto-propagate CellError in the framework
(coerceArgumentsToRequiredTypes skips propagation for SCALAR), so the
raw error reaches resolveFieldIndex and buildDatabaseCriteria. Both
sites used to fall through into generic #VALUE! / BadCriterion,
discarding the original error type.

Now both check for CellError upfront and return it verbatim, preserving
#DIV/0!, #NUM!, etc. — matching Excel's error-propagation behavior.
Addresses Cursor Bugbot finding.

Regression tests added in hyperformula-tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Dutch translation had a triple A typo — DCOUNT is DBAANTAL and the
COUNTA variant with C suffix should be DBAANTALC. Addresses Cursor
Bugbot finding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous value lacked the D prefix and reversed the word order.
Verified against Microsoft Support's official Norwegian Excel
reference: DCOUNT → DANTALL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
isExtendedNumber silently skipped CellError values, letting numeric
aggregation D-functions return a clean number even when a matching
field cell contained #DIV/0! / #N/A / etc. Excel propagates the error
instead.

Each of DSUM, DPRODUCT, DAVERAGE, DMAX, DMIN now returns the error
immediately when encountered. collectNumericValues (shared by DSTDEV,
DSTDEVP, DVAR, DVARP) now returns CellError | number[] so those four
propagate too. DCOUNT, DCOUNTA, and DGET are unchanged — COUNT-family
semantics in Excel do not propagate field errors, and DGET already
returns the cell verbatim.

Regression test added in hyperformula-tests. Addresses Cursor Bugbot
finding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extends the CellError propagation introduced in 947a9fd to DCOUNT
and DCOUNTA. This keeps all 12 D-functions consistent: a matching
field cell containing an error surfaces the error with its original
type rather than being silently skipped or counted. Addresses Cursor
Bugbot findings.

Regression tests added in hyperformula-tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- plPL: DCOUNTA 'BD.ILE.REKORDOW.A' -> 'BD.ILE.REKORDÓW.A' (missing Ó,
  matches DCOUNT pattern; Cursor Bugbot finding).
- plPL: DAVERAGE 'BD.SREDNIA' -> 'BD.ŚREDNIA' (missing Ś, per Microsoft
  Polish documentation; pre-emptive fix for the same class of issue).
- DGET: explicit CellError check on the matched cell makes the error-
  propagation path consistent with the other D-functions (previously
  worked by accident via fall-through).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… docs

Proactive audit against Microsoft's official localized Excel
documentation (https://support.microsoft.com/{lang}/office/...) caught
systemic mismatches beyond what Cursor Bugbot had flagged so far.

Czech (csCZ) — 7 corrections (diacritics + DSUM word):
- DAVERAGE DPRUMER -> DPRŮMĚR
- DCOUNT   DPOCET -> DPOČET
- DCOUNTA  DPOCET2 -> DPOČET2
- DGET     DZISKAT -> DZÍSKAT
- DPRODUCT DSOUCIN -> DSOUČIN
- DSTDEV   DSMODCH.VYBER -> DSMODCH.VÝBĚR
- DSUM     DSOUCET -> DSUMA (entire word was wrong)
- DVAR     DVAR.VYBER -> DVAR.VÝBĚR

Turkish (trTR) — 11 corrections (VSEÇ prefix across the family):
- DAVERAGE VORTALAMA -> VSEÇORT
- DCOUNT   VSAY -> VSEÇSAY
- DCOUNTA  VSAYMA -> VSEÇSAYDOLU
- DMAX     VMAKS -> VSEÇMAK
- DMIN     VMİN -> VSEÇMİN
- DPRODUCT VÇARPIM -> VSEÇÇARP
- DSTDEV   VSTDSAPMA -> VSEÇSTDSAPMA
- DSTDEVP  VSTDSAPMAP -> VSEÇSTDSAPMAS
- DSUM     VTOPLA -> VSEÇTOPLA
- DVAR     VVAR -> VSEÇVAR
- DVARP    VVARP -> VSEÇVARS
(DGET stays as VAL — Microsoft Turkish keeps the short form.)

Danish (daDK) — 3 corrections:
- DAVERAGE DGENNEMSNIT -> DMIDDEL
- DCOUNT   ANTAL.DB -> DTÆL
- DCOUNTA  ANTAL.DBV -> DTÆLV

Portuguese (ptPT) — 3 corrections:
- DCOUNTA  BDCONTARA -> BDCONTAR.VAL
- DGET     BDEXTRAIR -> BDOBTER
- DPRODUCT BDPRODUTO -> BDMULTIPL

All 24 changes verified directly against Microsoft's support pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The defensive CellError guard added earlier returned the error as soon
as a matching row with a field error was seen, even when more rows
also matched. Excel's DGET resolves the "multiple matches → #NUM!"
check first and only surfaces field errors when exactly one record
matches.

Move the CellError check to after the loop so the match count is
finalised first. Addresses Cursor Bugbot finding.

Regression tests added in hyperformula-tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Basic Usage sections added in earlier HF-85 commits duplicate the
framework integration guides being delivered in PR #1653 (HF-122).
Reverting them here keeps HF-85's scope strictly to D-functions and
avoids three-way conflict when #1653 merges first.
Per sequba review: DGET returning #VALUE! / #NUM! and DMAX/DMIN/DPRODUCT
returning 0 for no-match are Excel-identical behaviors, not HyperFormula
deviations. They do not belong in known-limitations.md.

The SEQUENCE/FILTER parse-time-dimensions bullet remains — that one is
a HyperFormula-specific limitation.
Per Microsoft Excel Russian translations, DCOUNTA is БСЧЁТА (with Ё, not Е).
The pattern is consistent with COUNT (СЧЁТ), COUNTA (СЧЁТЗ), and DCOUNT
(БСЧЁТ) which all use Ё. The previous БСЧЕТА was a typo missing the
ё diacritical mark.

Cursor Bugbot finding (real bug, not historical re-flag — verified by
cross-referencing with COUNT/COUNTA/DCOUNT existing translations and
Microsoft Excel Russian docs convention).
Per Cursor Bugbot review and Excel behavior — when a criteria header
cell evaluates to a CellError (e.g., #REF!, #DIV/0!), the D-function
should return that error rather than silently mapping the column to
-1 and continuing. This aligns the header-cell behavior with the data
cell behavior at the same location (lines 535-537), which already
propagated CellError.
@marcin-kordas-hoc marcin-kordas-hoc force-pushed the feature/HF-85-implement-function-dcount branch from fae8844 to 74c4397 Compare April 29, 2026 09:51
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 29, 2026

Codecov Report

❌ Patch coverage is 94.84979% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.16%. Comparing base (8b525cb) to head (74c4397).

Files with missing lines Patch % Lines
src/interpreter/plugin/DatabasePlugin.ts 94.82% 12 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #1652      +/-   ##
===========================================
- Coverage    97.18%   97.16%   -0.03%     
===========================================
  Files          174      175       +1     
  Lines        15086    15319     +233     
  Branches      3223     3287      +64     
===========================================
+ Hits         14662    14884     +222     
- Misses         424      435      +11     
Files with missing lines Coverage Δ
src/i18n/languages/csCZ.ts 100.00% <ø> (ø)
src/i18n/languages/daDK.ts 100.00% <ø> (ø)
src/i18n/languages/deDE.ts 100.00% <ø> (ø)
src/i18n/languages/enGB.ts 100.00% <ø> (ø)
src/i18n/languages/esES.ts 100.00% <ø> (ø)
src/i18n/languages/fiFI.ts 100.00% <ø> (ø)
src/i18n/languages/frFR.ts 100.00% <ø> (ø)
src/i18n/languages/huHU.ts 100.00% <ø> (ø)
src/i18n/languages/itIT.ts 100.00% <ø> (ø)
src/i18n/languages/nbNO.ts 100.00% <ø> (ø)
... and 8 more

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants