sqlbench 0.1.59__tar.gz → 0.1.61__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sqlbench-0.1.59 → sqlbench-0.1.61}/PKG-INFO +1 -1
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/main_window.py +43 -25
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/tabs/sql_tab.py +143 -46
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/version.py +1 -1
- {sqlbench-0.1.59 → sqlbench-0.1.61}/.github/workflows/pypi-publish.yml +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/.gitignore +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/CLAUDE.md +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/Makefile +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/PYQT_MIGRATION_FEATURES.md +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/README.md +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/pyproject.toml +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/requirements.txt +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/__init__.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/__main__.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/adapters.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/app.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/database.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/dialogs/__init__.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/dialogs/connection_dialog.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/dialogs/regex_builder_dialog.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/launcher.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/__init__.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/connection_tree.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/dialogs/__init__.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/dialogs/connection_dialog.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/dialogs/query_manager_dialog.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/dialogs/record_viewer_dialog.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/dialogs/regex_builder_dialog.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/dialogs/settings_dialog.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/icons.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/syntax.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/tab_widget.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/tabs/__init__.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/tabs/spool_tab.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/theme.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/resources/db_ibmi.png +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/resources/db_mysql.png +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/resources/db_postgresql.png +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/resources/db_unknown.png +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/resources/sqlbench.png +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/tabs/__init__.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/tabs/spool_tab.py +0 -0
- {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/tabs/sql_tab.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlbench
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.61
|
|
4
4
|
Summary: A multi-database SQL workbench with support for IBM i, MySQL, and PostgreSQL
|
|
5
5
|
Project-URL: Homepage, https://github.com/jpsteil/sqlbench
|
|
6
6
|
Project-URL: Repository, https://github.com/jpsteil/sqlbench
|
|
@@ -8,7 +8,7 @@ and tabbed query/spool interface.
|
|
|
8
8
|
import subprocess
|
|
9
9
|
import threading
|
|
10
10
|
from typing import Optional, Dict, Any
|
|
11
|
-
from PyQt6.QtCore import Qt,
|
|
11
|
+
from PyQt6.QtCore import Qt, QTimer, pyqtSignal
|
|
12
12
|
from PyQt6.QtGui import QAction, QIcon, QKeySequence, QCloseEvent
|
|
13
13
|
from PyQt6.QtWidgets import (
|
|
14
14
|
QMainWindow,
|
|
@@ -141,25 +141,28 @@ class MainWindow(QMainWindow):
|
|
|
141
141
|
self.status_bar.showMessage("Ready")
|
|
142
142
|
|
|
143
143
|
def _restore_state(self) -> None:
|
|
144
|
-
"""Restore window
|
|
145
|
-
|
|
144
|
+
"""Restore window size (position is compositor-controlled on Wayland)."""
|
|
145
|
+
w = get_setting("window_width")
|
|
146
|
+
h = get_setting("window_height")
|
|
146
147
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
if w and h:
|
|
149
|
+
try:
|
|
150
|
+
w, h = int(w), int(h)
|
|
151
|
+
w = max(w, self.minimumWidth())
|
|
152
|
+
h = max(h, self.minimumHeight())
|
|
153
|
+
# Clamp to fit on screen
|
|
154
|
+
screen = QApplication.primaryScreen().availableGeometry()
|
|
155
|
+
w = min(w, screen.width())
|
|
156
|
+
h = min(h, screen.height())
|
|
157
|
+
self.resize(w, h)
|
|
158
|
+
except (ValueError, TypeError):
|
|
159
|
+
self.resize(1400, 900)
|
|
151
160
|
else:
|
|
152
|
-
# Default size
|
|
153
161
|
self.resize(1400, 900)
|
|
154
|
-
# Center on screen
|
|
155
|
-
screen = QApplication.primaryScreen().geometry()
|
|
156
|
-
self.move(
|
|
157
|
-
(screen.width() - self.width()) // 2,
|
|
158
|
-
(screen.height() - self.height()) // 2
|
|
159
|
-
)
|
|
160
162
|
|
|
161
163
|
# Restore main splitter from ratio
|
|
162
164
|
ratio_str = get_setting("layout_main_ratio")
|
|
165
|
+
|
|
163
166
|
if ratio_str:
|
|
164
167
|
try:
|
|
165
168
|
ratio = float(ratio_str)
|
|
@@ -209,9 +212,9 @@ class MainWindow(QMainWindow):
|
|
|
209
212
|
self._connect(last_conn)
|
|
210
213
|
|
|
211
214
|
def _save_state(self) -> None:
|
|
212
|
-
"""Save window
|
|
213
|
-
|
|
214
|
-
|
|
215
|
+
"""Save window size and state."""
|
|
216
|
+
set_setting("window_width", str(self.width()))
|
|
217
|
+
set_setting("window_height", str(self.height()))
|
|
215
218
|
|
|
216
219
|
# Save main splitter as ratio
|
|
217
220
|
sizes = self.splitter.sizes()
|
|
@@ -332,13 +335,6 @@ class MainWindow(QMainWindow):
|
|
|
332
335
|
self.splitter.setSizes([250, 1000])
|
|
333
336
|
self.resize(1400, 900)
|
|
334
337
|
|
|
335
|
-
# Center on screen
|
|
336
|
-
screen = QApplication.primaryScreen().geometry()
|
|
337
|
-
self.move(
|
|
338
|
-
(screen.width() - self.width()) // 2,
|
|
339
|
-
(screen.height() - self.height()) // 2
|
|
340
|
-
)
|
|
341
|
-
|
|
342
338
|
set_setting("font_size", "13")
|
|
343
339
|
self._apply_font_size()
|
|
344
340
|
self.status_bar.showMessage("Layout reset to defaults", 3000)
|
|
@@ -633,7 +629,12 @@ class MainWindow(QMainWindow):
|
|
|
633
629
|
success, message = result
|
|
634
630
|
self.status_bar.showMessage("Upgrade complete" if success else "Upgrade failed", 3000)
|
|
635
631
|
if success:
|
|
636
|
-
QMessageBox.
|
|
632
|
+
result = QMessageBox.question(
|
|
633
|
+
self, "Upgrade Complete",
|
|
634
|
+
"SQLBench has been upgraded.\n\nWould you like to restart now?",
|
|
635
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
|
636
|
+
if result == QMessageBox.StandardButton.Yes:
|
|
637
|
+
self._restart_app()
|
|
637
638
|
else:
|
|
638
639
|
QMessageBox.warning(self, "Upgrade Failed", message)
|
|
639
640
|
|
|
@@ -642,6 +643,23 @@ class MainWindow(QMainWindow):
|
|
|
642
643
|
thread.start()
|
|
643
644
|
QTimer.singleShot(2000, poll_result)
|
|
644
645
|
|
|
646
|
+
def _restart_app(self) -> None:
|
|
647
|
+
"""Restart the application."""
|
|
648
|
+
import sys
|
|
649
|
+
import os
|
|
650
|
+
|
|
651
|
+
# Save window state
|
|
652
|
+
self._save_state()
|
|
653
|
+
|
|
654
|
+
# Start new process before closing
|
|
655
|
+
if sys.argv[0].endswith('sqlbench') or 'sqlbench' in sys.argv[0]:
|
|
656
|
+
subprocess.Popen([sys.argv[0]])
|
|
657
|
+
else:
|
|
658
|
+
subprocess.Popen([sys.executable, '-m', 'sqlbench'])
|
|
659
|
+
|
|
660
|
+
self.close()
|
|
661
|
+
os._exit(0)
|
|
662
|
+
|
|
645
663
|
def set_status(self, message: str, timeout: int = 0) -> None:
|
|
646
664
|
"""Set status bar message."""
|
|
647
665
|
self.status_bar.showMessage(message, timeout)
|
|
@@ -281,6 +281,8 @@ class ScriptWorker(QThread):
|
|
|
281
281
|
"row_count": 0,
|
|
282
282
|
"success": True,
|
|
283
283
|
"error": None,
|
|
284
|
+
"rows": None,
|
|
285
|
+
"description": None,
|
|
284
286
|
}
|
|
285
287
|
|
|
286
288
|
try:
|
|
@@ -293,6 +295,8 @@ class ScriptWorker(QThread):
|
|
|
293
295
|
rows = cursor.fetchall()
|
|
294
296
|
result["row_count"] = len(rows)
|
|
295
297
|
result["status"] = f"{len(rows)} row(s) returned"
|
|
298
|
+
result["description"] = cursor.description
|
|
299
|
+
result["rows"] = rows[:10000]
|
|
296
300
|
else:
|
|
297
301
|
rc = cursor.rowcount if cursor.rowcount >= 0 else 0
|
|
298
302
|
result["row_count"] = rc
|
|
@@ -370,22 +374,31 @@ class SQLEditor(QPlainTextEdit):
|
|
|
370
374
|
# Shortcuts
|
|
371
375
|
self._setup_shortcuts()
|
|
372
376
|
|
|
377
|
+
def _add_menu_action(self, menu, text, slot, shortcut=None) -> QAction:
|
|
378
|
+
"""Add action to menu with proper PyQt6 overload handling."""
|
|
379
|
+
action = QAction(text, menu)
|
|
380
|
+
if shortcut:
|
|
381
|
+
action.setShortcut(shortcut)
|
|
382
|
+
action.triggered.connect(slot)
|
|
383
|
+
menu.addAction(action)
|
|
384
|
+
return action
|
|
385
|
+
|
|
373
386
|
def _show_context_menu(self, pos) -> None:
|
|
374
387
|
"""Show context menu."""
|
|
375
388
|
menu = QMenu(self)
|
|
376
389
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
390
|
+
undo = self._add_menu_action(menu, "Undo", self.undo, QKeySequence("Ctrl+Z"))
|
|
391
|
+
undo.setEnabled(self.document().isUndoAvailable())
|
|
392
|
+
redo = self._add_menu_action(menu, "Redo", self.redo, QKeySequence("Ctrl+Y"))
|
|
393
|
+
redo.setEnabled(self.document().isRedoAvailable())
|
|
381
394
|
menu.addSeparator()
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
395
|
+
self._add_menu_action(menu, "Cut", self.cut, QKeySequence("Ctrl+X"))
|
|
396
|
+
self._add_menu_action(menu, "Copy", self.copy, QKeySequence("Ctrl+C"))
|
|
397
|
+
self._add_menu_action(menu, "Paste", self.paste, QKeySequence("Ctrl+V"))
|
|
385
398
|
menu.addSeparator()
|
|
386
|
-
|
|
399
|
+
self._add_menu_action(menu, "Select All", self.selectAll, QKeySequence("Ctrl+A"))
|
|
387
400
|
menu.addSeparator()
|
|
388
|
-
|
|
401
|
+
self._add_menu_action(menu, "Find...", self.find_requested.emit, QKeySequence("Ctrl+F"))
|
|
389
402
|
|
|
390
403
|
menu.exec(self.mapToGlobal(pos))
|
|
391
404
|
|
|
@@ -399,16 +412,6 @@ class SQLEditor(QPlainTextEdit):
|
|
|
399
412
|
shortcut_exec_all = QShortcut(QKeySequence("Ctrl+F5"), self)
|
|
400
413
|
shortcut_exec_all.activated.connect(self.execute_all_requested.emit)
|
|
401
414
|
|
|
402
|
-
# Ctrl+Z - Undo
|
|
403
|
-
shortcut_undo = QShortcut(QKeySequence("Ctrl+Z"), self)
|
|
404
|
-
shortcut_undo.activated.connect(self.undo)
|
|
405
|
-
|
|
406
|
-
# Ctrl+Y or Ctrl+Shift+Z - Redo
|
|
407
|
-
shortcut_redo = QShortcut(QKeySequence("Ctrl+Y"), self)
|
|
408
|
-
shortcut_redo.activated.connect(self.redo)
|
|
409
|
-
shortcut_redo2 = QShortcut(QKeySequence("Ctrl+Shift+Z"), self)
|
|
410
|
-
shortcut_redo2.activated.connect(self.redo)
|
|
411
|
-
|
|
412
415
|
def update_theme(self) -> None:
|
|
413
416
|
"""Update colors when theme changes."""
|
|
414
417
|
self.highlighter.update_theme()
|
|
@@ -532,10 +535,20 @@ class ResultsTable(QTableWidget):
|
|
|
532
535
|
def _show_context_menu(self, pos) -> None:
|
|
533
536
|
"""Show context menu."""
|
|
534
537
|
menu = QMenu(self)
|
|
535
|
-
|
|
538
|
+
|
|
539
|
+
copy_act = QAction("Copy", menu)
|
|
540
|
+
copy_act.setShortcut(QKeySequence("Ctrl+C"))
|
|
541
|
+
copy_act.triggered.connect(self._copy_selection)
|
|
542
|
+
menu.addAction(copy_act)
|
|
543
|
+
|
|
536
544
|
menu.addAction("Copy with Headers", self._copy_with_headers)
|
|
537
545
|
menu.addSeparator()
|
|
538
|
-
|
|
546
|
+
|
|
547
|
+
sel_act = QAction("Select All", menu)
|
|
548
|
+
sel_act.setShortcut(QKeySequence("Ctrl+A"))
|
|
549
|
+
sel_act.triggered.connect(self.selectAll)
|
|
550
|
+
menu.addAction(sel_act)
|
|
551
|
+
|
|
539
552
|
menu.exec(self.mapToGlobal(pos))
|
|
540
553
|
|
|
541
554
|
def _copy_selection(self) -> None:
|
|
@@ -693,6 +706,8 @@ class SQLTab(QWidget):
|
|
|
693
706
|
self._original_values: dict = {} # row_index -> original row tuple
|
|
694
707
|
self._modified_cells: dict = {} # row_index -> {col_index: new_value}
|
|
695
708
|
self._loading_results = False # guard flag to ignore cellChanged during loads
|
|
709
|
+
self._in_script_mode = False
|
|
710
|
+
self._script_results = [] # result dicts for sub-tab field lookups
|
|
696
711
|
|
|
697
712
|
self._setup_ui()
|
|
698
713
|
self._connect_signals()
|
|
@@ -841,13 +856,19 @@ class SQLTab(QWidget):
|
|
|
841
856
|
results_layout.setSpacing(0)
|
|
842
857
|
|
|
843
858
|
# Results controls
|
|
844
|
-
|
|
845
|
-
results_layout.addWidget(
|
|
859
|
+
self._results_controls = self._create_results_controls()
|
|
860
|
+
results_layout.addWidget(self._results_controls)
|
|
846
861
|
|
|
847
862
|
# Results table
|
|
848
863
|
self.results_table = ResultsTable()
|
|
849
864
|
results_layout.addWidget(self.results_table)
|
|
850
865
|
|
|
866
|
+
# Script sub-tabs (hidden by default, shown in script mode)
|
|
867
|
+
self._script_sub_tabs = QTabWidget()
|
|
868
|
+
self._script_sub_tabs.setDocumentMode(True)
|
|
869
|
+
self._script_sub_tabs.hide()
|
|
870
|
+
results_layout.addWidget(self._script_sub_tabs)
|
|
871
|
+
|
|
851
872
|
# Status label
|
|
852
873
|
self.results_status = QLabel("No results")
|
|
853
874
|
self.results_status.setProperty("subheading", True)
|
|
@@ -1178,6 +1199,14 @@ class SQLTab(QWidget):
|
|
|
1178
1199
|
self.results_table.verticalHeader().setDefaultSectionSize(row_height)
|
|
1179
1200
|
self.fields_table.verticalHeader().setDefaultSectionSize(row_height)
|
|
1180
1201
|
|
|
1202
|
+
# Scale any visible script sub-tab tables
|
|
1203
|
+
if self._in_script_mode:
|
|
1204
|
+
for i in range(self._script_sub_tabs.count()):
|
|
1205
|
+
w = self._script_sub_tabs.widget(i)
|
|
1206
|
+
if isinstance(w, ResultsTable):
|
|
1207
|
+
w.scale_columns(scale)
|
|
1208
|
+
w.verticalHeader().setDefaultSectionSize(row_height)
|
|
1209
|
+
|
|
1181
1210
|
# Update stats font
|
|
1182
1211
|
font = self.stats_text.font()
|
|
1183
1212
|
font.setPointSize(size)
|
|
@@ -1219,6 +1248,93 @@ class SQLTab(QWidget):
|
|
|
1219
1248
|
self._script_worker.error.connect(self._on_query_error)
|
|
1220
1249
|
self._script_worker.start()
|
|
1221
1250
|
|
|
1251
|
+
def _enter_script_mode(self, results: List) -> None:
|
|
1252
|
+
"""Switch to script sub-tab layout."""
|
|
1253
|
+
self._in_script_mode = True
|
|
1254
|
+
self._script_results = results
|
|
1255
|
+
|
|
1256
|
+
# Hide single-query controls and table
|
|
1257
|
+
self._results_controls.hide()
|
|
1258
|
+
self.results_table.hide()
|
|
1259
|
+
|
|
1260
|
+
# Clear previous sub-tabs
|
|
1261
|
+
try:
|
|
1262
|
+
self._script_sub_tabs.currentChanged.disconnect(self._on_script_sub_tab_changed)
|
|
1263
|
+
except TypeError:
|
|
1264
|
+
pass
|
|
1265
|
+
while self._script_sub_tabs.count():
|
|
1266
|
+
w = self._script_sub_tabs.widget(0)
|
|
1267
|
+
self._script_sub_tabs.removeTab(0)
|
|
1268
|
+
w.deleteLater()
|
|
1269
|
+
|
|
1270
|
+
# Summary tab
|
|
1271
|
+
summary_table = ResultsTable()
|
|
1272
|
+
summary_table.setSortingEnabled(False)
|
|
1273
|
+
summary_table.setColumnCount(4)
|
|
1274
|
+
summary_table.setHorizontalHeaderLabels(["#", "SQL", "Result", "Time"])
|
|
1275
|
+
summary_table.setRowCount(len(results))
|
|
1276
|
+
for i, r in enumerate(results):
|
|
1277
|
+
summary_table.setItem(i, 0, QTableWidgetItem(str(r["stmt"])))
|
|
1278
|
+
summary_table.setItem(i, 1, QTableWidgetItem(r["sql"]))
|
|
1279
|
+
status_item = QTableWidgetItem(r["status"])
|
|
1280
|
+
if not r["success"]:
|
|
1281
|
+
status_item.setForeground(QColor(255, 80, 80))
|
|
1282
|
+
summary_table.setItem(i, 2, status_item)
|
|
1283
|
+
summary_table.setItem(i, 3, QTableWidgetItem(f"{r['time']:.3f}s"))
|
|
1284
|
+
summary_table.resizeColumnsToContents()
|
|
1285
|
+
for col in range(summary_table.columnCount()):
|
|
1286
|
+
if summary_table.columnWidth(col) > 400:
|
|
1287
|
+
summary_table.setColumnWidth(col, 400)
|
|
1288
|
+
summary_table.setSortingEnabled(True)
|
|
1289
|
+
self._script_sub_tabs.addTab(summary_table, "Summary")
|
|
1290
|
+
|
|
1291
|
+
# Per-SELECT result tabs
|
|
1292
|
+
for r in results:
|
|
1293
|
+
if r["rows"] is not None and r["description"] is not None:
|
|
1294
|
+
table = ResultsTable()
|
|
1295
|
+
table.load_results(r["rows"], r["description"])
|
|
1296
|
+
label = f"#{r['stmt']} {r['sql'][:40]}"
|
|
1297
|
+
self._script_sub_tabs.addTab(table, label)
|
|
1298
|
+
|
|
1299
|
+
# Connect tab-changed signal for Fields updates
|
|
1300
|
+
self._script_sub_tabs.currentChanged.connect(self._on_script_sub_tab_changed)
|
|
1301
|
+
|
|
1302
|
+
self._script_sub_tabs.show()
|
|
1303
|
+
|
|
1304
|
+
def _exit_script_mode(self) -> None:
|
|
1305
|
+
"""Restore single-query layout."""
|
|
1306
|
+
if not self._in_script_mode:
|
|
1307
|
+
return
|
|
1308
|
+
self._in_script_mode = False
|
|
1309
|
+
self._script_results = []
|
|
1310
|
+
|
|
1311
|
+
# Clear and hide sub-tabs
|
|
1312
|
+
self._script_sub_tabs.hide()
|
|
1313
|
+
while self._script_sub_tabs.count():
|
|
1314
|
+
w = self._script_sub_tabs.widget(0)
|
|
1315
|
+
self._script_sub_tabs.removeTab(0)
|
|
1316
|
+
w.deleteLater()
|
|
1317
|
+
|
|
1318
|
+
# Restore single-query widgets
|
|
1319
|
+
self._results_controls.show()
|
|
1320
|
+
self.results_table.show()
|
|
1321
|
+
|
|
1322
|
+
def _on_script_sub_tab_changed(self, index: int) -> None:
|
|
1323
|
+
"""Update Fields tab when a script sub-tab is selected."""
|
|
1324
|
+
if index == 0:
|
|
1325
|
+
# Summary tab — clear fields
|
|
1326
|
+
self._update_fields(None)
|
|
1327
|
+
return
|
|
1328
|
+
# Map sub-tab index to the result that has rows
|
|
1329
|
+
select_idx = 0
|
|
1330
|
+
for r in self._script_results:
|
|
1331
|
+
if r["rows"] is not None and r["description"] is not None:
|
|
1332
|
+
select_idx += 1
|
|
1333
|
+
if select_idx == index:
|
|
1334
|
+
self._update_fields(r["description"])
|
|
1335
|
+
return
|
|
1336
|
+
self._update_fields(None)
|
|
1337
|
+
|
|
1222
1338
|
def _on_script_finished(self, results: List, total_time: float) -> None:
|
|
1223
1339
|
"""Handle script execution completion."""
|
|
1224
1340
|
self._reset_buttons()
|
|
@@ -1237,29 +1353,8 @@ class SQLTab(QWidget):
|
|
|
1237
1353
|
except Exception:
|
|
1238
1354
|
pass
|
|
1239
1355
|
|
|
1240
|
-
#
|
|
1241
|
-
self.
|
|
1242
|
-
self.results_table.clear()
|
|
1243
|
-
self.results_table.setSortingEnabled(False)
|
|
1244
|
-
self.results_table.setColumnCount(4)
|
|
1245
|
-
self.results_table.setHorizontalHeaderLabels(["#", "SQL", "Result", "Time"])
|
|
1246
|
-
self.results_table.setRowCount(len(results))
|
|
1247
|
-
|
|
1248
|
-
for i, r in enumerate(results):
|
|
1249
|
-
self.results_table.setItem(i, 0, QTableWidgetItem(str(r["stmt"])))
|
|
1250
|
-
self.results_table.setItem(i, 1, QTableWidgetItem(r["sql"]))
|
|
1251
|
-
status_item = QTableWidgetItem(r["status"])
|
|
1252
|
-
if not r["success"]:
|
|
1253
|
-
status_item.setForeground(QColor(255, 80, 80))
|
|
1254
|
-
self.results_table.setItem(i, 2, status_item)
|
|
1255
|
-
self.results_table.setItem(i, 3, QTableWidgetItem(f"{r['time']:.3f}s"))
|
|
1256
|
-
|
|
1257
|
-
self.results_table.resizeColumnsToContents()
|
|
1258
|
-
for col in range(self.results_table.columnCount()):
|
|
1259
|
-
if self.results_table.columnWidth(col) > 400:
|
|
1260
|
-
self.results_table.setColumnWidth(col, 400)
|
|
1261
|
-
self.results_table.setSortingEnabled(True)
|
|
1262
|
-
self._loading_results = False
|
|
1356
|
+
# Build script sub-tabs (summary + per-SELECT result tabs)
|
|
1357
|
+
self._enter_script_mode(results)
|
|
1263
1358
|
|
|
1264
1359
|
# Update statistics
|
|
1265
1360
|
success = sum(1 for r in results if r["success"])
|
|
@@ -1370,6 +1465,7 @@ class SQLTab(QWidget):
|
|
|
1370
1465
|
exec_time: float, fetch_time: float,
|
|
1371
1466
|
total_rows: int = 0) -> None:
|
|
1372
1467
|
"""Handle query completion."""
|
|
1468
|
+
self._exit_script_mode()
|
|
1373
1469
|
self._reset_buttons()
|
|
1374
1470
|
|
|
1375
1471
|
# Log to database
|
|
@@ -1561,6 +1657,7 @@ class SQLTab(QWidget):
|
|
|
1561
1657
|
exec_time: float, fetch_time: float,
|
|
1562
1658
|
total_rows: int = 0) -> None:
|
|
1563
1659
|
"""Handle page load completion."""
|
|
1660
|
+
self._exit_script_mode()
|
|
1564
1661
|
self._reset_buttons()
|
|
1565
1662
|
|
|
1566
1663
|
self._loading_results = True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|