Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion lib/features/redis/redis_key_tree_engine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,15 @@ class RedisKeyFolderNode {
final List<RedisKeyLeafNode> leaves;

/// Total count of keys in this subtree (both direct leaves and nested leaves).
int get totalKeyCount {
///
/// Counted on first use and kept: the folder row asks for it on every
/// rebuild, and a walk of the whole subtree each time costs O(keys x depth).
/// The tree is not changed once the engine returns it, so the count stays
/// right; it is lazy so a node whose lists are filled after construction
/// still counts what is in them at first use.
late final int totalKeyCount = _countKeys();

int _countKeys() {
var count = leaves.length;
for (final f in folders) {
count += f.totalKeyCount;
Expand Down
12 changes: 10 additions & 2 deletions lib/features/workspace/grid_groupings_engine.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/foundation.dart';
import 'package:querya_desktop/features/workspace/grid_selection_calc_engine.dart';

/// Aggregation operation to perform on groups.
enum GroupingAggType {
Expand Down Expand Up @@ -194,8 +195,15 @@ abstract final class GridGroupingsEngine {

for (final row in rows) {
if (targetCol < row.length) {
final val = row[targetCol].replaceAll(',', '').trim();
final parsed = double.tryParse(val);
// `trim()` hands back the cell itself when there is nothing to trim,
// and the comma-aware parse does not copy it either. Anything it
// cannot read takes the old copy, so the result is the same as
// `double.tryParse(cell.replaceAll(',', '').trim())`.
final cell = row[targetCol].trim();
final parsed = cell.contains(',')
? GridSelectionCalcEngine.parseThousandsSeparated(cell) ??
double.tryParse(cell.replaceAll(',', '').trim())
: double.tryParse(cell);
if (parsed != null && !parsed.isNaN && !parsed.isInfinite) {
numbers.add(parsed);
}
Expand Down
1 change: 0 additions & 1 deletion lib/features/workspace/grid_selection_calc_engine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,6 @@ abstract final class GridSelectionCalcEngine {
/// exact: an integer below 2^53 times or divided by an exactly represented
/// power of ten is one correctly rounded operation. Longer numbers, which
/// do not fit that, take the copy and `double.tryParse`.
@foundation.visibleForTesting
static double? parseThousandsSeparated(String s) {
final n = s.length;
var i = 0;
Expand Down
18 changes: 18 additions & 0 deletions test/features/workspace/data_grid_engines_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,24 @@ void main() {
expect(activeAvg.aggValue, equals(200.0));
});

test('aggregates numbers written with thousands separators', () {
final groups = GridGroupingsEngine.buildGroups(
groupColIndices: [0],
rows: const [
['a', '1,234.5'],
['a', ' 2,000 '],
['a', '1,2,3'], // commas count for nothing: 123
['a', 'n/a, see note'], // not a number: skipped
['a', '10'],
],
aggConfig: const GroupAggregationConfig(
aggType: GroupingAggType.sum,
targetColIndex: 1,
),
);
expect(groups.single.aggValue, closeTo(1234.5 + 2000 + 123 + 10, 1e-9));
});

test('sorts groups by key or aggregate', () {
final keySorted = GridGroupingsEngine.buildGroups(
groupColIndices: [1],
Expand Down
Loading