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
11 changes: 10 additions & 1 deletion lib/core/database/database_error_mapper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:querya_desktop/core/database/postgres_connection.dart';
import 'package:querya_desktop/core/database/querya_database_exception.dart';
import 'package:querya_desktop/core/database/redis_connection.dart';
import 'package:querya_desktop/core/database/sqlite_connection.dart';
import 'package:querya_desktop/core/database/statement_queue.dart';

/// Which engine produced an error; only used to tailor the wording.
enum DatabaseDriver { postgres, mysql, sqlite, mongodb, redis, extension }
Expand All @@ -27,7 +28,15 @@ QueryaDatabaseException mapDatabaseError(

QueryaDatabaseException? mapped;

if (_hasAny(lower, const [
if (error is StatementWaitTimeoutException) {
mapped = ConnectionTimeoutException(
error.message,
remediationHint: 'Wait for the running statement to finish, or cancel '
'it, then try again',
originalError: error,
stackTrace: stackTrace,
);
} else if (_hasAny(lower, const [
'lock wait timeout',
'lock timeout',
'database is locked',
Expand Down
18 changes: 7 additions & 11 deletions lib/core/database/mysql_connection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:mysql_client/mysql_client.dart';
import 'package:querya_desktop/core/database/statement_queue.dart';
import 'package:querya_desktop/core/database/database_error_mapper.dart';
import 'package:querya_desktop/core/database/mysql_result_cells.dart';
import 'package:querya_desktop/core/database/table_schema_meta.dart';
Expand Down Expand Up @@ -147,10 +148,11 @@ class MysqlConnection {
MySQLConnection? _conn;
Future<void>? _connecting;

/// Statements on this session run one at a time, here. The client would
/// otherwise poll for the previous statement and give up after 10 s, which
/// closed the session for every caller.
Future<void> _statementQueue = Future<void>.value();
/// Statements on this session run one at a time, here and not in the
/// driver: a statement's timeout starts when it starts, and waiting for the
/// one before it has its own limit (#1216).
@visibleForTesting
final statementQueue = StatementQueue();

/// Test seam: replaces the driver call of [execute].
@visibleForTesting
Expand All @@ -161,12 +163,6 @@ class MysqlConnection {
Duration? timeout,
)? runStatementForTest;

Future<T> _serialized<T>(Future<T> Function() run) {
final previous = _statementQueue;
final turn = Completer<void>();
_statementQueue = turn.future;
return previous.then((_) => run()).whenComplete(() => turn.complete());
}
bool _isConnected = false;
bool _inTransaction = false;

Expand Down Expand Up @@ -485,7 +481,7 @@ class MysqlConnection {
bool iterable,
Duration? timeout,
) {
return _serialized(() async {
return statementQueue.run(() async {
final seam = runStatementForTest;
final c = _conn;
if (seam == null && (!isConnected || c == null)) {
Expand Down
Loading
Loading