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
106 changes: 98 additions & 8 deletions lib/core/database/result_row_string_convert.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,120 @@ import 'package:flutter/foundation.dart';
const int kResultStringConvertYieldEvery = 250;
const int kResultStringConvertComputeThreshold = 1000;

/// High-performance string interning pool for deduplicating cell strings
/// across low-cardinality database columns (e.g. booleans, enums, status codes, IDs).
class StringInternPool {
StringInternPool({
this.maxEntries = 4096,
this.maxStringLength = 128,
}) {
_pool.addAll(_preloaded);
}

final int maxEntries;
final int maxStringLength;

static const Map<String, String> _preloaded = {
'NULL': 'NULL',
'true': 'true',
'false': 'false',
'0': '0',
'1': '1',
'2': '2',
'3': '3',
'4': '4',
'5': '5',
'6': '6',
'7': '7',
'8': '8',
'9': '9',
'10': '10',
'': '',
'active': 'active',
'inactive': 'inactive',
'pending': 'pending',
'completed': 'completed',
'success': 'success',
'failed': 'failed',
'error': 'error',
'warning': 'warning',
'info': 'info',
'deleted': 'deleted',
'draft': 'draft',
'published': 'published',
};

final Map<String, String> _pool = {};

int get size => _pool.length;

/// Returns the canonical deduplicated instance of [value].
String intern(String value) {
if (value.length > maxStringLength) {
return value;
}
final existing = _pool[value];
if (existing != null) return existing;

if (_pool.length < maxEntries) {
_pool[value] = value;
}
return value;
}

/// Converts [value] to string and returns the interned canonical instance.
String internObject(Object? value) {
if (value == null) return 'NULL';
if (value is String) return intern(value);
if (value is bool) return value ? 'true' : 'false';
if (value is int && value >= 0 && value <= 10) {
return _preloaded[value.toString()] ?? value.toString();
}
return intern(value.toString());
}
}

/// Maps null cells to `'NULL'` and others via [Object.toString].
String resultCellToDisplayString(Object? value) =>
value == null ? 'NULL' : value.toString();
String resultCellToDisplayString(Object? value, [StringInternPool? pool]) {
if (pool != null) {
return pool.internObject(value);
}
if (value == null) return 'NULL';
if (value is bool) return value ? 'true' : 'false';
return value.toString();
}

/// Converts [rowValues] to string rows synchronously.
List<List<String>> convertResultRowsToStringsSync(List<List<Object?>> rowValues) {
/// Converts [rowValues] to string rows synchronously using a string interning pool.
List<List<String>> convertResultRowsToStringsSync(
List<List<Object?>> rowValues, {
StringInternPool? pool,
}) {
if (rowValues.isEmpty) return const [];
final activePool = pool ?? StringInternPool();
return [
for (final row in rowValues)
[for (final value in row) resultCellToDisplayString(value)],
[for (final value in row) activePool.internObject(value)],
];
}

/// Top-level function suitable for [compute] offloading.
List<List<String>> convertResultRowsToStringsCompute(List<List<Object?>> rowValues) =>
convertResultRowsToStringsSync(rowValues);

/// Converts [rowValues] to string rows, yielding periodically.
/// Converts [rowValues] to string rows, yielding periodically and interning strings.
Future<List<List<String>>> convertResultRowsToStringsYielding(
List<List<Object?>> rowValues, {
int yieldEvery = kResultStringConvertYieldEvery,
StringInternPool? pool,
}) async {
if (rowValues.isEmpty) return const [];

final activePool = pool ?? StringInternPool();
final out = <List<String>>[];
for (var i = 0; i < rowValues.length; i++) {
final row = rowValues[i];
out.add([
for (final value in row) resultCellToDisplayString(value),
for (final value in row) activePool.internObject(value),
]);
if (yieldEvery > 0 && (i + 1) % yieldEvery == 0) {
await Future<void>.delayed(Duration.zero);
Expand All @@ -46,10 +131,15 @@ Future<List<List<String>>> convertResultRowsToStringsAdaptive(
List<List<Object?>> rowValues, {
int computeThreshold = kResultStringConvertComputeThreshold,
int yieldEvery = kResultStringConvertYieldEvery,
StringInternPool? pool,
}) async {
if (rowValues.isEmpty) return const [];
if (rowValues.length >= computeThreshold) {
return compute(convertResultRowsToStringsCompute, rowValues);
}
return convertResultRowsToStringsYielding(rowValues, yieldEvery: yieldEvery);
return convertResultRowsToStringsYielding(
rowValues,
yieldEvery: yieldEvery,
pool: pool,
);
}
100 changes: 96 additions & 4 deletions test/core/database/result_row_string_convert_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,55 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:querya_desktop/core/database/result_row_string_convert.dart';

void main() {
group('StringInternPool', () {
test('deduplicates identical string instances', () {
final pool = StringInternPool();

// Create separate String instances dynamically
final s1 = String.fromCharCodes('active'.codeUnits);
final s2 = String.fromCharCodes('active'.codeUnits);

expect(identical(s1, s2), isFalse);

final interned1 = pool.intern(s1);
final interned2 = pool.intern(s2);

expect(identical(interned1, interned2), isTrue);
});

test('preloads common database literals', () {
final pool = StringInternPool();

expect(identical(pool.internObject(null), 'NULL'), isTrue);
expect(identical(pool.internObject(true), 'true'), isTrue);
expect(identical(pool.internObject(false), 'false'), isTrue);
expect(identical(pool.internObject(0), '0'), isTrue);
expect(identical(pool.internObject(1), '1'), isTrue);
});

test('respects maxStringLength boundary', () {
final pool = StringInternPool(maxStringLength: 10);
const longStr = 'this_is_a_very_long_string_that_should_not_be_interned';

final res = pool.intern(longStr);
expect(res, longStr);
// Pool size should not increase for long string
final initialSize = pool.size;
pool.intern(longStr);
expect(pool.size, initialSize);
});

test('respects maxEntries capacity limit', () {
final pool = StringInternPool(maxEntries: 30);

for (var i = 0; i < 50; i++) {
pool.intern('unique_key_$i');
}

expect(pool.size, lessThanOrEqualTo(30));
});
});

group('result_row_string_convert', () {
final sampleRows = <List<Object?>>[
[1, null, 'a'],
Expand All @@ -13,9 +62,24 @@ void main() {
['2', 'x', 'NULL'],
];

test('convertResultRowsToStringsSync maps rows correctly', () {
expect(convertResultRowsToStringsSync(sampleRows), expectedOutput);
expect(convertResultRowsToStringsSync(const []), isEmpty);
test('convertResultRowsToStringsSync maps rows correctly and deduplicates repeated cells', () {
final rowsWithDuplicates = <List<Object?>>[
['active', 1, true, 'US'],
['active', 1, true, 'US'],
['active', 2, false, 'EU'],
];

final out = convertResultRowsToStringsSync(rowsWithDuplicates);
expect(out.length, 3);
expect(out[0], ['active', '1', 'true', 'US']);
expect(out[1], ['active', '1', 'true', 'US']);
expect(out[2], ['active', '2', 'false', 'EU']);

// Deduplicated string references must be identical pointers
expect(identical(out[0][0], out[1][0]), isTrue);
expect(identical(out[0][1], out[1][1]), isTrue);
expect(identical(out[0][2], out[1][2]), isTrue);
expect(identical(out[0][3], out[1][3]), isTrue);
});

test('convertResultRowsToStringsCompute maps rows correctly', () {
Expand Down Expand Up @@ -54,6 +118,34 @@ void main() {
expect(out[0], ['0', 'NULL', 'val_0']);
expect(out[9], ['9', 'NULL', 'val_9']);
});

test('benchmark 10,000 low-cardinality rows demonstrates pointer reuse', () {
final statuses = ['active', 'pending', 'cancelled', 'completed'];
final countries = ['US', 'DE', 'FR', 'GB', 'JP'];

final dataset = List<List<Object?>>.generate(
10000,
(i) => [
i % 10,
statuses[i % statuses.length],
countries[i % countries.length],
i % 2 == 0,
null,
],
);

final stopwatch = Stopwatch()..start();
final result = convertResultRowsToStringsSync(dataset);
stopwatch.stop();

expect(result.length, 10000);
expect(stopwatch.elapsedMilliseconds, lessThan(100));

// Pointer verification
expect(identical(result[0][1], result[4][1]), isTrue);
expect(identical(result[0][2], result[5][2]), isTrue);
expect(identical(result[0][3], result[2][3]), isTrue);
expect(identical(result[0][4], result[1][4]), isTrue);
});
});
}

Loading