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
33 changes: 33 additions & 0 deletions lib/core/database/result_row_string_convert.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/// Converts SQL result cells to display strings without a second isolate copy.
///
/// Prefer this over [compute] for large matrices: shipping `List<List<Object?>>`
/// across isolates often costs more than `toString()` itself and roughly
/// doubles peak memory. Yielding every [yieldEvery] rows keeps the UI isolate
/// responsive for 10k+ row caps.
library;

const int kResultStringConvertYieldEvery = 250;

/// Maps null cells to `'NULL'` and others via [Object.toString].
String resultCellToDisplayString(Object? value) =>
value == null ? 'NULL' : value.toString();

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

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),
]);
if (yieldEvery > 0 && (i + 1) % yieldEvery == 0) {
await Future<void>.delayed(Duration.zero);
}
}
return out;
}
21 changes: 11 additions & 10 deletions lib/features/mysql/mysql_sql_workspace.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import 'dart:async';
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart' as material;
import 'package:flutter/services.dart' show LogicalKeyboardKey;
import 'package:file_selector/file_selector.dart';
import 'package:querya_desktop/core/actions/sql_editor_actions.dart';
import 'package:querya_desktop/core/actions/sql_editor_command_bridge.dart';
import 'package:querya_desktop/core/database/mysql_service.dart';
import 'package:querya_desktop/core/database/result_row_string_convert.dart';
import 'package:querya_desktop/core/layout/vertical_split_pane.dart';
import 'package:querya_desktop/core/storage/app_settings.dart';
import 'package:querya_desktop/core/storage/local_db.dart';
Expand All @@ -18,7 +18,6 @@ import 'package:querya_desktop/features/main_screen/query_editor_tab.dart';
import 'package:querya_desktop/features/main_screen/results_tab.dart';
import 'package:querya_desktop/features/main_screen/sql_editor_chrome.dart';
import 'package:querya_desktop/features/main_screen/sql_query_history_dialog.dart';
import 'package:querya_desktop/features/mysql/mysql_result_utils.dart';
import 'package:querya_desktop/shared/widgets/widgets.dart';

/// Ad-hoc SQL editor + results for MySQL / MariaDB.
Expand Down Expand Up @@ -192,7 +191,8 @@ class _MysqlSqlWorkspaceState extends material.State<MysqlSqlWorkspace> {
cols.add(c.name.isNotEmpty ? c.name : 'col_${cols.length}');
}

final rawRows = <List<Object?>>[];
// Convert while streaming — no Object? matrix + isolate double-copy (#421).
final outRows = <List<String>>[];
var n = 0;
final cap = _resultMaxRows;
var truncated = false;
Expand All @@ -201,17 +201,18 @@ class _MysqlSqlWorkspaceState extends material.State<MysqlSqlWorkspace> {
truncated = true;
break;
}
rawRows.add(
List.generate(row.numOfColumns, (i) => row.colAt(i)),
outRows.add(
List.generate(
row.numOfColumns,
(i) => resultCellToDisplayString(row.colAt(i)),
),
);
n++;
if (n % kResultStringConvertYieldEvery == 0) {
await Future<void>.delayed(Duration.zero);
}
}

final job = MysqlResultConvertJob(rowValues: rawRows);
final outRows = rawRows.length > 500
? await compute(convertMysqlResultRowsToStrings, job)
: convertMysqlResultRowsToStrings(job);

int? affected;
if (cols.isEmpty && outRows.isEmpty) {
affected = _affectedInt(rs.affectedRows);
Expand Down
9 changes: 3 additions & 6 deletions lib/features/postgresql/postgres_sql_workspace.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import 'dart:async';
import 'dart:io';

import 'package:flutter/foundation.dart' show compute;
import 'package:flutter/material.dart' as material;
import 'package:flutter/services.dart' show LogicalKeyboardKey;
import 'package:file_selector/file_selector.dart';
import 'package:querya_desktop/features/postgresql/postgres_result_utils.dart';
import 'package:querya_desktop/core/actions/sql_editor_actions.dart';
import 'package:querya_desktop/core/actions/sql_editor_command_bridge.dart';
import 'package:postgres/postgres.dart' as pg;
import 'package:querya_desktop/core/database/postgres_service.dart';
import 'package:querya_desktop/core/database/postgres_sql.dart';
import 'package:querya_desktop/core/database/result_row_string_convert.dart';
import 'package:querya_desktop/core/layout/vertical_split_pane.dart';
import 'package:querya_desktop/core/storage/app_settings.dart';
import 'package:querya_desktop/core/storage/local_db.dart';
Expand Down Expand Up @@ -359,10 +358,8 @@ class _PostgresSqlWorkspaceState extends material.State<PostgresSqlWorkspace> {
n++;
}

final job = PostgresResultConvertJob(rowValues: rawRows);
final outRows = rawRows.length > 500
? await compute(convertPostgresResultRowsToStrings, job)
: convertPostgresResultRowsToStrings(job);
// Yielding convert avoids isolate double-copy of the matrix (#421).
final outRows = await convertResultRowsToStringsYielding(rawRows);

setState(() {
_columns = cols;
Expand Down
20 changes: 11 additions & 9 deletions lib/features/postgresql/postgres_table_view.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:flutter/foundation.dart' show compute;
import 'package:flutter/material.dart' as material;
import 'package:querya_desktop/core/database/postgres_connection.dart';
import 'package:querya_desktop/core/database/postgres_service.dart';
import 'package:querya_desktop/core/database/result_row_string_convert.dart';
import 'package:querya_desktop/core/storage/local_db.dart';
import 'package:querya_desktop/features/postgresql/postgres_sql_editor_dialog.dart';
import 'package:querya_desktop/features/postgresql/postgres_table_privileges_dialog.dart';
Expand Down Expand Up @@ -187,11 +187,12 @@ class _PostgresTableViewState extends material.State<PostgresTableView> {
(i) => result.schema.columns[i].columnName ?? 'col_$i',
);

final rawRows = result.map((row) {
return List<dynamic>.generate(row.length, (i) => row[i]);
}).toList();
final rawRows = <List<Object?>>[
for (final row in result)
List<Object?>.generate(row.length, (i) => row[i]),
];

final stringRows = await compute(convertResultRowsToStrings, rawRows);
final stringRows = await convertResultRowsToStringsYielding(rawRows);

if (!mounted) return;
setState(() {
Expand Down Expand Up @@ -235,11 +236,12 @@ class _PostgresTableViewState extends material.State<PostgresTableView> {
(i) => result.schema.columns[i].columnName ?? 'col_$i',
);

final rawRows = result.map((row) {
return List<dynamic>.generate(row.length, (i) => row[i]);
}).toList();
final rawRows = <List<Object?>>[
for (final row in result)
List<Object?>.generate(row.length, (i) => row[i]),
];

final stringRows = await compute(convertResultRowsToStrings, rawRows);
final stringRows = await convertResultRowsToStringsYielding(rawRows);

if (!mounted) return;
setState(() {
Expand Down
11 changes: 4 additions & 7 deletions lib/features/sqlite/sqlite_sql_workspace.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart' show compute;
import 'package:flutter/material.dart' as material;
import 'package:flutter/services.dart' show LogicalKeyboardKey;
import 'package:file_selector/file_selector.dart';
import 'package:querya_desktop/features/sqlite/sqlite_result_utils.dart';
import 'package:querya_desktop/core/actions/sql_editor_actions.dart';
import 'package:querya_desktop/core/actions/sql_editor_command_bridge.dart';
import 'package:querya_desktop/core/database/sql_limit.dart';
import 'package:querya_desktop/core/database/result_row_string_convert.dart';
import 'package:querya_desktop/core/database/sqlite_service.dart';
import 'package:querya_desktop/core/database/sql_limit.dart';
import 'package:querya_desktop/core/layout/vertical_split_pane.dart';
import 'package:querya_desktop/core/storage/app_settings.dart';
import 'package:querya_desktop/core/storage/local_db.dart';
Expand Down Expand Up @@ -178,10 +177,8 @@ class _SqliteSqlWorkspaceState extends material.State<SqliteSqlWorkspace> {
return cols.map((col) => row[col]).toList();
}).toList();

final job = SqliteResultConvertJob(rowValues: rawRows);
final outRows = rawRows.length > 500
? await compute(convertSqliteResultRowsToStrings, job)
: convertSqliteResultRowsToStrings(job);
// Yielding convert avoids isolate double-copy of the matrix (#421).
final outRows = await convertResultRowsToStringsYielding(rawRows);

setState(() {
_columns = cols;
Expand Down
25 changes: 25 additions & 0 deletions test/core/database/result_row_string_convert_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:querya_desktop/core/database/result_row_string_convert.dart';

void main() {
group('convertResultRowsToStringsYielding', () {
test('maps null to NULL and yields without isolate', () async {
final rows = <List<Object?>>[
[1, null, 'a'],
[2, 'x', null],
];
final out = await convertResultRowsToStringsYielding(
rows,
yieldEvery: 1,
);
expect(out, [
['1', 'NULL', 'a'],
['2', 'x', 'NULL'],
]);
});

test('empty input returns empty', () async {
expect(await convertResultRowsToStringsYielding(const []), isEmpty);
});
});
}
Loading