From ec33a5b586d209bc0584ca2cb823b1ac7300e02f Mon Sep 17 00:00:00 2001 From: Vitali Graf Date: Thu, 12 Feb 2026 21:31:40 +0100 Subject: [PATCH] Feature: Timeout-Einstellung und asynchrone DB-Abfrage mit Abbrechen-Dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DB-Abfragen laufen nun in einem Hintergrund-Thread mit QProgressDialog, sodass die UI nicht mehr einfriert. connect_timeout wird als konfigurierbarer Parameter (1-300s, Standard: 10) im Connection-String übergeben. Co-Authored-By: Claude Opus 4.6 --- src/conf.py | 1 + src/ui/PostgreSqlConfigDialog.py | 12 +- src/ui/PostgreSqlConfigDialog.ui | 20 ++ src/ui/PostgreSqlConfigDialog_ui.py | 326 +++++++++++++++------------- src/ui/mixins/database.py | 115 +++++++--- 5 files changed, 287 insertions(+), 187 deletions(-) diff --git a/src/conf.py b/src/conf.py index 35beca2..1122676 100644 --- a/src/conf.py +++ b/src/conf.py @@ -89,6 +89,7 @@ class PostgreSqlDb(BaseModel): username: str password: str ssl_mode: SSLMode = SSLMode.PREFER + timeout: int = 10 class Project(BaseModel): diff --git a/src/ui/PostgreSqlConfigDialog.py b/src/ui/PostgreSqlConfigDialog.py index 8c90bd1..ad0b458 100644 --- a/src/ui/PostgreSqlConfigDialog.py +++ b/src/ui/PostgreSqlConfigDialog.py @@ -19,8 +19,14 @@ class DatabaseTestThread(QThread): def run(self): """Führt den Datenbanktest in einem separaten Thread aus.""" try: - uri = f"postgresql://{self.connection_data['username']}:{self.connection_data['password']}@{self.connection_data['host']}:{self.connection_data['port']}/{self.connection_data['database']}" - + timeout = self.connection_data.get("timeout", 10) + uri = ( + f"postgresql://{self.connection_data['username']}:{self.connection_data['password']}" + f"@{self.connection_data['host']}:{self.connection_data['port']}" + f"/{self.connection_data['database']}" + f"?connect_timeout={timeout}" + ) + # Datenbankverbindung testen r = pl.read_database_uri( query="SELECT 1", @@ -106,6 +112,7 @@ class PostgreSqlConfigDialog(QDialog): self.ui.usernameEdit.setText(data.get("username", "")) self.ui.passwordEdit.setText(data.get("password", "")) self.ui.sslModeComboBox.setCurrentText(data.get("ssl_mode", "prefer")) + self.ui.timeoutSpinBox.setValue(data.get("timeout", 10)) def get_data(self): """Gibt die eingegebenen Daten zurück.""" @@ -126,4 +133,5 @@ class PostgreSqlConfigDialog(QDialog): "username": self.ui.usernameEdit.text().strip(), "password": self.ui.passwordEdit.text(), # Passwort kann leer sein "ssl_mode": self.ui.sslModeComboBox.currentText(), + "timeout": self.ui.timeoutSpinBox.value(), } diff --git a/src/ui/PostgreSqlConfigDialog.ui b/src/ui/PostgreSqlConfigDialog.ui index 20fdafe..0a8de8f 100644 --- a/src/ui/PostgreSqlConfigDialog.ui +++ b/src/ui/PostgreSqlConfigDialog.ui @@ -138,7 +138,27 @@ + + + + Timeout (s): + + + + + + 1 + + + 300 + + + 10 + + + + Verbindung testen diff --git a/src/ui/PostgreSqlConfigDialog_ui.py b/src/ui/PostgreSqlConfigDialog_ui.py index 0443022..f3cccff 100644 --- a/src/ui/PostgreSqlConfigDialog_ui.py +++ b/src/ui/PostgreSqlConfigDialog_ui.py @@ -1,156 +1,170 @@ -# -*- coding: utf-8 -*- - -################################################################################ -## Form generated from reading UI file 'PostgreSqlConfigDialog.ui' -## -## Created by: Qt User Interface Compiler version 6.9.1 -## -## WARNING! All changes made in this file will be lost when recompiling UI file! -################################################################################ - -from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, - QMetaObject, QObject, QPoint, QRect, - QSize, QTime, QUrl, Qt) -from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, - QFont, QFontDatabase, QGradient, QIcon, - QImage, QKeySequence, QLinearGradient, QPainter, - QPalette, QPixmap, QRadialGradient, QTransform) -from PySide6.QtWidgets import (QAbstractButton, QApplication, QComboBox, QDialog, - QDialogButtonBox, QFormLayout, QLabel, QLineEdit, - QPushButton, QSizePolicy, QSpinBox, QVBoxLayout, - QWidget) - -class Ui_PostgreSqlConfigDialog(object): - def setupUi(self, PostgreSqlConfigDialog): - if not PostgreSqlConfigDialog.objectName(): - PostgreSqlConfigDialog.setObjectName(u"PostgreSqlConfigDialog") - PostgreSqlConfigDialog.resize(397, 268) - PostgreSqlConfigDialog.setModal(True) - self.verticalLayout = QVBoxLayout(PostgreSqlConfigDialog) - self.verticalLayout.setObjectName(u"verticalLayout") - self.formLayout = QFormLayout() - self.formLayout.setObjectName(u"formLayout") - self.nameLabel = QLabel(PostgreSqlConfigDialog) - self.nameLabel.setObjectName(u"nameLabel") - - self.formLayout.setWidget(0, QFormLayout.ItemRole.LabelRole, self.nameLabel) - - self.nameEdit = QLineEdit(PostgreSqlConfigDialog) - self.nameEdit.setObjectName(u"nameEdit") - - self.formLayout.setWidget(0, QFormLayout.ItemRole.FieldRole, self.nameEdit) - - self.hostLabel = QLabel(PostgreSqlConfigDialog) - self.hostLabel.setObjectName(u"hostLabel") - - self.formLayout.setWidget(1, QFormLayout.ItemRole.LabelRole, self.hostLabel) - - self.hostEdit = QLineEdit(PostgreSqlConfigDialog) - self.hostEdit.setObjectName(u"hostEdit") - - self.formLayout.setWidget(1, QFormLayout.ItemRole.FieldRole, self.hostEdit) - - self.portLabel = QLabel(PostgreSqlConfigDialog) - self.portLabel.setObjectName(u"portLabel") - - self.formLayout.setWidget(2, QFormLayout.ItemRole.LabelRole, self.portLabel) - - self.portSpinBox = QSpinBox(PostgreSqlConfigDialog) - self.portSpinBox.setObjectName(u"portSpinBox") - self.portSpinBox.setMinimum(1) - self.portSpinBox.setMaximum(65535) - self.portSpinBox.setValue(5432) - - self.formLayout.setWidget(2, QFormLayout.ItemRole.FieldRole, self.portSpinBox) - - self.databaseLabel = QLabel(PostgreSqlConfigDialog) - self.databaseLabel.setObjectName(u"databaseLabel") - - self.formLayout.setWidget(3, QFormLayout.ItemRole.LabelRole, self.databaseLabel) - - self.databaseEdit = QLineEdit(PostgreSqlConfigDialog) - self.databaseEdit.setObjectName(u"databaseEdit") - - self.formLayout.setWidget(3, QFormLayout.ItemRole.FieldRole, self.databaseEdit) - - self.usernameLabel = QLabel(PostgreSqlConfigDialog) - self.usernameLabel.setObjectName(u"usernameLabel") - - self.formLayout.setWidget(4, QFormLayout.ItemRole.LabelRole, self.usernameLabel) - - self.usernameEdit = QLineEdit(PostgreSqlConfigDialog) - self.usernameEdit.setObjectName(u"usernameEdit") - - self.formLayout.setWidget(4, QFormLayout.ItemRole.FieldRole, self.usernameEdit) - - self.passwordLabel = QLabel(PostgreSqlConfigDialog) - self.passwordLabel.setObjectName(u"passwordLabel") - - self.formLayout.setWidget(5, QFormLayout.ItemRole.LabelRole, self.passwordLabel) - - self.passwordEdit = QLineEdit(PostgreSqlConfigDialog) - self.passwordEdit.setObjectName(u"passwordEdit") - self.passwordEdit.setEchoMode(QLineEdit.EchoMode.Password) - - self.formLayout.setWidget(5, QFormLayout.ItemRole.FieldRole, self.passwordEdit) - - self.sslModeLabel = QLabel(PostgreSqlConfigDialog) - self.sslModeLabel.setObjectName(u"sslModeLabel") - - self.formLayout.setWidget(6, QFormLayout.ItemRole.LabelRole, self.sslModeLabel) - - self.sslModeComboBox = QComboBox(PostgreSqlConfigDialog) - self.sslModeComboBox.addItem("") - self.sslModeComboBox.addItem("") - self.sslModeComboBox.addItem("") - self.sslModeComboBox.addItem("") - self.sslModeComboBox.addItem("") - self.sslModeComboBox.addItem("") - self.sslModeComboBox.setObjectName(u"sslModeComboBox") - - self.formLayout.setWidget(6, QFormLayout.ItemRole.FieldRole, self.sslModeComboBox) - - self.testConnectionButton = QPushButton(PostgreSqlConfigDialog) - self.testConnectionButton.setObjectName(u"testConnectionButton") - - self.formLayout.setWidget(7, QFormLayout.ItemRole.FieldRole, self.testConnectionButton) - - - self.verticalLayout.addLayout(self.formLayout) - - self.buttonBox = QDialogButtonBox(PostgreSqlConfigDialog) - self.buttonBox.setObjectName(u"buttonBox") - self.buttonBox.setOrientation(Qt.Orientation.Horizontal) - self.buttonBox.setStandardButtons(QDialogButtonBox.StandardButton.Cancel|QDialogButtonBox.StandardButton.Ok) - self.buttonBox.setCenterButtons(True) - - self.verticalLayout.addWidget(self.buttonBox) - - - self.retranslateUi(PostgreSqlConfigDialog) - self.buttonBox.accepted.connect(PostgreSqlConfigDialog.accept) - self.buttonBox.rejected.connect(PostgreSqlConfigDialog.reject) - - QMetaObject.connectSlotsByName(PostgreSqlConfigDialog) - # setupUi - - def retranslateUi(self, PostgreSqlConfigDialog): - PostgreSqlConfigDialog.setWindowTitle(QCoreApplication.translate("PostgreSqlConfigDialog", u"PostgreSQL Datenbank Konfiguration", None)) - self.nameLabel.setText(QCoreApplication.translate("PostgreSqlConfigDialog", u"Name:", None)) - self.hostLabel.setText(QCoreApplication.translate("PostgreSqlConfigDialog", u"Host:", None)) - self.hostEdit.setText(QCoreApplication.translate("PostgreSqlConfigDialog", u"localhost", None)) - self.portLabel.setText(QCoreApplication.translate("PostgreSqlConfigDialog", u"Port:", None)) - self.databaseLabel.setText(QCoreApplication.translate("PostgreSqlConfigDialog", u"Datenbank:", None)) - self.usernameLabel.setText(QCoreApplication.translate("PostgreSqlConfigDialog", u"Benutzername:", None)) - self.passwordLabel.setText(QCoreApplication.translate("PostgreSqlConfigDialog", u"Passwort:", None)) - self.sslModeLabel.setText(QCoreApplication.translate("PostgreSqlConfigDialog", u"SSL-Modus:", None)) - self.sslModeComboBox.setItemText(0, QCoreApplication.translate("PostgreSqlConfigDialog", u"disable", None)) - self.sslModeComboBox.setItemText(1, QCoreApplication.translate("PostgreSqlConfigDialog", u"allow", None)) - self.sslModeComboBox.setItemText(2, QCoreApplication.translate("PostgreSqlConfigDialog", u"prefer", None)) - self.sslModeComboBox.setItemText(3, QCoreApplication.translate("PostgreSqlConfigDialog", u"require", None)) - self.sslModeComboBox.setItemText(4, QCoreApplication.translate("PostgreSqlConfigDialog", u"verify-ca", None)) - self.sslModeComboBox.setItemText(5, QCoreApplication.translate("PostgreSqlConfigDialog", u"verify-full", None)) - - self.testConnectionButton.setText(QCoreApplication.translate("PostgreSqlConfigDialog", u"Verbindung testen", None)) - # retranslateUi - +# -*- coding: utf-8 -*- + +################################################################################ +## Form generated from reading UI file 'PostgreSqlConfigDialog.ui' +## +## Created by: Qt User Interface Compiler version 6.10.1 +## +## WARNING! All changes made in this file will be lost when recompiling UI file! +################################################################################ + +from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, + QMetaObject, QObject, QPoint, QRect, + QSize, QTime, QUrl, Qt) +from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, + QFont, QFontDatabase, QGradient, QIcon, + QImage, QKeySequence, QLinearGradient, QPainter, + QPalette, QPixmap, QRadialGradient, QTransform) +from PySide6.QtWidgets import (QAbstractButton, QApplication, QComboBox, QDialog, + QDialogButtonBox, QFormLayout, QLabel, QLineEdit, + QPushButton, QSizePolicy, QSpinBox, QVBoxLayout, + QWidget) + +class Ui_PostgreSqlConfigDialog(object): + def setupUi(self, PostgreSqlConfigDialog): + if not PostgreSqlConfigDialog.objectName(): + PostgreSqlConfigDialog.setObjectName(u"PostgreSqlConfigDialog") + PostgreSqlConfigDialog.resize(397, 268) + PostgreSqlConfigDialog.setModal(True) + self.verticalLayout = QVBoxLayout(PostgreSqlConfigDialog) + self.verticalLayout.setObjectName(u"verticalLayout") + self.formLayout = QFormLayout() + self.formLayout.setObjectName(u"formLayout") + self.nameLabel = QLabel(PostgreSqlConfigDialog) + self.nameLabel.setObjectName(u"nameLabel") + + self.formLayout.setWidget(0, QFormLayout.ItemRole.LabelRole, self.nameLabel) + + self.nameEdit = QLineEdit(PostgreSqlConfigDialog) + self.nameEdit.setObjectName(u"nameEdit") + + self.formLayout.setWidget(0, QFormLayout.ItemRole.FieldRole, self.nameEdit) + + self.hostLabel = QLabel(PostgreSqlConfigDialog) + self.hostLabel.setObjectName(u"hostLabel") + + self.formLayout.setWidget(1, QFormLayout.ItemRole.LabelRole, self.hostLabel) + + self.hostEdit = QLineEdit(PostgreSqlConfigDialog) + self.hostEdit.setObjectName(u"hostEdit") + + self.formLayout.setWidget(1, QFormLayout.ItemRole.FieldRole, self.hostEdit) + + self.portLabel = QLabel(PostgreSqlConfigDialog) + self.portLabel.setObjectName(u"portLabel") + + self.formLayout.setWidget(2, QFormLayout.ItemRole.LabelRole, self.portLabel) + + self.portSpinBox = QSpinBox(PostgreSqlConfigDialog) + self.portSpinBox.setObjectName(u"portSpinBox") + self.portSpinBox.setMinimum(1) + self.portSpinBox.setMaximum(65535) + self.portSpinBox.setValue(5432) + + self.formLayout.setWidget(2, QFormLayout.ItemRole.FieldRole, self.portSpinBox) + + self.databaseLabel = QLabel(PostgreSqlConfigDialog) + self.databaseLabel.setObjectName(u"databaseLabel") + + self.formLayout.setWidget(3, QFormLayout.ItemRole.LabelRole, self.databaseLabel) + + self.databaseEdit = QLineEdit(PostgreSqlConfigDialog) + self.databaseEdit.setObjectName(u"databaseEdit") + + self.formLayout.setWidget(3, QFormLayout.ItemRole.FieldRole, self.databaseEdit) + + self.usernameLabel = QLabel(PostgreSqlConfigDialog) + self.usernameLabel.setObjectName(u"usernameLabel") + + self.formLayout.setWidget(4, QFormLayout.ItemRole.LabelRole, self.usernameLabel) + + self.usernameEdit = QLineEdit(PostgreSqlConfigDialog) + self.usernameEdit.setObjectName(u"usernameEdit") + + self.formLayout.setWidget(4, QFormLayout.ItemRole.FieldRole, self.usernameEdit) + + self.passwordLabel = QLabel(PostgreSqlConfigDialog) + self.passwordLabel.setObjectName(u"passwordLabel") + + self.formLayout.setWidget(5, QFormLayout.ItemRole.LabelRole, self.passwordLabel) + + self.passwordEdit = QLineEdit(PostgreSqlConfigDialog) + self.passwordEdit.setObjectName(u"passwordEdit") + self.passwordEdit.setEchoMode(QLineEdit.EchoMode.Password) + + self.formLayout.setWidget(5, QFormLayout.ItemRole.FieldRole, self.passwordEdit) + + self.sslModeLabel = QLabel(PostgreSqlConfigDialog) + self.sslModeLabel.setObjectName(u"sslModeLabel") + + self.formLayout.setWidget(6, QFormLayout.ItemRole.LabelRole, self.sslModeLabel) + + self.sslModeComboBox = QComboBox(PostgreSqlConfigDialog) + self.sslModeComboBox.addItem("") + self.sslModeComboBox.addItem("") + self.sslModeComboBox.addItem("") + self.sslModeComboBox.addItem("") + self.sslModeComboBox.addItem("") + self.sslModeComboBox.addItem("") + self.sslModeComboBox.setObjectName(u"sslModeComboBox") + + self.formLayout.setWidget(6, QFormLayout.ItemRole.FieldRole, self.sslModeComboBox) + + self.timeoutLabel = QLabel(PostgreSqlConfigDialog) + self.timeoutLabel.setObjectName(u"timeoutLabel") + + self.formLayout.setWidget(7, QFormLayout.ItemRole.LabelRole, self.timeoutLabel) + + self.timeoutSpinBox = QSpinBox(PostgreSqlConfigDialog) + self.timeoutSpinBox.setObjectName(u"timeoutSpinBox") + self.timeoutSpinBox.setMinimum(1) + self.timeoutSpinBox.setMaximum(300) + self.timeoutSpinBox.setValue(10) + + self.formLayout.setWidget(7, QFormLayout.ItemRole.FieldRole, self.timeoutSpinBox) + + self.testConnectionButton = QPushButton(PostgreSqlConfigDialog) + self.testConnectionButton.setObjectName(u"testConnectionButton") + + self.formLayout.setWidget(8, QFormLayout.ItemRole.FieldRole, self.testConnectionButton) + + + self.verticalLayout.addLayout(self.formLayout) + + self.buttonBox = QDialogButtonBox(PostgreSqlConfigDialog) + self.buttonBox.setObjectName(u"buttonBox") + self.buttonBox.setOrientation(Qt.Orientation.Horizontal) + self.buttonBox.setStandardButtons(QDialogButtonBox.StandardButton.Cancel|QDialogButtonBox.StandardButton.Ok) + self.buttonBox.setCenterButtons(True) + + self.verticalLayout.addWidget(self.buttonBox) + + + self.retranslateUi(PostgreSqlConfigDialog) + self.buttonBox.accepted.connect(PostgreSqlConfigDialog.accept) + self.buttonBox.rejected.connect(PostgreSqlConfigDialog.reject) + + QMetaObject.connectSlotsByName(PostgreSqlConfigDialog) + # setupUi + + def retranslateUi(self, PostgreSqlConfigDialog): + PostgreSqlConfigDialog.setWindowTitle(QCoreApplication.translate("PostgreSqlConfigDialog", u"PostgreSQL Datenbank Konfiguration", None)) + self.nameLabel.setText(QCoreApplication.translate("PostgreSqlConfigDialog", u"Name:", None)) + self.hostLabel.setText(QCoreApplication.translate("PostgreSqlConfigDialog", u"Host:", None)) + self.hostEdit.setText(QCoreApplication.translate("PostgreSqlConfigDialog", u"localhost", None)) + self.portLabel.setText(QCoreApplication.translate("PostgreSqlConfigDialog", u"Port:", None)) + self.databaseLabel.setText(QCoreApplication.translate("PostgreSqlConfigDialog", u"Datenbank:", None)) + self.usernameLabel.setText(QCoreApplication.translate("PostgreSqlConfigDialog", u"Benutzername:", None)) + self.passwordLabel.setText(QCoreApplication.translate("PostgreSqlConfigDialog", u"Passwort:", None)) + self.sslModeLabel.setText(QCoreApplication.translate("PostgreSqlConfigDialog", u"SSL-Modus:", None)) + self.sslModeComboBox.setItemText(0, QCoreApplication.translate("PostgreSqlConfigDialog", u"disable", None)) + self.sslModeComboBox.setItemText(1, QCoreApplication.translate("PostgreSqlConfigDialog", u"allow", None)) + self.sslModeComboBox.setItemText(2, QCoreApplication.translate("PostgreSqlConfigDialog", u"prefer", None)) + self.sslModeComboBox.setItemText(3, QCoreApplication.translate("PostgreSqlConfigDialog", u"require", None)) + self.sslModeComboBox.setItemText(4, QCoreApplication.translate("PostgreSqlConfigDialog", u"verify-ca", None)) + self.sslModeComboBox.setItemText(5, QCoreApplication.translate("PostgreSqlConfigDialog", u"verify-full", None)) + + self.timeoutLabel.setText(QCoreApplication.translate("PostgreSqlConfigDialog", u"Timeout (s):", None)) + self.testConnectionButton.setText(QCoreApplication.translate("PostgreSqlConfigDialog", u"Verbindung testen", None)) + # retranslateUi + diff --git a/src/ui/mixins/database.py b/src/ui/mixins/database.py index 597c88f..c7995de 100644 --- a/src/ui/mixins/database.py +++ b/src/ui/mixins/database.py @@ -10,13 +10,35 @@ import logging from pathlib import Path import polars as pl -from PySide6.QtWidgets import QMessageBox +from PySide6.QtCore import QThread, Signal, Qt +from PySide6.QtWidgets import QMessageBox, QProgressDialog from conf import app_settings, TreeNode, XslFile logger = logging.getLogger(__name__) +class DatabaseQueryThread(QThread): + """Thread für asynchrone Datenbankabfragen.""" + + query_completed = Signal(object) # pl.DataFrame + query_failed = Signal(str) # Fehlermeldung + + def __init__(self, sql_query, connection_string): + super().__init__() + self.sql_query = sql_query + self.connection_string = connection_string + + def run(self): + try: + df = pl.read_database_uri(self.sql_query, self.connection_string, engine="connectorx").sort( + ["reporttyp_bez", "report_bez", "repfile_bez"] + ) + self.query_completed.emit(df) + except Exception as e: + self.query_failed.emit(str(e)) + + class DatabaseMixin: """ Mixin für Datenbank-Operationen. @@ -31,7 +53,7 @@ class DatabaseMixin: def on_load_from_fn2_clicked(self): """ Wird ausgeführt, wenn der Button "lade aus FN2" geklickt wird. - Führt SQL-Abfrage aus und aktualisiert die Projekt-Nodes. + Startet SQL-Abfrage asynchron mit Fortschrittsdialog. """ logger.debug("Button 'lade aus FN2' wurde geklickt!") @@ -52,29 +74,68 @@ class DatabaseMixin: QMessageBox.warning(self, "Warnung", "PostgreSQL-Datenbank-Konfiguration nicht gefunden.") return - # Führe SQL-Abfrage aus - df = self._execute_sql_query(db_config) - if df is None: + # SQL-Abfrage und Connection-String vorbereiten + sql_query, connection_string = self._prepare_sql_query(db_config) + if sql_query is None: return # Fehler bereits angezeigt - # Verarbeite die Daten wie in readCsv.py - new_nodes = self._process_sql_data(df) + # Fortschrittsdialog erstellen + self._db_progress = QProgressDialog("Verbinde mit Datenbank...", "Abbrechen", 0, 0, self) + self._db_progress.setWindowTitle("Datenbank-Abfrage") + self._db_progress.setWindowModality(Qt.WindowModal) + self._db_progress.setMinimumDuration(0) - # Merge mit vorhandenen Nodes - self._merge_nodes_with_existing(new_nodes) - - # Speichere die aktualisierten Projekt-Einstellungen - self._save_project_settings() - - # Lade das Projekt neu - self._load_nodes_to_tree() - - # QMessageBox.information(self, "Erfolg", "Daten erfolgreich aus FN2 geladen und Projekt aktualisiert!") + # Query-Thread erstellen und starten + self._db_query_thread = DatabaseQueryThread(sql_query, connection_string) + self._db_query_thread.query_completed.connect(self._on_db_query_completed) + self._db_query_thread.query_failed.connect(self._on_db_query_failed) + self._db_query_thread.finished.connect(self._cleanup_db_query) + self._db_progress.canceled.connect(self._on_db_query_canceled) + self._db_query_thread.start() except Exception as e: logger.error(f"Fehler beim Laden aus FN2: {e}") QMessageBox.critical(self, "Fehler", f"Fehler beim Laden aus FN2:\n{str(e)}") + def _on_db_query_completed(self, df): + """Wird aufgerufen, wenn die Datenbankabfrage erfolgreich war.""" + # Ignoriere Ergebnis, falls der Benutzer abgebrochen hat + if hasattr(self, "_db_progress") and self._db_progress.wasCanceled(): + return + + try: + new_nodes = self._process_sql_data(df) + self._merge_nodes_with_existing(new_nodes) + self._save_project_settings() + self._load_nodes_to_tree() + except Exception as e: + logger.error(f"Fehler beim Verarbeiten der DB-Daten: {e}") + QMessageBox.critical(self, "Fehler", f"Fehler beim Verarbeiten der Daten:\n{str(e)}") + + def _on_db_query_failed(self, error_msg): + """Wird aufgerufen, wenn die Datenbankabfrage fehlgeschlagen ist.""" + if hasattr(self, "_db_progress") and self._db_progress.wasCanceled(): + return + + error = f"Fehler beim Ausführen der SQL-Abfrage: {error_msg}" + logger.error(error) + QMessageBox.critical(self, "Fehler", error) + + def _on_db_query_canceled(self): + """Wird aufgerufen, wenn der Benutzer die Abfrage abbricht.""" + logger.info("Datenbankabfrage vom Benutzer abgebrochen") + + def _cleanup_db_query(self): + """Räumt nach der Datenbankabfrage auf.""" + if hasattr(self, "_db_progress"): + self._db_progress.canceled.disconnect(self._on_db_query_canceled) + self._db_progress.close() + self._db_progress.deleteLater() + del self._db_progress + if hasattr(self, "_db_query_thread"): + self._db_query_thread.deleteLater() + del self._db_query_thread + def _get_database_config(self, db_id): """ Holt die Datenbank-Konfiguration anhand der ID. @@ -90,29 +151,27 @@ class DatabaseMixin: return db return None - def _execute_sql_query(self, db_config): + def _prepare_sql_query(self, db_config): """ - Führt die SQL-Abfrage aus der data.sql Datei aus. + Bereitet SQL-Abfrage und Connection-String vor. Args: db_config: PostgreSQL-Datenbank-Konfiguration Returns: - pl.DataFrame|None: Die Abfrageergebnisse oder None bei Fehler + tuple[str, str]|tuple[None, None]: (sql_query, connection_string) oder (None, None) bei Fehler """ try: - # Lade SQL-Abfrage aus Datei sql_file_path = Path("src/res/data.sql") if not sql_file_path.exists(): QMessageBox.critical(self, "Fehler", f"SQL-Datei nicht gefunden: {sql_file_path}") - return None + return None, None with open(sql_file_path, "r", encoding="utf-8") as f: sql_query = f.read() logger.debug(f"SQL-Abfrage geladen: {len(sql_query)} Zeichen") - # Verbindung zur PostgreSQL-Datenbank herstellen connection_string = ( "postgresql://" f"{db_config.username}:" @@ -121,19 +180,17 @@ class DatabaseMixin: f"{db_config.port}/" f"{db_config.database}?" f"sslmode={db_config.ssl_mode.value}" + f"&connect_timeout={db_config.timeout}" ) logger.info(f"Verbinde zu PostgreSQL: {db_config.host}:{db_config.port}/{db_config.database}") - df = pl.read_database_uri(sql_query, connection_string, engine="connectorx").sort( - ["reporttyp_bez", "report_bez", "repfile_bez"] - ) - return df + return sql_query, connection_string except Exception as e: - error_msg = f"Fehler beim Ausführen der SQL-Abfrage: {str(e)}" + error_msg = f"Fehler beim Vorbereiten der SQL-Abfrage: {str(e)}" logger.error(error_msg) QMessageBox.critical(self, "Fehler", error_msg) - return None + return None, None def _process_sql_data(self, df): """