sqlbench 0.1.7__tar.gz → 0.1.8__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.7 → sqlbench-0.1.8}/PKG-INFO +1 -6
- {sqlbench-0.1.7 → sqlbench-0.1.8}/pyproject.toml +2 -5
- {sqlbench-0.1.7 → sqlbench-0.1.8}/sqlbench/adapters.py +0 -130
- {sqlbench-0.1.7 → sqlbench-0.1.8}/sqlbench/app.py +51 -5
- {sqlbench-0.1.7 → sqlbench-0.1.8}/sqlbench/dialogs/connection_dialog.py +11 -109
- {sqlbench-0.1.7 → sqlbench-0.1.8}/sqlbench/version.py +1 -1
- {sqlbench-0.1.7 → sqlbench-0.1.8}/.github/workflows/pypi-publish.yml +0 -0
- {sqlbench-0.1.7 → sqlbench-0.1.8}/.gitignore +0 -0
- {sqlbench-0.1.7 → sqlbench-0.1.8}/Makefile +0 -0
- {sqlbench-0.1.7 → sqlbench-0.1.8}/README.md +0 -0
- {sqlbench-0.1.7 → sqlbench-0.1.8}/requirements.txt +0 -0
- {sqlbench-0.1.7 → sqlbench-0.1.8}/sqlbench/__init__.py +0 -0
- {sqlbench-0.1.7 → sqlbench-0.1.8}/sqlbench/__main__.py +0 -0
- {sqlbench-0.1.7 → sqlbench-0.1.8}/sqlbench/database.py +0 -0
- {sqlbench-0.1.7 → sqlbench-0.1.8}/sqlbench/dialogs/__init__.py +0 -0
- {sqlbench-0.1.7 → sqlbench-0.1.8}/sqlbench/dialogs/regex_builder_dialog.py +0 -0
- {sqlbench-0.1.7 → sqlbench-0.1.8}/sqlbench/launcher.py +0 -0
- {sqlbench-0.1.7 → sqlbench-0.1.8}/sqlbench/resources/sqlbench.png +0 -0
- {sqlbench-0.1.7 → sqlbench-0.1.8}/sqlbench/tabs/__init__.py +0 -0
- {sqlbench-0.1.7 → sqlbench-0.1.8}/sqlbench/tabs/spool_tab.py +0 -0
- {sqlbench-0.1.7 → sqlbench-0.1.8}/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.8
|
|
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
|
|
@@ -24,23 +24,18 @@ Classifier: Topic :: Database :: Front-Ends
|
|
|
24
24
|
Requires-Python: >=3.9
|
|
25
25
|
Requires-Dist: sqlparse>=0.5.0
|
|
26
26
|
Provides-Extra: all
|
|
27
|
-
Requires-Dist: ibm-db>=3.0.0; extra == 'all'
|
|
28
27
|
Requires-Dist: mysql-connector-python>=8.0.0; extra == 'all'
|
|
29
28
|
Requires-Dist: openpyxl>=3.1.0; extra == 'all'
|
|
30
29
|
Requires-Dist: psycopg2-binary>=2.9.0; extra == 'all'
|
|
31
30
|
Requires-Dist: pyodbc>=4.0.0; extra == 'all'
|
|
32
|
-
Requires-Dist: reportlab>=4.0.0; extra == 'all'
|
|
33
31
|
Provides-Extra: dev
|
|
34
32
|
Requires-Dist: black>=23.0.0; extra == 'dev'
|
|
35
33
|
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
36
34
|
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
37
35
|
Provides-Extra: export
|
|
38
36
|
Requires-Dist: openpyxl>=3.1.0; extra == 'export'
|
|
39
|
-
Requires-Dist: reportlab>=4.0.0; extra == 'export'
|
|
40
37
|
Provides-Extra: ibmi
|
|
41
38
|
Requires-Dist: pyodbc>=4.0.0; extra == 'ibmi'
|
|
42
|
-
Provides-Extra: ibmi-db
|
|
43
|
-
Requires-Dist: ibm-db>=3.0.0; extra == 'ibmi-db'
|
|
44
39
|
Provides-Extra: mysql
|
|
45
40
|
Requires-Dist: mysql-connector-python>=8.0.0; extra == 'mysql'
|
|
46
41
|
Provides-Extra: postgresql
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sqlbench"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.8"
|
|
8
8
|
description = "A multi-database SQL workbench with support for IBM i, MySQL, and PostgreSQL"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -36,14 +36,11 @@ dependencies = [
|
|
|
36
36
|
mysql = ["mysql-connector-python>=8.0.0"]
|
|
37
37
|
postgresql = ["psycopg2-binary>=2.9.0"]
|
|
38
38
|
ibmi = ["pyodbc>=4.0.0"]
|
|
39
|
-
|
|
40
|
-
export = ["reportlab>=4.0.0", "openpyxl>=3.1.0"]
|
|
39
|
+
export = ["openpyxl>=3.1.0"]
|
|
41
40
|
all = [
|
|
42
41
|
"mysql-connector-python>=8.0.0",
|
|
43
42
|
"psycopg2-binary>=2.9.0",
|
|
44
43
|
"pyodbc>=4.0.0",
|
|
45
|
-
"ibm_db>=3.0.0",
|
|
46
|
-
"reportlab>=4.0.0",
|
|
47
44
|
"openpyxl>=3.1.0",
|
|
48
45
|
]
|
|
49
46
|
dev = [
|
|
@@ -1,32 +1,6 @@
|
|
|
1
1
|
"""Database adapters for different database types."""
|
|
2
2
|
|
|
3
|
-
import os
|
|
4
3
|
from abc import ABC, abstractmethod
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def _setup_ibm_db_environment():
|
|
9
|
-
"""Set up environment variables for ibm_db if clidriver is installed."""
|
|
10
|
-
if os.environ.get("IBM_DB_HOME"):
|
|
11
|
-
return # Already configured
|
|
12
|
-
|
|
13
|
-
# Check common install locations
|
|
14
|
-
clidriver_paths = [
|
|
15
|
-
Path.home() / "db2drivers" / "clidriver",
|
|
16
|
-
Path("/opt/ibm/db2/clidriver"),
|
|
17
|
-
Path("/opt/clidriver"),
|
|
18
|
-
]
|
|
19
|
-
|
|
20
|
-
for cli_path in clidriver_paths:
|
|
21
|
-
if (cli_path / "lib").exists():
|
|
22
|
-
os.environ["IBM_DB_HOME"] = str(cli_path)
|
|
23
|
-
lib_path = os.environ.get("LD_LIBRARY_PATH", "")
|
|
24
|
-
os.environ["LD_LIBRARY_PATH"] = f"{cli_path}/lib:{lib_path}"
|
|
25
|
-
break
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
# Set up ibm_db environment before any imports
|
|
29
|
-
_setup_ibm_db_environment()
|
|
30
4
|
|
|
31
5
|
|
|
32
6
|
class DBAdapter(ABC):
|
|
@@ -201,109 +175,6 @@ class IBMiAdapter(DBAdapter):
|
|
|
201
175
|
"""
|
|
202
176
|
|
|
203
177
|
|
|
204
|
-
class IBMiDBAdapter(DBAdapter):
|
|
205
|
-
"""Adapter for IBM i (AS/400) via ibm_db (native CLI driver)."""
|
|
206
|
-
|
|
207
|
-
db_type = "ibmi_db"
|
|
208
|
-
display_name = "IBM i (ibm_db)"
|
|
209
|
-
default_port = 446 # DRDA port
|
|
210
|
-
requires_database = False
|
|
211
|
-
supports_spool = True
|
|
212
|
-
required_module = "ibm_db"
|
|
213
|
-
install_hint = "pip install sqlbench[ibmi-db]"
|
|
214
|
-
|
|
215
|
-
def connect(self, host, user, password, port=None, database=None):
|
|
216
|
-
import ibm_db_dbi
|
|
217
|
-
|
|
218
|
-
# Build connection string for IBM i
|
|
219
|
-
# DATABASE can be *LOCAL or the RDB name
|
|
220
|
-
db_name = database if database else "*LOCAL"
|
|
221
|
-
port_num = port if port else 446
|
|
222
|
-
|
|
223
|
-
conn_str = (
|
|
224
|
-
f"DATABASE={db_name};"
|
|
225
|
-
f"HOSTNAME={host};"
|
|
226
|
-
f"PORT={port_num};"
|
|
227
|
-
f"PROTOCOL=TCPIP;"
|
|
228
|
-
f"UID={user};"
|
|
229
|
-
f"PWD={password};"
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
# ibm_db_dbi provides DB-API 2.0 compliant interface
|
|
233
|
-
return ibm_db_dbi.connect(conn_str, "", "")
|
|
234
|
-
|
|
235
|
-
def get_version(self, conn):
|
|
236
|
-
try:
|
|
237
|
-
cursor = conn.cursor()
|
|
238
|
-
cursor.execute("SELECT OS_VERSION, OS_RELEASE FROM SYSIBMADM.ENV_SYS_INFO")
|
|
239
|
-
row = cursor.fetchone()
|
|
240
|
-
cursor.close()
|
|
241
|
-
if row:
|
|
242
|
-
return f"{row[0]}.{row[1]}"
|
|
243
|
-
except Exception:
|
|
244
|
-
pass
|
|
245
|
-
return None
|
|
246
|
-
|
|
247
|
-
def add_pagination(self, sql, limit, offset=0):
|
|
248
|
-
"""IBM i uses OFFSET/FETCH syntax."""
|
|
249
|
-
sql_stripped = sql.strip()
|
|
250
|
-
while sql_stripped.endswith(';'):
|
|
251
|
-
sql_stripped = sql_stripped[:-1].strip()
|
|
252
|
-
|
|
253
|
-
if offset > 0:
|
|
254
|
-
return f"{sql_stripped} OFFSET {offset} ROWS FETCH FIRST {limit} ROWS ONLY"
|
|
255
|
-
return f"{sql_stripped} FETCH FIRST {limit} ROWS ONLY"
|
|
256
|
-
|
|
257
|
-
def get_select_limit_query(self, table_ref, limit):
|
|
258
|
-
"""Get a SELECT query with row limit for IBM i."""
|
|
259
|
-
return f"SELECT * FROM {table_ref} FETCH FIRST {limit} ROWS ONLY"
|
|
260
|
-
|
|
261
|
-
def get_version_query(self):
|
|
262
|
-
"""Get the SQL to retrieve IBM i version."""
|
|
263
|
-
return "SELECT OS_VERSION || '.' || OS_RELEASE FROM SYSIBMADM.ENV_SYS_INFO"
|
|
264
|
-
|
|
265
|
-
def get_columns_query(self, tables):
|
|
266
|
-
if not tables:
|
|
267
|
-
return None
|
|
268
|
-
|
|
269
|
-
table_conditions = []
|
|
270
|
-
for table in tables:
|
|
271
|
-
if '.' in table:
|
|
272
|
-
schema, tbl = table.split('.', 1)
|
|
273
|
-
table_conditions.append(
|
|
274
|
-
f"(TABLE_SCHEMA = '{schema.upper()}' AND TABLE_NAME = '{tbl.upper()}')"
|
|
275
|
-
)
|
|
276
|
-
else:
|
|
277
|
-
table_conditions.append(f"TABLE_NAME = '{table.upper()}'")
|
|
278
|
-
|
|
279
|
-
where_clause = " OR ".join(table_conditions)
|
|
280
|
-
return f"""
|
|
281
|
-
SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE, LENGTH, NUMERIC_SCALE
|
|
282
|
-
FROM QSYS2.SYSCOLUMNS
|
|
283
|
-
WHERE {where_clause}
|
|
284
|
-
ORDER BY TABLE_SCHEMA, TABLE_NAME, ORDINAL_POSITION
|
|
285
|
-
"""
|
|
286
|
-
|
|
287
|
-
def get_tables_query(self):
|
|
288
|
-
"""Get tables from IBM i - returns schema, table_name, table_type."""
|
|
289
|
-
return """
|
|
290
|
-
SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE
|
|
291
|
-
FROM QSYS2.SYSTABLES
|
|
292
|
-
WHERE TABLE_TYPE IN ('T', 'P', 'V')
|
|
293
|
-
ORDER BY TABLE_SCHEMA, TABLE_NAME
|
|
294
|
-
"""
|
|
295
|
-
|
|
296
|
-
def is_numeric_type(self, type_code):
|
|
297
|
-
"""Check if a type_code represents a numeric type for ibm_db."""
|
|
298
|
-
# ibm_db uses type codes similar to CLI
|
|
299
|
-
# Check for Python numeric types as fallback
|
|
300
|
-
from decimal import Decimal
|
|
301
|
-
if isinstance(type_code, type):
|
|
302
|
-
return type_code in (int, float, Decimal)
|
|
303
|
-
# ibm_db type codes for numeric: various integer constants
|
|
304
|
-
return False
|
|
305
|
-
|
|
306
|
-
|
|
307
178
|
class MySQLAdapter(DBAdapter):
|
|
308
179
|
"""Adapter for MySQL."""
|
|
309
180
|
|
|
@@ -513,7 +384,6 @@ class PostgreSQLAdapter(DBAdapter):
|
|
|
513
384
|
# Registry of available adapters
|
|
514
385
|
ADAPTERS = {
|
|
515
386
|
'ibmi': IBMiAdapter,
|
|
516
|
-
'ibmi_db': IBMiDBAdapter,
|
|
517
387
|
'mysql': MySQLAdapter,
|
|
518
388
|
'postgresql': PostgreSQLAdapter,
|
|
519
389
|
}
|
|
@@ -1037,11 +1037,7 @@ class SQLBenchApp:
|
|
|
1037
1037
|
text=True
|
|
1038
1038
|
)
|
|
1039
1039
|
if result.returncode == 0:
|
|
1040
|
-
self.root.after(0,
|
|
1041
|
-
"Upgrade Complete",
|
|
1042
|
-
"SQLBench has been upgraded.\n\n"
|
|
1043
|
-
"Please restart the application to use the new version."
|
|
1044
|
-
))
|
|
1040
|
+
self.root.after(0, self._show_restart_dialog)
|
|
1045
1041
|
else:
|
|
1046
1042
|
error = result.stderr or result.stdout or "Unknown error"
|
|
1047
1043
|
self.root.after(0, lambda: tk.messagebox.showerror(
|
|
@@ -1068,6 +1064,56 @@ class SQLBenchApp:
|
|
|
1068
1064
|
from sqlbench.version import __version__
|
|
1069
1065
|
tk.messagebox.showinfo("About", f"SQLBench v{__version__}\nMulti-database SQL Workbench")
|
|
1070
1066
|
|
|
1067
|
+
def _show_restart_dialog(self, message="SQLBench has been upgraded.\n\nPlease restart the application to use the new version."):
|
|
1068
|
+
"""Show a dialog with restart option."""
|
|
1069
|
+
dialog = tk.Toplevel(self.root)
|
|
1070
|
+
dialog.title("Restart Required")
|
|
1071
|
+
dialog.transient(self.root)
|
|
1072
|
+
dialog.grab_set()
|
|
1073
|
+
dialog.resizable(False, False)
|
|
1074
|
+
|
|
1075
|
+
# Message
|
|
1076
|
+
msg_frame = ttk.Frame(dialog, padding=20)
|
|
1077
|
+
msg_frame.pack(fill=tk.BOTH, expand=True)
|
|
1078
|
+
ttk.Label(msg_frame, text=message, wraplength=300).pack()
|
|
1079
|
+
|
|
1080
|
+
# Buttons
|
|
1081
|
+
btn_frame = ttk.Frame(dialog, padding=(20, 0, 20, 20))
|
|
1082
|
+
btn_frame.pack(fill=tk.X)
|
|
1083
|
+
ttk.Button(btn_frame, text="Restart Now", command=lambda: self._restart_app()).pack(side=tk.LEFT, padx=5)
|
|
1084
|
+
ttk.Button(btn_frame, text="Later", command=dialog.destroy).pack(side=tk.LEFT, padx=5)
|
|
1085
|
+
|
|
1086
|
+
# Center on parent
|
|
1087
|
+
dialog.update_idletasks()
|
|
1088
|
+
w = dialog.winfo_width()
|
|
1089
|
+
h = dialog.winfo_height()
|
|
1090
|
+
x = self.root.winfo_x() + (self.root.winfo_width() - w) // 2
|
|
1091
|
+
y = self.root.winfo_y() + (self.root.winfo_height() - h) // 2
|
|
1092
|
+
dialog.geometry(f"+{x}+{y}")
|
|
1093
|
+
|
|
1094
|
+
def _restart_app(self):
|
|
1095
|
+
"""Restart the application."""
|
|
1096
|
+
import sys
|
|
1097
|
+
import os
|
|
1098
|
+
|
|
1099
|
+
# Save current state before restart
|
|
1100
|
+
self._save_geometry()
|
|
1101
|
+
self._save_active_tab()
|
|
1102
|
+
self._save_connection_state()
|
|
1103
|
+
|
|
1104
|
+
# Close connections gracefully
|
|
1105
|
+
for name in list(self.connections.keys()):
|
|
1106
|
+
try:
|
|
1107
|
+
self.connections[name]['conn'].close()
|
|
1108
|
+
except Exception:
|
|
1109
|
+
pass
|
|
1110
|
+
|
|
1111
|
+
self.root.destroy()
|
|
1112
|
+
|
|
1113
|
+
# Re-execute the application
|
|
1114
|
+
python = sys.executable
|
|
1115
|
+
os.execv(python, [python] + sys.argv)
|
|
1116
|
+
|
|
1071
1117
|
def _show_settings(self):
|
|
1072
1118
|
"""Show settings dialog."""
|
|
1073
1119
|
settings_win = tk.Toplevel(self.root)
|
|
@@ -206,7 +206,7 @@ class ConnectionDialog:
|
|
|
206
206
|
for conn in self._connections:
|
|
207
207
|
# Show type indicator
|
|
208
208
|
db_type = conn.get("db_type", "ibmi")
|
|
209
|
-
type_indicator = {"ibmi": "[i]", "
|
|
209
|
+
type_indicator = {"ibmi": "[i]", "mysql": "[M]", "postgresql": "[P]"}.get(db_type, "[?]")
|
|
210
210
|
# Mark unavailable connections
|
|
211
211
|
if db_type not in available_types:
|
|
212
212
|
type_indicator = "[!]"
|
|
@@ -432,97 +432,6 @@ class ConnectionDialog:
|
|
|
432
432
|
status_text.config(state=tk.DISABLED)
|
|
433
433
|
dialog.update()
|
|
434
434
|
|
|
435
|
-
def install_clidriver(log_status):
|
|
436
|
-
"""Download and install IBM Db2 clidriver for ibm_db."""
|
|
437
|
-
import platform
|
|
438
|
-
import tarfile
|
|
439
|
-
import urllib.request
|
|
440
|
-
from pathlib import Path
|
|
441
|
-
|
|
442
|
-
# Determine platform and download URL
|
|
443
|
-
system = platform.system().lower()
|
|
444
|
-
machine = platform.machine().lower()
|
|
445
|
-
|
|
446
|
-
if system == "linux" and machine in ("x86_64", "amd64"):
|
|
447
|
-
url = "https://public.dhe.ibm.com/ibmdl/export/pub/software/data/db2/drivers/odbc_cli/linuxx64_odbc_cli.tar.gz"
|
|
448
|
-
elif system == "linux" and machine in ("aarch64", "arm64"):
|
|
449
|
-
url = "https://public.dhe.ibm.com/ibmdl/export/pub/software/data/db2/drivers/odbc_cli/linuxarm64_odbc_cli.tar.gz"
|
|
450
|
-
elif system == "darwin" and machine in ("x86_64", "amd64"):
|
|
451
|
-
url = "https://public.dhe.ibm.com/ibmdl/export/pub/software/data/db2/drivers/odbc_cli/macos64_odbc_cli.tar.gz"
|
|
452
|
-
elif system == "darwin" and machine == "arm64":
|
|
453
|
-
# M1/M2 Mac - use x86_64 version with Rosetta or native if available
|
|
454
|
-
url = "https://public.dhe.ibm.com/ibmdl/export/pub/software/data/db2/drivers/odbc_cli/macos64_odbc_cli.tar.gz"
|
|
455
|
-
elif system == "windows":
|
|
456
|
-
log_status(" Windows: Please download clidriver manually from IBM")
|
|
457
|
-
return False
|
|
458
|
-
else:
|
|
459
|
-
log_status(f" Unsupported platform: {system}/{machine}")
|
|
460
|
-
return False
|
|
461
|
-
|
|
462
|
-
# Set up paths
|
|
463
|
-
home = Path.home()
|
|
464
|
-
driver_dir = home / "db2drivers"
|
|
465
|
-
clidriver_path = driver_dir / "clidriver"
|
|
466
|
-
tar_file = driver_dir / "odbc_cli.tar.gz"
|
|
467
|
-
|
|
468
|
-
# Check if already installed
|
|
469
|
-
if (clidriver_path / "lib").exists():
|
|
470
|
-
log_status(" clidriver already installed")
|
|
471
|
-
return True
|
|
472
|
-
|
|
473
|
-
try:
|
|
474
|
-
# Create directory
|
|
475
|
-
driver_dir.mkdir(parents=True, exist_ok=True)
|
|
476
|
-
|
|
477
|
-
# Download
|
|
478
|
-
log_status(" Downloading clidriver (~100MB)...")
|
|
479
|
-
urllib.request.urlretrieve(url, tar_file)
|
|
480
|
-
|
|
481
|
-
# Extract
|
|
482
|
-
log_status(" Extracting...")
|
|
483
|
-
with tarfile.open(tar_file, "r:gz") as tar:
|
|
484
|
-
tar.extractall(path=driver_dir)
|
|
485
|
-
|
|
486
|
-
# Clean up tar file
|
|
487
|
-
tar_file.unlink()
|
|
488
|
-
|
|
489
|
-
# Set environment variable hint
|
|
490
|
-
log_status(" clidriver installed to ~/db2drivers/clidriver")
|
|
491
|
-
|
|
492
|
-
# Update environment for current process
|
|
493
|
-
import os
|
|
494
|
-
cli_path = str(clidriver_path)
|
|
495
|
-
os.environ["IBM_DB_HOME"] = cli_path
|
|
496
|
-
lib_path = os.environ.get("LD_LIBRARY_PATH", "")
|
|
497
|
-
os.environ["LD_LIBRARY_PATH"] = f"{cli_path}/lib:{lib_path}"
|
|
498
|
-
|
|
499
|
-
# Add to shell config
|
|
500
|
-
shell_config = home / ".bashrc"
|
|
501
|
-
if not shell_config.exists():
|
|
502
|
-
shell_config = home / ".profile"
|
|
503
|
-
|
|
504
|
-
config_lines = [
|
|
505
|
-
f'\n# IBM Db2 clidriver (added by SQLBench)',
|
|
506
|
-
f'export IBM_DB_HOME=~/db2drivers/clidriver',
|
|
507
|
-
f'export LD_LIBRARY_PATH=$IBM_DB_HOME/lib:$LD_LIBRARY_PATH',
|
|
508
|
-
]
|
|
509
|
-
|
|
510
|
-
# Check if already in config
|
|
511
|
-
try:
|
|
512
|
-
existing = shell_config.read_text() if shell_config.exists() else ""
|
|
513
|
-
if "IBM_DB_HOME" not in existing:
|
|
514
|
-
with open(shell_config, "a") as f:
|
|
515
|
-
f.write("\n".join(config_lines) + "\n")
|
|
516
|
-
log_status(f" Added environment vars to {shell_config.name}")
|
|
517
|
-
except Exception:
|
|
518
|
-
log_status(" Note: Add IBM_DB_HOME to your shell config manually")
|
|
519
|
-
|
|
520
|
-
return True
|
|
521
|
-
|
|
522
|
-
except Exception as e:
|
|
523
|
-
log_status(f" clidriver install failed: {e}")
|
|
524
|
-
return False
|
|
525
|
-
|
|
526
435
|
def do_install():
|
|
527
436
|
install_btn.config(state=tk.DISABLED)
|
|
528
437
|
selected = [db_type for db_type, var in check_vars.items() if var.get()]
|
|
@@ -533,35 +442,20 @@ class ConnectionDialog:
|
|
|
533
442
|
|
|
534
443
|
python = sys.executable
|
|
535
444
|
for db_type in selected:
|
|
536
|
-
extra = {"ibmi": "ibmi", "
|
|
445
|
+
extra = {"ibmi": "ibmi", "mysql": "mysql", "postgresql": "postgresql"}.get(db_type)
|
|
537
446
|
if extra:
|
|
538
|
-
# Special handling for ibm_db - need clidriver first
|
|
539
|
-
if db_type == "ibmi_db":
|
|
540
|
-
log_status("Installing IBM clidriver...")
|
|
541
|
-
if not install_clidriver(log_status):
|
|
542
|
-
log_status(" Skipping ibm_db (clidriver required)")
|
|
543
|
-
continue
|
|
544
|
-
|
|
545
447
|
# Map db_type to actual pip package
|
|
546
448
|
packages = {
|
|
547
449
|
"ibmi": "pyodbc",
|
|
548
|
-
"ibmi_db": "ibm_db",
|
|
549
450
|
"mysql": "mysql-connector-python",
|
|
550
451
|
"postgresql": "psycopg2-binary",
|
|
551
452
|
}
|
|
552
453
|
package = packages.get(db_type, extra)
|
|
553
454
|
log_status(f"Installing {package}...")
|
|
554
455
|
try:
|
|
555
|
-
# Set up environment - inherit current env and add IBM_DB_HOME if needed
|
|
556
|
-
env = os.environ.copy()
|
|
557
|
-
if db_type == "ibmi_db":
|
|
558
|
-
cli_path = Path.home() / "db2drivers" / "clidriver"
|
|
559
|
-
if cli_path.exists():
|
|
560
|
-
env["IBM_DB_HOME"] = str(cli_path)
|
|
561
|
-
|
|
562
456
|
result = subprocess.run(
|
|
563
457
|
[python, "-m", "pip", "install", package],
|
|
564
|
-
capture_output=True, text=True, timeout=180
|
|
458
|
+
capture_output=True, text=True, timeout=180
|
|
565
459
|
)
|
|
566
460
|
if result.returncode == 0:
|
|
567
461
|
log_status(f" {extra}: OK")
|
|
@@ -575,9 +469,17 @@ class ConnectionDialog:
|
|
|
575
469
|
|
|
576
470
|
log_status("\nDone! Please restart SQLBench to use new drivers.")
|
|
577
471
|
install_btn.config(state=tk.NORMAL)
|
|
472
|
+
dialog.after(0, lambda: restart_btn.pack(side=tk.LEFT, padx=5))
|
|
473
|
+
|
|
474
|
+
def do_restart():
|
|
475
|
+
dialog.destroy()
|
|
476
|
+
if self.app:
|
|
477
|
+
self.app._restart_app()
|
|
578
478
|
|
|
579
479
|
btn_frame = ttk.Frame(dialog)
|
|
580
480
|
btn_frame.pack(pady=10)
|
|
581
481
|
install_btn = ttk.Button(btn_frame, text="Install Selected", command=lambda: threading.Thread(target=do_install, daemon=True).start())
|
|
582
482
|
install_btn.pack(side=tk.LEFT, padx=5)
|
|
483
|
+
restart_btn = ttk.Button(btn_frame, text="Restart Now", command=do_restart)
|
|
484
|
+
# restart_btn is packed after installation completes
|
|
583
485
|
ttk.Button(btn_frame, text="Close", command=dialog.destroy).pack(side=tk.LEFT, padx=5)
|
|
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
|