From 2bcd5cf45b036004e3b5a95a20e6ba3dddbbaaa4 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Thu, 15 Oct 2026 07:01:41 +0300 Subject: [PATCH 1/2] perf(history): build the one-line preview with an early-exit scanner _previewOneLine collapsed whitespace over the whole query with a regex and only then cut it to 93 characters, so a 100 KB migration in the history was copied whole for every row just to show its first line. previewSqlOneLine reads characters from the start, collapses whitespace on the fly and stops once the preview is known to be longer than 96. The output is the same as before; a test compares it with the old implementation. Closes #1545 --- .../workspace/sql_query_history_dialog.dart | 57 ++++++++++++++++--- .../sql_query_history_preview_test.dart | 44 ++++++++++++++ 2 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 test/features/workspace/sql_query_history_preview_test.dart diff --git a/lib/features/workspace/sql_query_history_dialog.dart b/lib/features/workspace/sql_query_history_dialog.dart index 69ec52b3..4e7bb8aa 100644 --- a/lib/features/workspace/sql_query_history_dialog.dart +++ b/lib/features/workspace/sql_query_history_dialog.dart @@ -85,13 +85,7 @@ class _SqlQueryHistoryDialogContentState }); } - static final _whitespacePattern = RegExp(r'\s+'); - - static String _previewOneLine(String sql) { - final collapsed = sql.replaceAll(_whitespacePattern, ' ').trim(); - if (collapsed.length <= 96) return collapsed; - return '${collapsed.substring(0, 93)}…'; - } + static String _previewOneLine(String sql) => previewSqlOneLine(sql); static String? _formatWhen(String iso) { final t = DateTime.tryParse(iso)?.toLocal(); @@ -405,3 +399,52 @@ class _SqlQueryHistoryDialogContentState ); } } + +/// Longest one-line preview kept whole; longer text is cut to +/// [_previewKeep] characters plus an ellipsis. +const int _previewMax = 96; +const int _previewKeep = 93; + +/// Same set as the regex `\s` (what the old `replaceAll(RegExp(r'\s+'), ' ')` +/// collapsed). +bool _isPreviewWhitespace(int c) => + (c >= 0x09 && c <= 0x0D) || + c == 0x20 || + c == 0xA0 || + c == 0x1680 || + (c >= 0x2000 && c <= 0x200A) || + c == 0x2028 || + c == 0x2029 || + c == 0x202F || + c == 0x205F || + c == 0x3000 || + c == 0xFEFF; + +/// One-line, whitespace-collapsed, trimmed preview of [sql] for a list row. +/// +/// Reads only as far as it needs: a 100 KB migration is cut after about a +/// hundred characters instead of being copied whole by a regex replace (#1545). +@visibleForTesting +String previewSqlOneLine(String sql) { + final out = StringBuffer(); + var length = 0; + var pendingSpace = false; + for (var i = 0; i < sql.length; i++) { + if (_isPreviewWhitespace(sql.codeUnitAt(i))) { + pendingSpace = length > 0; + continue; + } + if (pendingSpace) { + out.writeCharCode(0x20); + length++; + pendingSpace = false; + if (length > _previewMax) break; + } + out.writeCharCode(sql.codeUnitAt(i)); + length++; + if (length > _previewMax) break; + } + final text = out.toString(); + if (length <= _previewMax) return text; + return '${text.substring(0, _previewKeep)}…'; +} diff --git a/test/features/workspace/sql_query_history_preview_test.dart b/test/features/workspace/sql_query_history_preview_test.dart new file mode 100644 index 00000000..98f2d188 --- /dev/null +++ b/test/features/workspace/sql_query_history_preview_test.dart @@ -0,0 +1,44 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:querya_desktop/features/workspace/sql_query_history_dialog.dart'; + +/// The implementation before #1545: collapse the whole text, then cut. +String _reference(String sql) { + final collapsed = sql.replaceAll(RegExp(r'\s+'), ' ').trim(); + if (collapsed.length <= 96) return collapsed; + return '${collapsed.substring(0, 93)}…'; +} + +void main() { + group('previewSqlOneLine (#1545)', () { + test('matches the old collapse-then-cut result', () { + final inputs = [ + '', + ' ', + ' \n\t ', + 'SELECT 1', + ' SELECT 1 \n FROM\tt ', + 'SELECT a, b FROM t', + 'a' * 96, + 'a' * 97, + 'a' * 96 + ' ', + 'a' * 96 + ' b', + 'a' * 95 + ' b', + 'a' * 94 + ' b c', + ('select x from t\n' * 20), + '\n\n\n' + 'x ' * 200, + 'a' * 5000, + 'word ' * 30000, + ]; + for (final sql in inputs) { + expect(previewSqlOneLine(sql), _reference(sql), + reason: 'input of length ${sql.length}'); + } + }); + + test('long text is cut to 93 characters and an ellipsis', () { + final out = previewSqlOneLine('SELECT ${'column_name, ' * 100}FROM t'); + expect(out.length, 94); + expect(out.endsWith('…'), isTrue); + }); + }); +} From 10ed868d78e344caf542dc58b2dd7d5f71a9641c Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Thu, 15 Oct 2026 07:05:01 +0300 Subject: [PATCH 2/2] test(history): compose preview inputs with interpolation (lint) --- .../workspace/sql_query_history_preview_test.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/features/workspace/sql_query_history_preview_test.dart b/test/features/workspace/sql_query_history_preview_test.dart index 98f2d188..ce57adc4 100644 --- a/test/features/workspace/sql_query_history_preview_test.dart +++ b/test/features/workspace/sql_query_history_preview_test.dart @@ -20,12 +20,12 @@ void main() { 'SELECT a, b FROM t', 'a' * 96, 'a' * 97, - 'a' * 96 + ' ', - 'a' * 96 + ' b', - 'a' * 95 + ' b', - 'a' * 94 + ' b c', + '${'a' * 96} ', + '${'a' * 96} b', + '${'a' * 95} b', + '${'a' * 94} b c', ('select x from t\n' * 20), - '\n\n\n' + 'x ' * 200, + '\n\n\n${'x ' * 200}', 'a' * 5000, 'word ' * 30000, ];