# -*- coding: utf-8 -*- """数据库查询工作线程""" import sys from pathlib import Path from typing import List, Dict, Any, Optional, Tuple from PySide6.QtCore import QThread, Signal # 添加项目路径 PROJECT_ROOT = Path(__file__).resolve().parents[2] if str(PROJECT_ROOT) not in sys.path: sys.path.insert(0, str(PROJECT_ROOT)) class DBWorker(QThread): """数据库查询工作线程""" # 信号 query_finished = Signal(list, list) # 查询完成 (columns, rows) query_error = Signal(str) # 查询错误 connection_status = Signal(bool, str) # 连接状态 (connected, message) tables_loaded = Signal(dict) # 表列表加载完成 {schema: [(table, rows, updated_at), ...]} def __init__(self, parent=None): super().__init__(parent) self.conn = None self._task = None self._task_args = None def connect_db(self, dsn: str): """连接数据库""" self._task = "connect" self._task_args = (dsn,) self.start() def disconnect_db(self): """断开数据库连接""" self._task = "disconnect" self._task_args = None self.start() def execute_query(self, sql: str, params: Optional[tuple] = None): """执行查询""" self._task = "query" self._task_args = (sql, params) self.start() def load_tables(self, schemas: Optional[List[str]] = None): """加载表列表""" self._task = "load_tables" self._task_args = (schemas,) self.start() def run(self): """执行任务""" if self._task == "connect": self._do_connect(*self._task_args) elif self._task == "disconnect": self._do_disconnect() elif self._task == "query": self._do_query(*self._task_args) elif self._task == "load_tables": self._do_load_tables(*self._task_args) def _do_connect(self, dsn: str): """执行连接""" try: import psycopg2 from psycopg2.extras import RealDictCursor self.conn = psycopg2.connect(dsn, connect_timeout=10) self.conn.set_session(autocommit=True) # 测试连接 with self.conn.cursor() as cur: cur.execute("SELECT version()") version = cur.fetchone()[0] self.connection_status.emit(True, f"已连接: {version[:50]}...") except ImportError: self.connection_status.emit(False, "缺少 psycopg2 模块,请安装: pip install psycopg2-binary") except Exception as e: self.conn = None self.connection_status.emit(False, f"连接失败: {e}") def _do_disconnect(self): """执行断开连接""" if self.conn: try: self.conn.close() except Exception: pass self.conn = None self.connection_status.emit(False, "已断开连接") def _do_query(self, sql: str, params: Optional[tuple]): """执行查询""" if not self.conn: self.query_error.emit("未连接到数据库") return try: from psycopg2.extras import RealDictCursor with self.conn.cursor(cursor_factory=RealDictCursor) as cur: cur.execute(sql, params) # 检查是否有结果 if cur.description: columns = [desc[0] for desc in cur.description] rows = [dict(row) for row in cur.fetchall()] self.query_finished.emit(columns, rows) else: self.query_finished.emit([], []) except Exception as e: self.query_error.emit(f"查询失败: {e}") def _do_load_tables(self, schemas: Optional[List[str]]): """加载表列表""" if not self.conn: self.query_error.emit("未连接到数据库") return try: if schemas is None: schemas = ["billiards_ods", "billiards_dwd", "billiards_dws", "etl_admin"] result = {} for schema in schemas: tables = [] # 获取表列表 sql = """ SELECT t.table_name, COALESCE(s.n_live_tup, 0) as row_count FROM information_schema.tables t LEFT JOIN pg_stat_user_tables s ON t.table_name = s.relname AND t.table_schema = s.schemaname WHERE t.table_schema = %s AND t.table_type = 'BASE TABLE' ORDER BY t.table_name """ with self.conn.cursor() as cur: cur.execute(sql, (schema,)) for row in cur.fetchall(): table_name = row[0] row_count = row[1] or 0 # 尝试获取最新更新时间 updated_at = None try: # 尝试 fetched_at 字段 cur.execute(f'SELECT MAX(fetched_at) FROM "{schema}"."{table_name}"') result_row = cur.fetchone() if result_row and result_row[0]: updated_at = str(result_row[0])[:19] except Exception: pass if not updated_at: try: # 尝试 updated_at 字段 cur.execute(f'SELECT MAX(updated_at) FROM "{schema}"."{table_name}"') result_row = cur.fetchone() if result_row and result_row[0]: updated_at = str(result_row[0])[:19] except Exception: pass tables.append((table_name, row_count, updated_at or "-")) result[schema] = tables self.tables_loaded.emit(result) except Exception as e: self.query_error.emit(f"加载表列表失败: {e}") def is_connected(self) -> bool: """检查是否已连接""" if not self.conn: return False try: with self.conn.cursor() as cur: cur.execute("SELECT 1") return True except Exception: return False