Skip to content

Commit 45ed5b8

Browse files
sequbaKrizz
andauthored
Refactor DependencyGraph to enable loading big spreadsheets (#1604)
* remove nodeids map and store node id directly * Fix unit tests * Add explenatory comment * Add changelog entry * Rename method to more descriptive name * Fix linter warnings --------- Co-authored-by: Kristjan Tulmin <kriz@live.com>
1 parent b87d281 commit 45ed5b8

19 files changed

Lines changed: 368 additions & 263 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1313
- Added a new function: N. [#1585](https://github.com/handsontable/hyperformula/issues/1585)
1414
- Added a new function: VALUE. [#1592](https://github.com/handsontable/hyperformula/issues/1592)
1515

16+
### Fixed
17+
18+
- Fixed `Error Map maximum size exceeded` error when loading big spreadsheets. [#1602](https://github.com/handsontable/hyperformula/issues/1602)
19+
1620
## [3.1.1] - 2025-12-18
1721

1822
### Fixed

src/DependencyGraph/AddressMapping/AddressMapping.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {Maybe} from '../../Maybe'
1111
import {SheetBoundaries} from '../../Sheet'
1212
import {ColumnsSpan, RowsSpan} from '../../Span'
1313
import {ArrayFormulaVertex, DenseStrategy, ValueCellVertex} from '../index'
14-
import {CellVertex} from '../Vertex'
14+
import {CellVertex} from '../CellVertex'
1515
import {ChooseAddressMapping} from './ChooseAddressMappingPolicy'
1616
import {AddressMappingStrategy} from './AddressMappingStrategy'
1717

src/DependencyGraph/AddressMapping/AddressMappingStrategy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import {SheetCellAddress, SimpleCellAddress} from '../../Cell'
77
import {Maybe} from '../../Maybe'
88
import {ColumnsSpan, RowsSpan} from '../../Span'
9-
import {CellVertex} from '../Vertex'
9+
import {CellVertex} from '../CellVertex'
1010

1111
export type AddressMappingStrategyConstructor = new (width: number, height: number) => AddressMappingStrategy
1212

src/DependencyGraph/AddressMapping/DenseStrategy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import {SheetCellAddress, simpleCellAddress, SimpleCellAddress} from '../../Cell'
77
import {Maybe} from '../../Maybe'
88
import {ColumnsSpan, RowsSpan} from '../../Span'
9-
import {CellVertex} from '../Vertex'
9+
import {CellVertex} from '../CellVertex'
1010
import {AddressMappingStrategy} from './AddressMappingStrategy'
1111

1212
/**

src/DependencyGraph/AddressMapping/SparseStrategy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import {SheetCellAddress, simpleCellAddress, SimpleCellAddress} from '../../Cell'
77
import {Maybe} from '../../Maybe'
88
import {ColumnsSpan, RowsSpan} from '../../Span'
9-
import {CellVertex} from '../Vertex'
9+
import {CellVertex} from '../CellVertex'
1010
import {AddressMappingStrategy} from './AddressMappingStrategy'
1111

1212
/**

src/DependencyGraph/CellVertex.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2025 Handsoncode. All rights reserved.
4+
*/
5+
6+
import {InterpreterValue} from '../interpreter/InterpreterValue'
7+
import {Vertex} from './Vertex'
8+
9+
/**
10+
* Represents vertex which keeps values of one or more cells
11+
*/
12+
export abstract class CellVertex extends Vertex {
13+
public abstract getCellValue(): InterpreterValue
14+
15+
constructor() {
16+
super()
17+
}
18+
}

src/DependencyGraph/DependencyGraph.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ export class DependencyGraph {
172172
}
173173

174174
public processCellDependencies(cellDependencies: CellDependency[], endVertex: Vertex) {
175-
const endVertexId = this.graph.getNodeId(endVertex)
175+
const endVertexId = endVertex.idInGraph
176176

177177
if (endVertexId === undefined) {
178178
throw new Error('End vertex not found')
@@ -188,8 +188,8 @@ export class DependencyGraph {
188188
this.rangeMapping.addOrUpdateVertex(rangeVertex)
189189
}
190190

191-
this.graph.addNodeAndReturnId(rangeVertex)
192-
const rangeVertexId = this.graph.getNodeId(rangeVertex)
191+
this.graph.addNodeIfNotExists(rangeVertex)
192+
const rangeVertexId = rangeVertex.idInGraph
193193

194194
if (rangeVertexId === undefined) {
195195
throw new Error('Range vertex not found')
@@ -257,7 +257,7 @@ export class DependencyGraph {
257257
}
258258

259259
const newVertex = new EmptyCellVertex()
260-
const newVertexId = this.graph.addNodeAndReturnId(newVertex)
260+
const newVertexId = this.graph.addNodeIfNotExists(newVertex)
261261
this.addressMapping.setCell(address, newVertex)
262262

263263
return { vertex: newVertex, id: newVertexId }
@@ -567,12 +567,12 @@ export class DependencyGraph {
567567
}
568568

569569
public addVertex(address: SimpleCellAddress, vertex: CellVertex): void {
570-
this.graph.addNodeAndReturnId(vertex)
570+
this.graph.addNodeIfNotExists(vertex)
571571
this.addressMapping.setCell(address, vertex)
572572
}
573573

574574
public addArrayVertex(address: SimpleCellAddress, vertex: ArrayFormulaVertex): void {
575-
this.graph.addNodeAndReturnId(vertex)
575+
this.graph.addNodeIfNotExists(vertex)
576576
this.setAddressMappingForArrayVertex(vertex, address)
577577
}
578578

@@ -861,7 +861,7 @@ export class DependencyGraph {
861861
}
862862

863863
private exchangeGraphNode(oldNode: Vertex, newNode: Vertex) {
864-
this.graph.addNodeAndReturnId(newNode)
864+
this.graph.addNodeIfNotExists(newNode)
865865
const adjNodesStored = this.graph.adjacentNodes(oldNode)
866866
this.removeVertex(oldNode)
867867
adjNodesStored.forEach((adjacentNode) => {
@@ -876,30 +876,30 @@ export class DependencyGraph {
876876
}
877877

878878
private correctInfiniteRangesDependency(address: SimpleCellAddress) {
879-
const relevantInfiniteRanges = (this.graph.getInfiniteRanges())
880-
.filter(({ node }) => (node as RangeVertex).range.addressInRange(address))
879+
const relevantInfiniteRanges = this.graph.getInfiniteRanges()
880+
.filter(node => (node as RangeVertex).range.addressInRange(address))
881881

882882
if (relevantInfiniteRanges.length <= 0) {
883883
return
884884
}
885885

886886
const { vertex, id: maybeVertexId } = this.fetchCellOrCreateEmpty(address)
887-
const vertexId = maybeVertexId ?? this.graph.getNodeId(vertex)
887+
const vertexId = maybeVertexId ?? vertex.idInGraph
888888

889889
if (vertexId === undefined) {
890890
throw new Error('Vertex not found')
891891
}
892892

893-
relevantInfiniteRanges.forEach(({ id }) => {
894-
this.graph.addEdge(vertexId, id)
893+
relevantInfiniteRanges.forEach((node) => {
894+
this.graph.addEdge(vertexId, node)
895895
})
896896
}
897897

898898
private exchangeOrAddGraphNode(oldNode: Maybe<Vertex>, newNode: Vertex) {
899899
if (oldNode) {
900900
this.exchangeGraphNode(oldNode, newNode)
901901
} else {
902-
this.graph.addNodeAndReturnId(newNode)
902+
this.graph.addNodeIfNotExists(newNode)
903903
}
904904
}
905905

@@ -946,7 +946,7 @@ export class DependencyGraph {
946946

947947
private correctInfiniteRangesDependenciesByRangeVertex(vertex: RangeVertex) {
948948
this.graph.getInfiniteRanges()
949-
.forEach(({ id: infiniteRangeVertexId, node: infiniteRangeVertex }) => {
949+
.forEach((infiniteRangeVertex) => {
950950
const intersection = vertex.range.intersectionWith((infiniteRangeVertex as RangeVertex).range)
951951

952952
if (intersection === undefined) {
@@ -955,7 +955,7 @@ export class DependencyGraph {
955955

956956
intersection.addresses(this).forEach((address: SimpleCellAddress) => {
957957
const { vertex, id } = this.fetchCellOrCreateEmpty(address)
958-
this.graph.addEdge(id ?? vertex, infiniteRangeVertexId)
958+
this.graph.addEdge(id ?? vertex, infiniteRangeVertex)
959959
})
960960
})
961961
}
@@ -1069,7 +1069,7 @@ export class DependencyGraph {
10691069
while (find.smallerRangeVertex === undefined) {
10701070
const newRangeVertex = new RangeVertex(AbsoluteCellRange.spanFrom(currentRangeVertex.range.start, currentRangeVertex.range.width(), currentRangeVertex.range.height() - 1))
10711071
this.rangeMapping.addOrUpdateVertex(newRangeVertex)
1072-
this.graph.addNodeAndReturnId(newRangeVertex)
1072+
this.graph.addNodeIfNotExists(newRangeVertex)
10731073
const restRange = new AbsoluteCellRange(simpleCellAddress(currentRangeVertex.range.start.sheet, currentRangeVertex.range.start.col, currentRangeVertex.range.end.row), currentRangeVertex.range.end)
10741074
this.addAllFromRange(restRange, currentRangeVertex)
10751075
this.graph.addEdge(newRangeVertex, currentRangeVertex)

src/DependencyGraph/EmptyCellVertex.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
* Copyright (c) 2025 Handsoncode. All rights reserved.
44
*/
55

6+
import {CellVertex} from './CellVertex'
67
import {EmptyValue, EmptyValueType} from '../interpreter/InterpreterValue'
78

89
/**
910
* Represents singleton vertex bound to all empty cells
1011
*/
11-
export class EmptyCellVertex {
12-
constructor() {}
12+
export class EmptyCellVertex extends CellVertex {
13+
constructor() {
14+
super()
15+
}
1316

1417
/**
1518
* Retrieves cell value bound to that singleton

src/DependencyGraph/FormulaVertex.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,23 @@ import {LazilyTransformingAstService} from '../LazilyTransformingAstService'
1414
import {Maybe} from '../Maybe'
1515
import {Ast} from '../parser'
1616
import {ColumnsSpan, RowsSpan} from '../Span'
17+
import {CellVertex} from './CellVertex'
1718

18-
export abstract class FormulaVertex {
19+
20+
/**
21+
* Abstract base class for vertices that contain formulas in the dependency graph.
22+
*
23+
* Stores formula AST, cell address, and version for lazy transformation support.
24+
* Has two concrete implementations: {@link ScalarFormulaVertex} for single-cell formulas
25+
* and {@link ArrayFormulaVertex} for array formulas that span multiple cells.
26+
*/
27+
export abstract class FormulaVertex extends CellVertex {
1928
protected constructor(
2029
protected formula: Ast,
2130
protected cellAddress: SimpleCellAddress,
2231
public version: number
2332
) {
33+
super()
2434
}
2535

2636
public get width(): number {
@@ -79,6 +89,12 @@ export abstract class FormulaVertex {
7989
public abstract isComputed(): boolean
8090
}
8191

92+
/**
93+
* Represents a formula vertex that produces an array result spanning multiple cells.
94+
*
95+
* Array formulas are transformed eagerly (unlike scalar formulas) and store their
96+
* computed values in a {@link CellArray} structure.
97+
*/
8298
export class ArrayFormulaVertex extends FormulaVertex {
8399
array: CellArray
84100

@@ -221,7 +237,10 @@ export class ArrayFormulaVertex extends FormulaVertex {
221237
}
222238

223239
/**
224-
* Represents vertex which keeps formula
240+
* Represents a formula vertex that produces a single scalar value.
241+
*
242+
* Unlike {@link ArrayFormulaVertex}, scalar formulas are transformed lazily
243+
* and cache their computed value for retrieval.
225244
*/
226245
export class ScalarFormulaVertex extends FormulaVertex {
227246
/** Most recently computed value of this formula. */

0 commit comments

Comments
 (0)