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
44 changes: 10 additions & 34 deletions lib/core/database/mongodb_connection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:mongo_dart/mongo_dart.dart';
import 'package:querya_desktop/core/database/database_error_mapper.dart';
import 'package:querya_desktop/core/database/ssh_tunnel_host.dart';
import 'package:querya_desktop/core/database/mongodb_uri.dart';
import 'package:querya_desktop/core/security/ssh_tunnel_config.dart';
import 'package:querya_desktop/core/security/ssh_tunnel_manager.dart';
import 'package:querya_desktop/core/security/ssl_certificate_support.dart';
import 'package:querya_desktop/core/storage/connection_secrets_store.dart';
import 'package:querya_desktop/core/storage/local_db.dart';

/// MongoDB connection configuration and state.
class MongoConnection {
class MongoConnection with SshTunnelHost {
MongoConnection({
required this.id,
required this.name,
Expand Down Expand Up @@ -60,11 +60,10 @@ class MongoConnection {
final String? replicaSet;
String? _connectionString;

@override
final SshTunnelConfig? sshConfig;
@override
final SshTunnelSecrets? sshSecrets;
SshTunnelHandle? _sshTunnelHandle;

SshTunnelHandle? get sshTunnelHandle => _sshTunnelHandle;

/// Handshake URI for this live session (includes auth). Not persisted; not
/// exposed via [password] / [connectionString] after [scrubCredentials].
Expand Down Expand Up @@ -234,17 +233,6 @@ class MongoConnection {

try {
if (sshConfig != null && sshConfig!.enabled) {
var sec = sshSecrets;
if (sec == null && id > 0) {
final stored =
await ConnectionSecretsStore.readSshSecretsForConnection(id);
sec = SshTunnelSecrets(
password: stored.password,
privateKey: stored.privateKey,
passphrase: stored.passphrase,
jumpPassword: stored.jumpPassword,
);
}
String targetHost = host;
int targetPort = port;
if (effectiveConnectionString != null &&
Expand All @@ -258,19 +246,15 @@ class MongoConnection {
targetPort = seed.port ?? 27017;
}
}
_sshTunnelHandle = await SshTunnelManager.instance.openTunnel(
config: sshConfig!,
secrets: sec ?? SshTunnelSecrets(),
remoteHost: targetHost,
remotePort: targetPort,
);
adoptSshTunnel(
await openSshTunnel(id: id, host: targetHost, port: targetPort));
}

final uri = await _effectiveMongoUri(
pass: effectivePassword,
connStr: effectiveConnectionString,
hostOverride: _sshTunnelHandle?.localHost,
portOverride: _sshTunnelHandle?.localPort,
hostOverride: sshTunnelHandle?.localHost,
portOverride: sshTunnelHandle?.localPort,
);
_db = await Db.create(uri);
await _db!.open();
Expand All @@ -285,10 +269,7 @@ class MongoConnection {
_isConnected = false;
_db = null;
await _cleanupTempTlsKey();
try {
await _sshTunnelHandle?.release();
} catch (_) {}
_sshTunnelHandle = null;
await releaseSshTunnel();
rethrowMappedDatabaseError(e, st, driver: DatabaseDriver.mongodb);
}
}
Expand Down Expand Up @@ -361,12 +342,7 @@ class MongoConnection {
}
}
await _cleanupTempTlsKey();
try {
await _sshTunnelHandle?.release();
} catch (e) {
debugPrint('MongoConnection.sshRelease: $e');
}
_sshTunnelHandle = null;
await releaseSshTunnel(logLabel: 'MongoConnection.sshRelease');
}

Future<void> _cleanupTempTlsKey() async {
Expand Down
61 changes: 15 additions & 46 deletions lib/core/database/mysql_connection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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/ssh_tunnel_host.dart';
import 'package:querya_desktop/core/database/querya_database_exception.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 @@ -93,7 +94,7 @@ String replaceDatabaseInMysqlConnectionString(
}

/// Pure-Dart MySQL client wrapper around [MySQLConnection] (`mysql_client`).
class MysqlConnection {
class MysqlConnection with SshTunnelHost {
MysqlConnection({
required this.id,
required this.name,
Expand Down Expand Up @@ -137,11 +138,10 @@ class MysqlConnection {
final bool useSSL;
String? _connectionString;

@override
final SshTunnelConfig? sshConfig;
@override
final SshTunnelSecrets? sshSecrets;
SshTunnelHandle? _sshTunnelHandle;

SshTunnelHandle? get sshTunnelHandle => _sshTunnelHandle;

String? get password => _password;
String? get connectionString => _connectionString;
Expand Down Expand Up @@ -234,27 +234,9 @@ class MysqlConnection {
}

try {
if (sshConfig != null && sshConfig!.enabled) {
var sec = sshSecrets;
if (sec == null && id > 0) {
final stored =
await ConnectionSecretsStore.readSshSecretsForConnection(id);
sec = SshTunnelSecrets(
password: stored.password,
privateKey: stored.privateKey,
passphrase: stored.passphrase,
jumpPassword: stored.jumpPassword,
);
}
openedHandle = await SshTunnelManager.instance.openTunnel(
config: sshConfig!,
secrets: sec ?? SshTunnelSecrets(),
remoteHost: host,
remotePort: port,
);
if (generation != _generation) throw _ConnectSuperseded();
_sshTunnelHandle = openedHandle;
}
openedHandle = await openSshTunnel(id: id, host: host, port: port);
if (generation != _generation) throw _ConnectSuperseded();
adoptSshTunnel(openedHandle);

final user = username ?? '';
final pass = effectivePassword ?? '';
Expand All @@ -274,8 +256,8 @@ class MysqlConnection {
}
validateMysqlSslMode(sslMode, sslPaths);
final securityContext = buildSecurityContext(sslPaths);
final effectiveHost = _sshTunnelHandle?.localHost ?? parsed.host;
final effectivePort = _sshTunnelHandle?.localPort ?? parsed.port;
final effectiveHost = sshTunnelHandle?.localHost ?? parsed.host;
final effectivePort = sshTunnelHandle?.localPort ?? parsed.port;
opened = await MySQLConnection.createConnection(
host: effectiveHost,
port: effectivePort,
Expand All @@ -293,8 +275,8 @@ class MysqlConnection {
final sslPaths =
extractSslCertificatePathsFromString(effectiveConnectionString);
final securityContext = buildSecurityContext(sslPaths);
final effectiveHost = _sshTunnelHandle?.localHost ?? host;
final effectivePort = _sshTunnelHandle?.localPort ?? port;
final effectiveHost = sshTunnelHandle?.localHost ?? host;
final effectivePort = sshTunnelHandle?.localPort ?? port;
opened = await MySQLConnection.createConnection(
host: effectiveHost,
port: effectivePort,
Expand Down Expand Up @@ -327,15 +309,12 @@ class MysqlConnection {
try {
await openedHandle?.release();
} catch (_) {}
if (identical(_sshTunnelHandle, openedHandle)) _sshTunnelHandle = null;
forgetSshTunnel(openedHandle);
throw StateError('The connection was closed while it was being opened');
}
_isConnected = false;
_conn = null;
try {
await _sshTunnelHandle?.release();
} catch (_) {}
_sshTunnelHandle = null;
await releaseSshTunnel();
rethrowMappedDatabaseError(e, st, driver: DatabaseDriver.mysql);
}
}
Expand Down Expand Up @@ -432,12 +411,7 @@ class MysqlConnection {
} catch (e) {
debugPrint('MysqlConnection.disconnect: $e');
}
try {
await _sshTunnelHandle?.release();
} catch (e) {
debugPrint('MysqlConnection.sshRelease: $e');
}
_sshTunnelHandle = null;
await releaseSshTunnel(logLabel: 'MysqlConnection.sshRelease');
}

/// Best-effort close. The `mysql_client` driver may not allow graceful [close]
Expand All @@ -459,12 +433,7 @@ class MysqlConnection {
} catch (e) {
debugPrint('MysqlConnection.forceClose: $e');
}
try {
await _sshTunnelHandle?.release();
} catch (e) {
debugPrint('MysqlConnection.forceClose ssh: $e');
}
_sshTunnelHandle = null;
await releaseSshTunnel(logLabel: 'MysqlConnection.forceClose ssh');
}

/// Session hint for Table Browser / tree (`MysqlSessionMode.readOnly`).
Expand Down
58 changes: 13 additions & 45 deletions lib/core/database/postgres_connection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import 'package:flutter/foundation.dart';
import 'package:postgres/postgres.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/ssh_tunnel_host.dart';
import 'package:querya_desktop/core/database/querya_database_exception.dart';
import 'package:querya_desktop/core/security/ssh_tunnel_config.dart';
import 'package:querya_desktop/core/security/ssh_tunnel_manager.dart';
import 'package:querya_desktop/core/storage/connection_secrets_store.dart';
import 'package:querya_desktop/core/storage/local_db.dart';

Expand Down Expand Up @@ -69,7 +69,7 @@ int? postgresReltuplesEstimate(Object? value) {
}

/// PostgreSQL connection using the pure-Dart `postgres` package.
class PostgresConnection {
class PostgresConnection with SshTunnelHost {
PostgresConnection({
required this.id,
required this.name,
Expand Down Expand Up @@ -135,11 +135,10 @@ class PostgresConnection {
final String? sslCert;
final String? sslKey;

@override
final SshTunnelConfig? sshConfig;
@override
final SshTunnelSecrets? sshSecrets;
SshTunnelHandle? _sshTunnelHandle;

SshTunnelHandle? get sshTunnelHandle => _sshTunnelHandle;

String? get password => _password;
String? get connectionString => _connectionString;
Expand Down Expand Up @@ -262,25 +261,7 @@ class PostgresConnection {
}

try {
if (sshConfig != null && sshConfig!.enabled) {
var sec = sshSecrets;
if (sec == null && id > 0) {
final stored =
await ConnectionSecretsStore.readSshSecretsForConnection(id);
sec = SshTunnelSecrets(
password: stored.password,
privateKey: stored.privateKey,
passphrase: stored.passphrase,
jumpPassword: stored.jumpPassword,
);
}
_sshTunnelHandle = await SshTunnelManager.instance.openTunnel(
config: sshConfig!,
secrets: sec ?? SshTunnelSecrets(),
remoteHost: host,
remotePort: port,
);
}
adoptSshTunnel(await openSshTunnel(id: id, host: host, port: port));

if (_usesConnectionString(effectiveConnectionString)) {
// Pool passes target catalog via [database]; URI alone would always open
Expand All @@ -300,10 +281,10 @@ class PostgresConnection {
(sslKey != null && sslKey!.trim().isNotEmpty),
hasRootCert: sslRootCert != null && sslRootCert!.trim().isNotEmpty,
);
final endpoint = _sshTunnelHandle != null
final endpoint = sshTunnelHandle != null
? Endpoint(
host: _sshTunnelHandle!.localHost,
port: _sshTunnelHandle!.localPort,
host: sshTunnelHandle!.localHost,
port: sshTunnelHandle!.localPort,
database: parsed.endpoints.first.database,
username: parsed.endpoints.first.username,
password: parsed.endpoints.first.password,
Expand All @@ -330,8 +311,8 @@ class PostgresConnection {
_conn = await Connection.open(
_buildEndpoint(
pass: effectivePassword,
hostOverride: _sshTunnelHandle?.localHost,
portOverride: _sshTunnelHandle?.localPort,
hostOverride: sshTunnelHandle?.localHost,
portOverride: sshTunnelHandle?.localPort,
),
settings: _buildSettings(),
);
Expand All @@ -342,10 +323,7 @@ class PostgresConnection {
} catch (e, st) {
_isConnected = false;
_conn = null;
try {
await _sshTunnelHandle?.release();
} catch (_) {}
_sshTunnelHandle = null;
await releaseSshTunnel();
Error.throwWithStackTrace(
PostgresConnectionException(
'Failed to connect to PostgreSQL${name.isNotEmpty ? ' ($name)' : ''}: '
Expand All @@ -368,12 +346,7 @@ class PostgresConnection {
} catch (e) {
debugPrint('PostgresConnection.disconnect: $e');
}
try {
await _sshTunnelHandle?.release();
} catch (e) {
debugPrint('PostgresConnection.sshRelease: $e');
}
_sshTunnelHandle = null;
await releaseSshTunnel(logLabel: 'PostgresConnection.sshRelease');
}

/// Drops the TCP session immediately (kills pending client I/O). Used when
Expand All @@ -388,12 +361,7 @@ class PostgresConnection {
} catch (e) {
debugPrint('PostgresConnection.forceClose: $e');
}
try {
await _sshTunnelHandle?.release();
} catch (e) {
debugPrint('PostgresConnection.forceClose ssh: $e');
}
_sshTunnelHandle = null;
await releaseSshTunnel(logLabel: 'PostgresConnection.forceClose ssh');
}

/// Session-level default for transactions (browse vs SQL editor).
Expand Down
Loading
Loading