Skip to content
Merged

Dev #19

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
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-10 05:45:19.165964","version":"3.41.4","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-14 09:50:21.245489","version":"3.41.4","swift_package_manager_enabled":{"ios":false,"macos":false}}
88 changes: 88 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Сборка релизов и публикация в GitHub Releases.
# Запуск: создайте и запушьте тег, например v0.1.0:
# git tag v0.1.0
# git push origin v0.1.0

name: Release

on:
push:
tags:
- 'v*' # v0.1.0, v1.0.0, etc.

jobs:
build-windows:
name: Build Windows
runs-on: windows-latest
steps:
- uses: actions/checkout@v4

- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.38.5'
channel: 'stable'
cache: true

- name: Get dependencies
run: flutter pub get

- name: Build Windows (release)
run: flutter build windows --release

- name: Zip Windows artifact
run: |
$tag = "${{ github.ref_name }}"
$dir = "build\windows\x64\runner\Release"
Compress-Archive -Path "$dir\*" -DestinationPath "Querya-Desktop-$tag-windows.zip"

- name: Create Release and upload Windows
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: Release ${{ github.ref_name }}
generate_release_notes: true
files: Querya-Desktop-${{ github.ref_name }}-windows.zip
allow_updates: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

build-linux:
name: Build Linux
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Linux dependencies
run: |
sudo apt-get update
sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev

- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.38.5'
channel: 'stable'
cache: true

- name: Get dependencies
run: flutter pub get

- name: Build Linux (release)
run: flutter build linux --release

- name: Zip Linux artifact
run: |
cd build/linux/x64/release/bundle
zip -r ../../../../../../Querya-Desktop-${{ github.ref_name }}-linux.zip .

- name: Upload to Release (Linux)
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: Release ${{ github.ref_name }}
generate_release_notes: true
files: Querya-Desktop-${{ github.ref_name }}-linux.zip
allow_updates: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
283 changes: 283 additions & 0 deletions lib/core/database/postgres_connection.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
import 'package:postgres/postgres.dart';

/// PostgreSQL connection using the pure-Dart `postgres` package.
class PostgresConnection {
PostgresConnection({
required this.id,
required this.name,
required this.host,
this.port = 5432,
this.username,
this.password,
this.database,
this.useSSL = false,
this.connectionString,
});

final int id;
final String name;
final String host;
final int port;
final String? username;
final String? password;
final String? database;
final bool useSSL;
final String? connectionString;

Connection? _conn;
bool _isConnected = false;

bool get isConnected => _isConnected && _conn != null;

Endpoint _buildEndpoint() {
return Endpoint(
host: host,
port: port,
database: database ?? 'postgres',
username: username,
password: password,
);
}

ConnectionSettings _buildSettings() {
return ConnectionSettings(
sslMode: useSSL ? SslMode.require : SslMode.disable,
connectTimeout: const Duration(seconds: 10),
queryTimeout: const Duration(seconds: 30),
);
}

Future<void> connect() async {
if (_isConnected && _conn != null) return;
try {
_conn = await Connection.open(
_buildEndpoint(),
settings: _buildSettings(),
);
_isConnected = true;
} catch (e) {
_isConnected = false;
_conn = null;
rethrow;
}
}

Future<void> disconnect() async {
_isConnected = false;
final c = _conn;
_conn = null;
try {
await c?.close();
} catch (_) {}
}

Future<bool> testConnection() async {
try {
await connect();
if (_conn != null) {
await _conn!.execute('SELECT 1');
return true;
}
return false;
} catch (_) {
return false;
} finally {
await disconnect();
}
}

Future<Result> execute(String sql) async {
if (!isConnected || _conn == null) {
throw StateError('Not connected to PostgreSQL');
}
return _conn!.execute(sql);
}

Future<List<String>> listDatabases() async {
if (!isConnected || _conn == null) {
throw StateError('Not connected to PostgreSQL');
}
final result = await _conn!.execute(
"SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY datname",
);
return result.map((row) => row[0] as String).toList();
}

Future<List<String>> listSchemas() async {
if (!isConnected || _conn == null) {
throw StateError('Not connected to PostgreSQL');
}
final result = await _conn!.execute(
"SELECT schema_name FROM information_schema.schemata "
"WHERE schema_name NOT IN ('pg_catalog', 'information_schema', 'pg_toast') "
"ORDER BY schema_name",
);
return result.map((row) => row[0] as String).toList();
}

Future<List<String>> listTables({String schema = 'public'}) async {
if (!isConnected || _conn == null) {
throw StateError('Not connected to PostgreSQL');
}
final result = await _conn!.execute(
Sql.named(
"SELECT table_name FROM information_schema.tables "
"WHERE table_schema = @schema AND table_type = 'BASE TABLE' "
"ORDER BY table_name",
),
parameters: {'schema': schema},
);
return result.map((row) => row[0] as String).toList();
}

Future<List<String>> listViews({String schema = 'public'}) async {
if (!isConnected || _conn == null) {
throw StateError('Not connected to PostgreSQL');
}
final result = await _conn!.execute(
Sql.named(
"SELECT table_name FROM information_schema.views "
"WHERE table_schema = @schema "
"ORDER BY table_name",
),
parameters: {'schema': schema},
);
return result.map((row) => row[0] as String).toList();
}

Future<List<String>> listFunctions({String schema = 'public'}) async {
if (!isConnected || _conn == null) {
throw StateError('Not connected to PostgreSQL');
}
final result = await _conn!.execute(
Sql.named(
"SELECT routine_name FROM information_schema.routines "
"WHERE routine_schema = @schema AND routine_type = 'FUNCTION' "
"ORDER BY routine_name",
),
parameters: {'schema': schema},
);
return result.map((row) => row[0] as String).toList();
}

Future<List<String>> listSequences({String schema = 'public'}) async {
if (!isConnected || _conn == null) {
throw StateError('Not connected to PostgreSQL');
}
final result = await _conn!.execute(
Sql.named(
"SELECT sequence_name FROM information_schema.sequences "
"WHERE sequence_schema = @schema "
"ORDER BY sequence_name",
),
parameters: {'schema': schema},
);
return result.map((row) => row[0] as String).toList();
}

Future<String> serverVersion() async {
if (!isConnected || _conn == null) {
throw StateError('Not connected to PostgreSQL');
}
final result = await _conn!.execute('SELECT version()');
return result.first[0] as String;
}

/// Fetches key server statistics for the stats dashboard.
Future<Map<String, dynamic>> serverStats() async {
if (!isConnected || _conn == null) {
throw StateError('Not connected to PostgreSQL');
}
final stats = <String, dynamic>{};

final ver = await _conn!.execute('SELECT version()');
stats['version'] = ver.first[0] as String;

final settings = await _conn!.execute(
"SELECT name, setting FROM pg_settings "
"WHERE name IN ('max_connections','shared_buffers','work_mem',"
"'effective_cache_size','server_version','data_directory',"
"'listen_addresses','port','server_encoding','timezone')",
);
final settingsMap = <String, String>{};
for (final row in settings) {
settingsMap[row[0] as String] = row[1] as String;
}
stats['settings'] = settingsMap;

final activity = await _conn!.execute(
"SELECT count(*) AS total, "
"count(*) FILTER (WHERE state = 'active') AS active, "
"count(*) FILTER (WHERE state = 'idle') AS idle "
"FROM pg_stat_activity",
);
if (activity.isNotEmpty) {
stats['connections_total'] = activity.first[0];
stats['connections_active'] = activity.first[1];
stats['connections_idle'] = activity.first[2];
}

final dbStats = await _conn!.execute(
"SELECT datname, pg_database_size(datname) AS size, "
"numbackends, xact_commit, xact_rollback, blks_read, blks_hit, "
"tup_returned, tup_fetched, tup_inserted, tup_updated, tup_deleted "
"FROM pg_stat_database WHERE datname NOT LIKE 'template%' "
"ORDER BY datname",
);
final dbList = <Map<String, dynamic>>[];
for (final row in dbStats) {
dbList.add({
'datname': row[0],
'size': row[1],
'numbackends': row[2],
'xact_commit': row[3],
'xact_rollback': row[4],
'blks_read': row[5],
'blks_hit': row[6],
'tup_returned': row[7],
'tup_fetched': row[8],
'tup_inserted': row[9],
'tup_updated': row[10],
'tup_deleted': row[11],
});
}
stats['databases'] = dbList;

try {
final uptime = await _conn!.execute(
"SELECT extract(epoch from (now() - pg_postmaster_start_time()))::bigint",
);
stats['uptime_seconds'] = uptime.first[0];
} catch (_) {}

try {
final dbSize = await _conn!.execute(
"SELECT pg_database_size(current_database())",
);
stats['current_db_size'] = dbSize.first[0];
} catch (_) {}

return stats;
}

/// Connect to a specific database (creates a new connection).
Future<PostgresConnection> connectToDatabase(String dbName) async {
return PostgresConnection(
id: id,
name: name,
host: host,
port: port,
username: username,
password: password,
database: dbName,
useSSL: useSSL,
);
}
}

class PostgresConnectionException implements Exception {
PostgresConnectionException(this.message);
final String message;
@override
String toString() => message;
}
Loading
Loading