22
33from __future__ import annotations
44
5- from typing import TYPE_CHECKING , Any
5+ from typing import TYPE_CHECKING , Any , Callable
66
77from sqlit .domains .explorer .ui .tree import db_switching as tree_db_switching
88from sqlit .domains .process_worker .ui .mixins .process_worker_lifecycle import (
@@ -58,16 +58,19 @@ def action_execute_query_atomic(self: QueryMixinHost) -> None:
5858 self .notify ("No query to execute" , severity = "warning" )
5959 return
6060
61- if hasattr (self , "_query_worker" ) and self ._query_worker is not None :
62- self ._query_worker .cancel ()
61+ def _proceed () -> None :
62+ if hasattr (self , "_query_worker" ) and self ._query_worker is not None :
63+ self ._query_worker .cancel ()
6364
64- self ._start_query_spinner ()
65+ self ._start_query_spinner ()
6566
66- self ._query_worker = self .run_worker (
67- self ._run_query_atomic_async (query ),
68- name = "query_execution_atomic" ,
69- exclusive = True ,
70- )
67+ self ._query_worker = self .run_worker (
68+ self ._run_query_atomic_async (query ),
69+ name = "query_execution_atomic" ,
70+ exclusive = True ,
71+ )
72+
73+ self ._maybe_confirm_query (query , _proceed )
7174
7275 def action_execute_single_statement (self : QueryMixinHost ) -> None :
7376 """Execute only the SQL statement at the current cursor position."""
@@ -96,16 +99,19 @@ def action_execute_single_statement(self: QueryMixinHost) -> None:
9699 self .notify ("No statement found at cursor" , severity = "warning" )
97100 return
98101
99- if hasattr (self , "_query_worker" ) and self ._query_worker is not None :
100- self ._query_worker .cancel ()
102+ def _proceed () -> None :
103+ if hasattr (self , "_query_worker" ) and self ._query_worker is not None :
104+ self ._query_worker .cancel ()
101105
102- self ._start_query_spinner ()
106+ self ._start_query_spinner ()
103107
104- self ._query_worker = self .run_worker (
105- self ._run_query_async (statement , keep_insert_mode = False ),
106- name = "query_execution_single" ,
107- exclusive = True ,
108- )
108+ self ._query_worker = self .run_worker (
109+ self ._run_query_async (statement , keep_insert_mode = False ),
110+ name = "query_execution_single" ,
111+ exclusive = True ,
112+ )
113+
114+ self ._maybe_confirm_query (statement , _proceed )
109115
110116 def _execute_query_common (self : QueryMixinHost , keep_insert_mode : bool ) -> None :
111117 """Common query execution logic."""
@@ -119,15 +125,71 @@ def _execute_query_common(self: QueryMixinHost, keep_insert_mode: bool) -> None:
119125 self .notify ("No query to execute" , severity = "warning" )
120126 return
121127
122- if hasattr (self , "_query_worker" ) and self ._query_worker is not None :
123- self ._query_worker .cancel ()
128+ def _proceed () -> None :
129+ if hasattr (self , "_query_worker" ) and self ._query_worker is not None :
130+ self ._query_worker .cancel ()
131+
132+ self ._start_query_spinner ()
133+
134+ self ._query_worker = self .run_worker (
135+ self ._run_query_async (query , keep_insert_mode ),
136+ name = "query_execution" ,
137+ exclusive = True ,
138+ )
139+
140+ self ._maybe_confirm_query (query , _proceed )
141+
142+ def _maybe_confirm_query (self : QueryMixinHost , query : str , proceed : Callable [[], None ]) -> None :
143+ """Confirm query execution based on alert mode, then call proceed."""
144+ from sqlit .domains .query .app .alerts import (
145+ AlertMode ,
146+ AlertSeverity ,
147+ classify_query_alert ,
148+ format_alert_mode ,
149+ should_confirm ,
150+ )
151+ from sqlit .shared .ui .screens .confirm import ConfirmScreen
124152
125- self ._start_query_spinner ()
153+ raw_mode = getattr (self .services .runtime , "query_alert_mode" , 0 ) or 0
154+ try :
155+ mode = AlertMode (int (raw_mode ))
156+ except ValueError :
157+ mode = AlertMode .OFF
158+
159+ if mode == AlertMode .OFF :
160+ proceed ()
161+ return
162+
163+ severity = classify_query_alert (query )
164+ if severity == AlertSeverity .NONE or not should_confirm (mode , severity ):
165+ proceed ()
166+ return
167+
168+ title = "Confirm query"
169+ if severity == AlertSeverity .DELETE :
170+ title = "Confirm DELETE query"
171+ elif severity == AlertSeverity .WRITE :
172+ title = "Confirm write query"
173+
174+ description = None
175+ snippet = query .strip ().splitlines ()[0 ] if query .strip () else ""
176+ if snippet :
177+ if len (snippet ) > 120 :
178+ snippet = snippet [:117 ] + "..."
179+ description = snippet
180+
181+ def _on_result (confirmed : bool | None ) -> None :
182+ if confirmed :
183+ proceed ()
184+ return
185+ self .notify (
186+ f"Query cancelled (alert mode: { format_alert_mode (mode )} )" ,
187+ severity = "warning" ,
188+ )
126189
127- self ._query_worker = self .run_worker (
128- self ._run_query_async (query , keep_insert_mode ),
129- name = "query_execution" ,
130- exclusive = True ,
190+ self .push_screen (
191+ ConfirmScreen (title , description , yes_label = "Run" , no_label = "Cancel" ),
192+ _on_result ,
131193 )
132194
133195 def _start_query_spinner (self : QueryMixinHost ) -> None :
0 commit comments