"""Column statistics dialog — shows row count, nulls, distinct, min, max, avg."""
from PyQt6.QtWidgets import (
QDialog, QVBoxLayout, QFormLayout, QLabel, QDialogButtonBox,
QGroupBox, QProgressBar, QSizePolicy,
)
from PyQt6.QtCore import Qt, QThread, pyqtSignal
from PyQt6.QtGui import QFont
class _StatsWorker(QThread):
result = pyqtSignal(dict)
error = pyqtSignal(str)
def __init__(self, driver, database: str, table: str, column: str):
super().__init__()
self._driver = driver
self._database = database
self._table = table
self._column = column
def run(self):
col = self._column
tbl = self._table
try:
_, rows, _ = self._driver.execute_query(
f'SELECT COUNT(*), COUNT("{col}"), COUNT(DISTINCT "{col}"), '
f'MIN("{col}"), MAX("{col}") FROM "{tbl}"'
)
total, non_null, distinct, min_val, max_val = rows[0]
null_count = (total or 0) - (non_null or 0)
except Exception as e:
self.error.emit(str(e))
return
avg_val = None
try:
_, avg_rows, _ = self._driver.execute_query(
f'SELECT AVG("{col}") FROM "{tbl}"'
)
avg_val = avg_rows[0][0]
except Exception:
pass
self.result.emit({
"total": total,
"non_null": non_null,
"nulls": null_count,
"distinct": distinct,
"min": min_val,
"max": max_val,
"avg": avg_val,
})
class ColumnStatsDialog(QDialog):
def __init__(self, driver, database: str, table: str, column: str, parent=None):
super().__init__(parent)
self.setWindowTitle(f"Column Statistics — {column}")
self.setMinimumWidth(360)
self._driver = driver
self._database = database
self._table = table
self._column = column
self._build_ui()
self._load()
def _build_ui(self):
root = QVBoxLayout(self)
root.setSpacing(10)
root.setContentsMargins(16, 16, 16, 16)
subtitle = QLabel(f"{self._table}.{self._column}")
subtitle.setTextFormat(Qt.TextFormat.RichText)
root.addWidget(subtitle)
# ── Stats group ───────────────────────────────────────────────────────
box = QGroupBox("Statistics")
self._form = QFormLayout(box)
self._form.setSpacing(6)
self._rows: dict[str, QLabel] = {}
for key, label in [
("total", "Total rows"),
("non_null", "Non-null"),
("nulls", "Null count"),
("distinct", "Distinct values"),
("min", "Min"),
("max", "Max"),
("avg", "Avg (numeric)"),
]:
lbl = QLabel("…")
lbl.setFont(QFont("Consolas", 10))
self._form.addRow(label + ":", lbl)
self._rows[key] = lbl
root.addWidget(box)
# ── Loading bar ───────────────────────────────────────────────────────
self._progress = QProgressBar()
self._progress.setMaximum(0)
self._progress.setFixedHeight(6)
self._progress.setTextVisible(False)
root.addWidget(self._progress)
bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
bb.rejected.connect(self.reject)
root.addWidget(bb)
def _load(self):
self._worker = _StatsWorker(
self._driver, self._database, self._table, self._column
)
self._worker.result.connect(self._on_result)
self._worker.error.connect(self._on_error)
self._worker.finished.connect(self._progress.hide)
self._worker.start()
def _on_result(self, stats: dict):
for key, lbl in self._rows.items():
val = stats.get(key)
if val is None:
text = "N/A"
elif isinstance(val, float):
text = f"{val:,.4f}"
else:
try:
text = f"{int(val):,}"
except (TypeError, ValueError):
text = str(val)
lbl.setText(text)
def _on_error(self, msg: str):
for lbl in self._rows.values():
lbl.setText("—")
self._rows["total"].setText(f"Error: {msg[:60]}")
self._rows["total"].setStyleSheet("color: #e78284;")