sqlshell 0.2.3__py3-none-any.whl → 0.3.1__py3-none-any.whl
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.
Potentially problematic release.
This version of sqlshell might be problematic. Click here for more details.
- sqlshell/__init__.py +35 -5
- sqlshell/db/__init__.py +2 -1
- sqlshell/db/database_manager.py +336 -23
- sqlshell/db/export_manager.py +188 -0
- sqlshell/editor_integration.py +127 -0
- sqlshell/execution_handler.py +421 -0
- sqlshell/main.py +570 -140
- sqlshell/query_tab.py +592 -7
- sqlshell/ui/filter_header.py +22 -1
- sqlshell/utils/profile_column.py +1586 -170
- sqlshell/utils/profile_foreign_keys.py +103 -11
- sqlshell/utils/profile_ohe.py +631 -0
- {sqlshell-0.2.3.dist-info → sqlshell-0.3.1.dist-info}/METADATA +126 -7
- {sqlshell-0.2.3.dist-info → sqlshell-0.3.1.dist-info}/RECORD +17 -13
- {sqlshell-0.2.3.dist-info → sqlshell-0.3.1.dist-info}/WHEEL +1 -1
- {sqlshell-0.2.3.dist-info → sqlshell-0.3.1.dist-info}/entry_points.txt +0 -0
- {sqlshell-0.2.3.dist-info → sqlshell-0.3.1.dist-info}/top_level.txt +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
import itertools
|
|
3
3
|
import pandas as pd
|
|
4
|
-
from typing import List, Dict, Tuple, Set
|
|
4
|
+
from typing import List, Dict, Tuple, Set, Callable
|
|
5
5
|
from PyQt6.QtWidgets import (
|
|
6
|
-
QApplication, QWidget, QVBoxLayout, QLabel, QTableWidget, QTableWidgetItem, QHeaderView, QTabWidget, QMainWindow
|
|
6
|
+
QApplication, QWidget, QVBoxLayout, QLabel, QTableWidget, QTableWidgetItem, QHeaderView, QTabWidget, QMainWindow,
|
|
7
|
+
QPushButton, QHBoxLayout, QMessageBox
|
|
7
8
|
)
|
|
8
9
|
from PyQt6.QtCore import Qt
|
|
9
10
|
|
|
@@ -217,7 +218,8 @@ def profile_foreign_keys(dfs: List[pd.DataFrame], df_names: List[str] = None, mi
|
|
|
217
218
|
return foreign_keys, inclusion_dependencies, integrity_results
|
|
218
219
|
|
|
219
220
|
|
|
220
|
-
def visualize_foreign_keys(dfs: List[pd.DataFrame], df_names: List[str] = None, min_match_ratio: float = 0.95
|
|
221
|
+
def visualize_foreign_keys(dfs: List[pd.DataFrame], df_names: List[str] = None, min_match_ratio: float = 0.95,
|
|
222
|
+
on_generate_join: Callable = None, parent=None):
|
|
221
223
|
"""
|
|
222
224
|
Create a visual representation of foreign key relationships between DataFrames.
|
|
223
225
|
|
|
@@ -225,6 +227,9 @@ def visualize_foreign_keys(dfs: List[pd.DataFrame], df_names: List[str] = None,
|
|
|
225
227
|
- dfs: List of pandas DataFrames to analyze
|
|
226
228
|
- df_names: Optional list of names for the DataFrames. If None, names will be generated.
|
|
227
229
|
- min_match_ratio: Minimum ratio of matching values to consider a foreign key
|
|
230
|
+
- on_generate_join: Callback function that will be called when the Generate JOIN button is clicked.
|
|
231
|
+
It receives a JOIN query string as its argument.
|
|
232
|
+
- parent: Parent widget for the QMainWindow. Typically the main application window.
|
|
228
233
|
|
|
229
234
|
Returns:
|
|
230
235
|
- QMainWindow: The visualization window
|
|
@@ -239,7 +244,7 @@ def visualize_foreign_keys(dfs: List[pd.DataFrame], df_names: List[str] = None,
|
|
|
239
244
|
)
|
|
240
245
|
|
|
241
246
|
# Create main window
|
|
242
|
-
window = QMainWindow()
|
|
247
|
+
window = QMainWindow(parent)
|
|
243
248
|
window.setWindowTitle("Foreign Key Analysis")
|
|
244
249
|
window.resize(900, 700)
|
|
245
250
|
|
|
@@ -268,6 +273,13 @@ def visualize_foreign_keys(dfs: List[pd.DataFrame], df_names: List[str] = None,
|
|
|
268
273
|
# Create tabs
|
|
269
274
|
tabs = QTabWidget()
|
|
270
275
|
|
|
276
|
+
# Define the "Add to editor" function to handle JOIN queries
|
|
277
|
+
def handle_join_query(query):
|
|
278
|
+
if on_generate_join:
|
|
279
|
+
on_generate_join(query)
|
|
280
|
+
QMessageBox.information(window, "JOIN Query Generated",
|
|
281
|
+
f"The following query has been added to the editor:\n\n{query}")
|
|
282
|
+
|
|
271
283
|
# Tab for Foreign Keys
|
|
272
284
|
fk_tab = QWidget()
|
|
273
285
|
fk_layout = QVBoxLayout()
|
|
@@ -276,12 +288,16 @@ def visualize_foreign_keys(dfs: List[pd.DataFrame], df_names: List[str] = None,
|
|
|
276
288
|
fk_header.setStyleSheet("font-weight: bold;")
|
|
277
289
|
fk_layout.addWidget(fk_header)
|
|
278
290
|
|
|
279
|
-
fk_table = QTableWidget(len(foreign_keys),
|
|
291
|
+
fk_table = QTableWidget(len(foreign_keys), 6) # Added column for Generate JOIN button
|
|
280
292
|
fk_table.setHorizontalHeaderLabels([
|
|
281
|
-
"Referenced Table", "Referenced Column", "Referencing Table", "Referencing Column", "Match Ratio"
|
|
293
|
+
"Referenced Table", "Referenced Column", "Referencing Table", "Referencing Column", "Match Ratio", "Action"
|
|
282
294
|
])
|
|
283
295
|
fk_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
|
|
284
296
|
|
|
297
|
+
# Set minimum width for the Action column
|
|
298
|
+
fk_table.horizontalHeader().setSectionResizeMode(5, QHeaderView.ResizeMode.Interactive)
|
|
299
|
+
fk_table.setColumnWidth(5, 140) # Set a fixed width for action column
|
|
300
|
+
|
|
285
301
|
for row, (pk_table, pk_col, fk_table_name, fk_col, match_ratio) in enumerate(foreign_keys):
|
|
286
302
|
fk_table.setItem(row, 0, QTableWidgetItem(pk_table))
|
|
287
303
|
fk_table.setItem(row, 1, QTableWidgetItem(pk_col))
|
|
@@ -298,6 +314,27 @@ def visualize_foreign_keys(dfs: List[pd.DataFrame], df_names: List[str] = None,
|
|
|
298
314
|
ratio_item.setForeground(Qt.GlobalColor.darkYellow)
|
|
299
315
|
fk_table.setItem(row, 4, ratio_item)
|
|
300
316
|
|
|
317
|
+
# Add Generate JOIN hyperlink - optimized for better visibility
|
|
318
|
+
if on_generate_join is not None:
|
|
319
|
+
button_widget = QWidget()
|
|
320
|
+
button_layout = QHBoxLayout(button_widget)
|
|
321
|
+
button_layout.setContentsMargins(0, 0, 0, 0) # Minimal margins
|
|
322
|
+
button_layout.setSpacing(0) # No spacing
|
|
323
|
+
|
|
324
|
+
# Create a styled hyperlink label
|
|
325
|
+
join_link = QLabel("<a href='#' style='color: #3498DB; font-weight: bold;'>Generate JOIN</a>")
|
|
326
|
+
join_link.setTextFormat(Qt.TextFormat.RichText)
|
|
327
|
+
join_link.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction)
|
|
328
|
+
join_link.setCursor(Qt.CursorShape.PointingHandCursor)
|
|
329
|
+
join_link.setAlignment(Qt.AlignmentFlag.AlignCenter) # Center the text
|
|
330
|
+
join_query = f"SELECT * FROM {fk_table_name} JOIN {pk_table} ON {fk_table_name}.{fk_col} = {pk_table}.{pk_col}"
|
|
331
|
+
|
|
332
|
+
# Connect linkActivated signal to handle the JOIN query
|
|
333
|
+
join_link.linkActivated.connect(lambda link, q=join_query: handle_join_query(q))
|
|
334
|
+
|
|
335
|
+
button_layout.addWidget(join_link)
|
|
336
|
+
fk_table.setCellWidget(row, 5, button_widget)
|
|
337
|
+
|
|
301
338
|
fk_layout.addWidget(fk_table)
|
|
302
339
|
fk_tab.setLayout(fk_layout)
|
|
303
340
|
tabs.addTab(fk_tab, "Foreign Keys")
|
|
@@ -310,12 +347,16 @@ def visualize_foreign_keys(dfs: List[pd.DataFrame], df_names: List[str] = None,
|
|
|
310
347
|
id_header.setStyleSheet("font-weight: bold;")
|
|
311
348
|
id_layout.addWidget(id_header)
|
|
312
349
|
|
|
313
|
-
id_table = QTableWidget(len(inclusion_dependencies),
|
|
350
|
+
id_table = QTableWidget(len(inclusion_dependencies), 6) # Added column for Generate JOIN button
|
|
314
351
|
id_table.setHorizontalHeaderLabels([
|
|
315
|
-
"Referenced Table", "Referenced Column", "Referencing Table", "Referencing Column", "Match Ratio"
|
|
352
|
+
"Referenced Table", "Referenced Column", "Referencing Table", "Referencing Column", "Match Ratio", "Action"
|
|
316
353
|
])
|
|
317
354
|
id_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
|
|
318
355
|
|
|
356
|
+
# Set minimum width for the Action column
|
|
357
|
+
id_table.horizontalHeader().setSectionResizeMode(5, QHeaderView.ResizeMode.Interactive)
|
|
358
|
+
id_table.setColumnWidth(5, 140) # Set a fixed width for action column
|
|
359
|
+
|
|
319
360
|
for row, (table1, col1, table2, col2, match_ratio) in enumerate(inclusion_dependencies):
|
|
320
361
|
id_table.setItem(row, 0, QTableWidgetItem(table1))
|
|
321
362
|
id_table.setItem(row, 1, QTableWidgetItem(col1))
|
|
@@ -332,6 +373,27 @@ def visualize_foreign_keys(dfs: List[pd.DataFrame], df_names: List[str] = None,
|
|
|
332
373
|
ratio_item.setForeground(Qt.GlobalColor.darkYellow)
|
|
333
374
|
id_table.setItem(row, 4, ratio_item)
|
|
334
375
|
|
|
376
|
+
# Add Generate JOIN hyperlink - optimized for better visibility
|
|
377
|
+
if on_generate_join is not None:
|
|
378
|
+
button_widget = QWidget()
|
|
379
|
+
button_layout = QHBoxLayout(button_widget)
|
|
380
|
+
button_layout.setContentsMargins(0, 0, 0, 0) # Minimal margins
|
|
381
|
+
button_layout.setSpacing(0) # No spacing
|
|
382
|
+
|
|
383
|
+
# Create a styled hyperlink label
|
|
384
|
+
join_link = QLabel("<a href='#' style='color: #3498DB; font-weight: bold;'>Generate JOIN</a>")
|
|
385
|
+
join_link.setTextFormat(Qt.TextFormat.RichText)
|
|
386
|
+
join_link.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction)
|
|
387
|
+
join_link.setCursor(Qt.CursorShape.PointingHandCursor)
|
|
388
|
+
join_link.setAlignment(Qt.AlignmentFlag.AlignCenter) # Center the text
|
|
389
|
+
join_query = f"SELECT * FROM {table2} JOIN {table1} ON {table2}.{col2} = {table1}.{col1}"
|
|
390
|
+
|
|
391
|
+
# Connect linkActivated signal to handle the JOIN query
|
|
392
|
+
join_link.linkActivated.connect(lambda link, q=join_query: handle_join_query(q))
|
|
393
|
+
|
|
394
|
+
button_layout.addWidget(join_link)
|
|
395
|
+
id_table.setCellWidget(row, 5, button_widget)
|
|
396
|
+
|
|
335
397
|
id_layout.addWidget(id_table)
|
|
336
398
|
id_tab.setLayout(id_layout)
|
|
337
399
|
tabs.addTab(id_tab, "Inclusion Dependencies")
|
|
@@ -352,12 +414,16 @@ def visualize_foreign_keys(dfs: List[pd.DataFrame], df_names: List[str] = None,
|
|
|
352
414
|
ri_layout.addWidget(ri_description)
|
|
353
415
|
|
|
354
416
|
# Create table for referential integrity
|
|
355
|
-
ri_table = QTableWidget(len(integrity_results),
|
|
417
|
+
ri_table = QTableWidget(len(integrity_results), 6) # Added column for Generate JOIN button
|
|
356
418
|
ri_table.setHorizontalHeaderLabels([
|
|
357
|
-
"Relationship", "Violations", "Total FK Values", "Violation %", "Example Violations"
|
|
419
|
+
"Relationship", "Violations", "Total FK Values", "Violation %", "Example Violations", "Action"
|
|
358
420
|
])
|
|
359
421
|
ri_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
|
|
360
422
|
|
|
423
|
+
# Set minimum width for the Action column
|
|
424
|
+
ri_table.horizontalHeader().setSectionResizeMode(5, QHeaderView.ResizeMode.Interactive)
|
|
425
|
+
ri_table.setColumnWidth(5, 140) # Set a fixed width for action column
|
|
426
|
+
|
|
361
427
|
row = 0
|
|
362
428
|
for key, stats in integrity_results.items():
|
|
363
429
|
pk_table, pk_col, fk_table, fk_col = key
|
|
@@ -383,6 +449,27 @@ def visualize_foreign_keys(dfs: List[pd.DataFrame], df_names: List[str] = None,
|
|
|
383
449
|
examples += f" (and {stats['violation_count'] - len(stats['violations'])} more)"
|
|
384
450
|
ri_table.setItem(row, 4, QTableWidgetItem(examples))
|
|
385
451
|
|
|
452
|
+
# Add Generate JOIN hyperlink - optimized for better visibility
|
|
453
|
+
if on_generate_join is not None:
|
|
454
|
+
button_widget = QWidget()
|
|
455
|
+
button_layout = QHBoxLayout(button_widget)
|
|
456
|
+
button_layout.setContentsMargins(0, 0, 0, 0) # Minimal margins
|
|
457
|
+
button_layout.setSpacing(0) # No spacing
|
|
458
|
+
|
|
459
|
+
# Create a styled hyperlink label
|
|
460
|
+
join_link = QLabel("<a href='#' style='color: #3498DB; font-weight: bold;'>Generate JOIN</a>")
|
|
461
|
+
join_link.setTextFormat(Qt.TextFormat.RichText)
|
|
462
|
+
join_link.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction)
|
|
463
|
+
join_link.setCursor(Qt.CursorShape.PointingHandCursor)
|
|
464
|
+
join_link.setAlignment(Qt.AlignmentFlag.AlignCenter) # Center the text
|
|
465
|
+
join_query = f"SELECT * FROM {fk_table} LEFT JOIN {pk_table} ON {fk_table}.{fk_col} = {pk_table}.{pk_col}"
|
|
466
|
+
|
|
467
|
+
# Connect linkActivated signal to handle the JOIN query
|
|
468
|
+
join_link.linkActivated.connect(lambda link, q=join_query: handle_join_query(q))
|
|
469
|
+
|
|
470
|
+
button_layout.addWidget(join_link)
|
|
471
|
+
ri_table.setCellWidget(row, 5, button_widget)
|
|
472
|
+
|
|
386
473
|
row += 1
|
|
387
474
|
|
|
388
475
|
ri_layout.addWidget(ri_table)
|
|
@@ -442,12 +529,17 @@ def test_profile_foreign_keys():
|
|
|
442
529
|
# Add some non-existent customer IDs
|
|
443
530
|
orders_df.loc[95:99, "customer_id"] = [25, 26, 27, 28, 29]
|
|
444
531
|
|
|
532
|
+
# Define a callback function to handle JOIN generation
|
|
533
|
+
def handle_join_query(query):
|
|
534
|
+
print(f"Generated JOIN query: {query}")
|
|
535
|
+
# In a real application, this would insert the query into the query editor
|
|
536
|
+
|
|
445
537
|
# Create and show visualization
|
|
446
538
|
dfs = [customers_df, products_df, orders_df, order_details_df]
|
|
447
539
|
df_names = ["Customers", "Products", "Orders", "OrderDetails"]
|
|
448
540
|
|
|
449
541
|
app = QApplication(sys.argv)
|
|
450
|
-
window = visualize_foreign_keys(dfs, df_names, min_match_ratio=0.9)
|
|
542
|
+
window = visualize_foreign_keys(dfs, df_names, min_match_ratio=0.9, on_generate_join=handle_join_query)
|
|
451
543
|
sys.exit(app.exec())
|
|
452
544
|
|
|
453
545
|
# Only run the test function when script is executed directly
|