Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
caf0c4f
feat(mongodb): pooled ensureConnected, explorer refresh, drop DB confirm
ZhuchkaTriplesix Mar 21, 2026
0e81709
test(connections): expanded folder layout; FoldersStorage.reload
ZhuchkaTriplesix Mar 21, 2026
119350f
chore: ignore flutter_*.log crash reports
ZhuchkaTriplesix Mar 21, 2026
1294da0
feat(mongodb): stats refresh controls + batched collection collStats
ZhuchkaTriplesix Mar 21, 2026
02cf640
fix(test): pump until ConnectionsPanel async load in expanded folder …
ZhuchkaTriplesix Mar 21, 2026
9b0e0c0
fix(ui): animate backdrop blur immediately on modal appear
ZhuchkaTriplesix Mar 21, 2026
0b49b62
fix(ui): modal backdrop blur without delayed pop-in
ZhuchkaTriplesix Mar 21, 2026
2031b5f
fix(postgres): SQL stmt timeout dropdown — No limit, 10s, 2 min label
ZhuchkaTriplesix Mar 21, 2026
c67710a
fix(test): avoid findsOneWidget hang on ConnectionsPanel expanded test
ZhuchkaTriplesix Mar 21, 2026
49a204a
try fi
ZhuchkaTriplesix Mar 21, 2026
0b2f0f1
fix
ZhuchkaTriplesix Mar 21, 2026
8613326
fix(postgres): use pooled database name when opening connection URIs
ZhuchkaTriplesix Mar 22, 2026
9e35c9b
fix(connections): stable PG tree keys + do not auto-expand new folders
ZhuchkaTriplesix Mar 22, 2026
094ab44
fix(storage): coerce SQLite integer columns in ConnectionRow.fromMap
ZhuchkaTriplesix Mar 22, 2026
ec6f2c0
feat(connections): add ConnectionsPanelState.reloadConnectionsFromDb
ZhuchkaTriplesix Mar 22, 2026
cd32f85
test(connections): await panel DB load via runAsync in expanded-folde…
ZhuchkaTriplesix Mar 22, 2026
b85e8cc
feat(connections): serialize _loadData + optional skipInitialDbLoadFo…
ZhuchkaTriplesix Mar 22, 2026
229ba99
test(connections): stabilize expanded-folder layout test
ZhuchkaTriplesix Mar 22, 2026
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
2 changes: 1 addition & 1 deletion .flutter-plugins-dependencies
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"path_provider_foundation","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_foundation-2.6.0\\\\","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\sqflite_darwin-2.4.2\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"path_provider_android","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_android-2.2.22\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_android","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\sqflite_android-2.4.2+2\\\\","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"bitsdojo_window_macos","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\bitsdojo_window_macos-0.1.4\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_foundation-2.6.0\\\\","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\sqflite_darwin-2.4.2\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"bitsdojo_window_linux","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\bitsdojo_window_linux-0.1.4\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_linux-2.2.1\\\\","native_build":false,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"bitsdojo_window_windows","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\bitsdojo_window_windows-0.1.6\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_windows-2.3.0\\\\","native_build":false,"dependencies":[],"dev_dependency":false}],"web":[]},"dependencyGraph":[{"name":"bitsdojo_window","dependencies":["bitsdojo_window_windows","bitsdojo_window_macos","bitsdojo_window_linux"]},{"name":"bitsdojo_window_linux","dependencies":[]},{"name":"bitsdojo_window_macos","dependencies":[]},{"name":"bitsdojo_window_windows","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"sqflite","dependencies":["sqflite_android","sqflite_darwin"]},{"name":"sqflite_android","dependencies":[]},{"name":"sqflite_darwin","dependencies":[]}],"date_created":"2026-03-21 12:40:42.216584","version":"3.41.5","swift_package_manager_enabled":{"ios":false,"macos":false}}
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"path_provider_foundation","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_foundation-2.6.0\\\\","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\sqflite_darwin-2.4.2\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"path_provider_android","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_android-2.2.22\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_android","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\sqflite_android-2.4.2+2\\\\","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"bitsdojo_window_macos","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\bitsdojo_window_macos-0.1.4\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_foundation-2.6.0\\\\","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\sqflite_darwin-2.4.2\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"bitsdojo_window_linux","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\bitsdojo_window_linux-0.1.4\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_linux-2.2.1\\\\","native_build":false,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"bitsdojo_window_windows","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\bitsdojo_window_windows-0.1.6\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"C:\\\\Users\\\\junte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_windows-2.3.0\\\\","native_build":false,"dependencies":[],"dev_dependency":false}],"web":[]},"dependencyGraph":[{"name":"bitsdojo_window","dependencies":["bitsdojo_window_windows","bitsdojo_window_macos","bitsdojo_window_linux"]},{"name":"bitsdojo_window_linux","dependencies":[]},{"name":"bitsdojo_window_macos","dependencies":[]},{"name":"bitsdojo_window_windows","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"sqflite","dependencies":["sqflite_android","sqflite_darwin"]},{"name":"sqflite_android","dependencies":[]},{"name":"sqflite_darwin","dependencies":[]}],"date_created":"2026-03-21 20:27:26.235937","version":"3.41.5","swift_package_manager_enabled":{"ios":false,"macos":false}}
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ Thumbs.db
*.so
*.dll
*.exe
flutter_01.log
flutter_*.log
28 changes: 28 additions & 0 deletions lib/core/database/mongodb_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,34 @@ class MongoService {

final Map<int, MongoConnection> _connections = {};

/// Returns a connected [MongoConnection] for [row], reusing an existing
/// pooled connection when it is already open for the same connection id.
///
/// Use this from the sidebar and explorer so they share one socket per saved
/// connection instead of opening parallel clients.
Future<MongoConnection> ensureConnected(ConnectionRow row) async {
if (row.type != 'mongodb') {
throw ArgumentError('Connection type must be mongodb');
}
final id = row.id ?? 0;
final existing = _connections[id];
if (existing != null && existing.isConnected) {
return existing;
}
final connection = createConnection(row);
await connection.connect();
return connection;
}

/// Disconnects the pooled connection for [id], if any (e.g. when the user
/// removes the connection from the browser).
Future<void> disconnectByConnectionId(int id) async {
final c = _connections[id];
if (c != null) {
await disconnect(c);
}
}

/// Creates (or replaces) a [MongoConnection] for the given [ConnectionRow].
/// If a connection with the same ID already exists it is disconnected first.
MongoConnection createConnection(ConnectionRow row) {
Expand Down
10 changes: 9 additions & 1 deletion lib/core/database/postgres_connection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,15 @@ class PostgresConnection {
if (_isConnected && _conn != null) return;
try {
if (_usesConnectionString) {
final parsed = parseConnectionString(connectionString!.trim());
// Pool passes target catalog via [database]; URI alone would always open
// the DB embedded in the string — every tree branch then queried the
// same database (duplicate tables under finance / logistics, etc.).
final dbName = database ?? 'postgres';
final uriForOpen = replaceDatabaseInConnectionString(
connectionString!.trim(),
dbName,
);
final parsed = parseConnectionString(uriForOpen);
final sslMode =
parsed.sslMode ?? (useSSL ? SslMode.require : SslMode.disable);
_conn = await Connection.open(
Expand Down
9 changes: 9 additions & 0 deletions lib/core/storage/folders_storage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class FoldersStorage {

List<String> _folders = [];
bool _loaded = false;
bool _migrationChecked = false;

List<String> get folders => List.unmodifiable(_folders);

Expand All @@ -32,6 +33,8 @@ class FoldersStorage {
}

Future<void> _migrateFromLegacyIfNeeded() async {
if (_migrationChecked) return;
_migrationChecked = true;
try {
final dir = await getApplicationSupportDirectory();
final sub = Directory('${dir.path}/querya_desktop');
Expand Down Expand Up @@ -75,4 +78,10 @@ class FoldersStorage {
await LocalDb.instance.removeFolder(name);
_folders = await LocalDb.instance.getFolders();
}

/// Reloads folder names from [LocalDb] (e.g. after external seeding in tests).
Future<void> reload() async {
_loaded = false;
await load();
}
}
37 changes: 24 additions & 13 deletions lib/core/storage/local_db.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,21 @@ import 'package:sqflite_common_ffi/sqflite_ffi.dart';
const _dbName = 'querya.db';
const _dbVersion = 4;

int? _sqliteInt(Object? v) {
if (v == null) return null;
if (v is int) return v;
if (v is num) return v.toInt();
return int.tryParse(v.toString());
}

/// Local SQLite database for folders and connections.
/// File: [applicationSupport]/querya_desktop/querya.db
class LocalDb {
LocalDb._();
static final LocalDb instance = LocalDb._();

Database? _db;
String? _cachedDbPath;

static Future<void> initFfi() async {
if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
Expand All @@ -24,12 +32,14 @@ class LocalDb {
Future<Database> _open() async {
if (_db != null && _db!.isOpen) return _db!;
await initFfi();
final dir = await getApplicationSupportDirectory();
final sub = Directory(p.join(dir.path, 'querya_desktop'));
if (!await sub.exists()) await sub.create(recursive: true);
final path = p.join(sub.path, _dbName);
if (_cachedDbPath == null) {
final dir = await getApplicationSupportDirectory();
final sub = Directory(p.join(dir.path, 'querya_desktop'));
if (!await sub.exists()) await sub.create(recursive: true);
_cachedDbPath = p.join(sub.path, _dbName);
}
_db = await databaseFactoryFfi.openDatabase(
path,
_cachedDbPath!,
options: OpenDatabaseOptions(
version: _dbVersion,
onCreate: _onCreate,
Expand Down Expand Up @@ -173,7 +183,7 @@ class LocalDb {
final db = await _open();
final rows = await db.query('folders', columns: ['id'], where: 'name = ?', whereArgs: [name]);
if (rows.isEmpty) return null;
return rows.first['id'] as int?;
return _sqliteInt(rows.first['id']);
}

Future<List<ConnectionRow>> getConnections() async {
Expand All @@ -182,9 +192,10 @@ class LocalDb {
return rows.map(ConnectionRow.fromMap).toList();
}

Future<void> addConnection(ConnectionRow row) async {
/// Inserts a row and returns the SQLite row id.
Future<int> addConnection(ConnectionRow row) async {
final db = await _open();
await db.insert('connections', row.toMap());
return db.insert('connections', row.toMap());
}

Future<void> removeConnection(int id) async {
Expand Down Expand Up @@ -248,19 +259,19 @@ class ConnectionRow {
};

static ConnectionRow fromMap(Map<String, Object?> m) => ConnectionRow(
id: m['id'] as int?,
id: _sqliteInt(m['id']),
type: m['type'] as String,
name: m['name'] as String,
host: m['host'] as String?,
port: m['port'] as int?,
port: _sqliteInt(m['port']),
username: m['username'] as String?,
password: m['password'] as String?,
databaseName: m['database_name'] as String?,
authSource: m['auth_source'] as String?,
useSSL: (m['use_ssl'] as int?) == 1,
useSSL: _sqliteInt(m['use_ssl']) == 1,
connectionString: m['connection_string'] as String?,
folderId: m['folder_id'] as int?,
sortOrder: m['sort_order'] as int? ?? 0,
folderId: _sqliteInt(m['folder_id']),
sortOrder: _sqliteInt(m['sort_order']) ?? 0,
createdAt: m['created_at'] as String,
);
}
Loading
Loading