22
33from __future__ import annotations
44
5+ import re
6+
57from typing import TYPE_CHECKING , Any , Callable
68
79from sqlit .domains .explorer .ui .tree import db_switching as tree_db_switching
2123 from sqlit .domains .query .app .transaction import TransactionExecutor
2224
2325
26+ _SCHEMA_CHANGE_RE = re .compile (
27+ r"\b(create|alter|drop|truncate|rename|comment|grant|revoke)\b" ,
28+ re .IGNORECASE ,
29+ )
30+ _SQL_COMMENT_RE = re .compile (r"(--[^\n]*|/\*.*?\*/)" , re .DOTALL )
31+ _SQL_LITERAL_RE = re .compile (r"('([^']|'')*'|\"([^\"]|\"\")*\"|`[^`]*`|\[[^\]]*\])" , re .DOTALL )
32+
33+
34+ def _strip_sql_comments_and_literals (sql : str ) -> str :
35+ sql = _SQL_COMMENT_RE .sub (" " , sql )
36+ return _SQL_LITERAL_RE .sub (" " , sql )
37+
38+
2439class QueryExecutionMixin (ProcessWorkerLifecycleMixin ):
2540 """Mixin providing query execution actions."""
2641
@@ -216,6 +231,21 @@ def _on_result(confirmed: bool | None) -> None:
216231 _on_result ,
217232 )
218233
234+ def _query_changes_schema (self : QueryMixinHost , query : str ) -> bool :
235+ cleaned = _strip_sql_comments_and_literals (query )
236+ return bool (_SCHEMA_CHANGE_RE .search (cleaned ))
237+
238+ def _maybe_refresh_explorer_after_query (self : QueryMixinHost , query : str ) -> None :
239+ if not self ._query_changes_schema (query ):
240+ return
241+ refresh = getattr (self , "_refresh_tree_after_schema_change" , None )
242+ if callable (refresh ):
243+ refresh ()
244+ return
245+ action = getattr (self , "action_refresh_tree" , None )
246+ if callable (action ):
247+ action ()
248+
219249 def _start_query_spinner (self : QueryMixinHost ) -> None :
220250 """Start the query execution spinner animation."""
221251 import time
@@ -481,6 +511,7 @@ async def _run_query_async(self: QueryMixinHost, query: str, keep_insert_mode: b
481511 )
482512 else :
483513 self ._display_non_query_result (result .rows_affected , elapsed_ms )
514+ self ._maybe_refresh_explorer_after_query (query )
484515 if keep_insert_mode :
485516 self ._restore_insert_mode ()
486517 return
@@ -500,6 +531,7 @@ async def _run_query_async(self: QueryMixinHost, query: str, keep_insert_mode: b
500531 except Exception :
501532 pass
502533 self ._display_multi_statement_results (multi_result , elapsed_ms )
534+ self ._maybe_refresh_explorer_after_query (query )
503535 else :
504536 # Single statement - existing behavior
505537 result = await asyncio .to_thread (
@@ -520,6 +552,7 @@ async def _run_query_async(self: QueryMixinHost, query: str, keep_insert_mode: b
520552 )
521553 else :
522554 self ._display_non_query_result (result .rows_affected , elapsed_ms )
555+ self ._maybe_refresh_explorer_after_query (query )
523556
524557 if keep_insert_mode :
525558 self ._restore_insert_mode ()
@@ -584,14 +617,17 @@ async def _run_query_atomic_async(self: QueryMixinHost, query: str) -> None:
584617 self .notify ("Transaction rolled back (error in statement)" , severity = "error" )
585618 else :
586619 self .notify ("Query executed atomically (committed)" , severity = "information" )
620+ self ._maybe_refresh_explorer_after_query (query )
587621 elif isinstance (result , QueryResult ):
588622 await self ._display_query_results (
589623 result .columns , result .rows , result .row_count , result .truncated , elapsed_ms
590624 )
591625 self .notify ("Query executed atomically (committed)" , severity = "information" )
626+ self ._maybe_refresh_explorer_after_query (query )
592627 else :
593628 self ._display_non_query_result (result .rows_affected , elapsed_ms )
594629 self .notify ("Query executed atomically (committed)" , severity = "information" )
630+ self ._maybe_refresh_explorer_after_query (query )
595631
596632 except Exception as e :
597633 self ._display_query_error (f"Transaction rolled back: { e } " )
0 commit comments