23
23
from textual_fastdatatable import DataTable
24
24
from textual_fastdatatable .backend import AutoBackendType
25
25
26
+ from harlequin import HarlequinConnection
26
27
from harlequin .adapter import HarlequinAdapter , HarlequinCursor
27
28
from harlequin .autocomplete import completer_factory
28
29
from harlequin .cache import BufferState , Cache , write_cache
42
43
from harlequin .exception import (
43
44
HarlequinConfigError ,
44
45
HarlequinConnectionError ,
46
+ HarlequinError ,
45
47
HarlequinQueryError ,
46
48
HarlequinThemeError ,
49
+ pretty_error_message ,
47
50
pretty_print_error ,
48
51
)
49
52
50
53
54
+ class DatabaseConnected (Message ):
55
+ def __init__ (self , connection : HarlequinConnection ) -> None :
56
+ super ().__init__ ()
57
+ self .connection = connection
58
+
59
+
51
60
class QuerySubmitted (Message ):
52
61
def __init__ (self , query_text : str , limit : int | None ) -> None :
53
62
super ().__init__ ()
@@ -126,14 +135,7 @@ def __init__(
126
135
)
127
136
self .exit (return_code = 2 )
128
137
self .query_timer : Union [float , None ] = None
129
- try :
130
- self .connection = self .adapter .connect ()
131
- except HarlequinConnectionError as e :
132
- pretty_print_error (e )
133
- self .exit (return_code = 2 )
134
- else :
135
- if self .connection .init_message :
136
- self .notify (self .connection .init_message )
138
+ self .connection : HarlequinConnection | None = None
137
139
138
140
try :
139
141
self .app_colors = HarlequinColors .from_theme (theme )
@@ -150,9 +152,12 @@ def compose(self) -> ComposeResult:
150
152
yield DataCatalog (type_color = self .app_colors .gray )
151
153
with Vertical (id = "main_panel" ):
152
154
yield EditorCollection (language = "sql" , theme = self .theme )
153
- yield RunQueryBar (max_results = self .max_results )
155
+ yield RunQueryBar (
156
+ max_results = self .max_results , classes = "non-responsive"
157
+ )
154
158
yield ResultsViewer (
155
- max_results = self .max_results , type_color = self .app_colors .gray
159
+ max_results = self .max_results ,
160
+ type_color = self .app_colors .gray ,
156
161
)
157
162
yield Footer ()
158
163
@@ -186,8 +191,9 @@ async def on_mount(self) -> None:
186
191
187
192
self .editor .focus ()
188
193
self .run_query_bar .checkbox .value = False
194
+ self .data_catalog .loading = True
189
195
190
- self .update_schema_data ()
196
+ self ._connect ()
191
197
192
198
@on (Button .Pressed , "#run_query" )
193
199
def submit_query_from_run_query_bar (self , message : Button .Pressed ) -> None :
@@ -208,6 +214,17 @@ def submit_query_from_editor(self, message: CodeEditor.Submitted) -> None:
208
214
)
209
215
)
210
216
217
+ @on (DatabaseConnected )
218
+ def initialize_app (self , message : DatabaseConnected ) -> None :
219
+ self .connection = message .connection
220
+ self .run_query_bar .set_responsive ()
221
+ self .results_viewer .show_table (did_run = False )
222
+ if message .connection .init_message :
223
+ self .notify (message .connection .init_message , title = "Database Connected." )
224
+ else :
225
+ self .notify ("Database Connected." )
226
+ self .update_schema_data ()
227
+
211
228
@on (DataCatalog .NodeSubmitted )
212
229
def insert_node_into_editor (
213
230
self , message : DataCatalog .NodeSubmitted [CatalogItem ]
@@ -277,11 +294,11 @@ def copy_data_to_clipboard(self, message: DataTable.SelectionCopied) -> None:
277
294
self .notify ("Selected data copied to clipboard." )
278
295
279
296
@on (Worker .StateChanged )
280
- def handle_worker_error (self , message : Worker .StateChanged ) -> None :
297
+ async def handle_worker_error (self , message : Worker .StateChanged ) -> None :
281
298
if message .state == WorkerState .ERROR :
282
- self ._handle_worker_error (message )
299
+ await self ._handle_worker_error (message )
283
300
284
- def _handle_worker_error (self , message : Worker .StateChanged ) -> None :
301
+ async def _handle_worker_error (self , message : Worker .StateChanged ) -> None :
285
302
if (
286
303
message .worker .name == "update_schema_data"
287
304
and message .worker .error is not None
@@ -296,16 +313,33 @@ def _handle_worker_error(self, message: Worker.StateChanged) -> None:
296
313
):
297
314
self .run_query_bar .set_responsive ()
298
315
self .results_viewer .show_table ()
299
- header = getattr (message .worker .error , "title" , "Query Error" )
316
+ header = getattr (
317
+ message .worker .error , "title" , message .worker .error .__class__ .__name__
318
+ )
300
319
self ._push_error_modal (
301
320
title = "Query Error" ,
302
321
header = header ,
303
322
error = message .worker .error ,
304
323
)
324
+ elif message .worker .name == "_connect" and message .worker .error is not None :
325
+ title = getattr (
326
+ message .worker .error ,
327
+ "title" ,
328
+ "Harlequin could not connect to your database." ,
329
+ )
330
+ error = (
331
+ message .worker .error
332
+ if isinstance (message .worker .error , HarlequinError )
333
+ else HarlequinConnectionError (
334
+ msg = str (message .worker .error ), title = title
335
+ )
336
+ )
337
+ self .exit (return_code = 2 , message = pretty_error_message (error ))
305
338
306
339
@on (NewCatalog )
307
340
def update_tree_and_completers (self , message : NewCatalog ) -> None :
308
341
self .data_catalog .update_tree (message .catalog )
342
+ self .data_catalog .loading = False
309
343
self .update_completers (message .catalog )
310
344
311
345
@on (QueriesExecuted )
@@ -383,6 +417,8 @@ def watch_full_screen(self, full_screen: bool) -> None:
383
417
384
418
@on (QuerySubmitted )
385
419
def execute_query (self , message : QuerySubmitted ) -> None :
420
+ if self .connection is None :
421
+ return
386
422
if message .query_text :
387
423
self .full_screen = False
388
424
self .run_query_bar .set_not_responsive ()
@@ -472,6 +508,17 @@ def action_toggle_sidebar(self) -> None:
472
508
else :
473
509
self .sidebar_hidden = not self .sidebar_hidden
474
510
511
+ @work (
512
+ thread = True ,
513
+ exclusive = True ,
514
+ exit_on_error = False ,
515
+ group = "connect" ,
516
+ description = "Connecting to DB" ,
517
+ )
518
+ def _connect (self ) -> None :
519
+ connection = self .adapter .connect ()
520
+ self .post_message (DatabaseConnected (connection = connection ))
521
+
475
522
@work (
476
523
thread = True ,
477
524
exclusive = True ,
@@ -480,6 +527,8 @@ def action_toggle_sidebar(self) -> None:
480
527
description = "Executing queries." ,
481
528
)
482
529
def _execute_query (self , message : QuerySubmitted ) -> None :
530
+ if self .connection is None :
531
+ return
483
532
cursors : Dict [str , HarlequinCursor ] = {}
484
533
queries = self ._split_query_text (message .query_text )
485
534
for q in queries :
@@ -514,7 +563,7 @@ def _push_error_modal(self, title: str, header: str, error: BaseException) -> No
514
563
title = title ,
515
564
header = header ,
516
565
error = error ,
517
- )
566
+ ),
518
567
)
519
568
520
569
@work (
@@ -543,6 +592,8 @@ def _fetch_data(
543
592
544
593
@work (thread = True , exclusive = True , exit_on_error = True , group = "completer_builders" )
545
594
def update_completers (self , catalog : Catalog ) -> None :
595
+ if self .connection is None :
596
+ return
546
597
if (
547
598
self .editor_collection .word_completer is not None
548
599
and self .editor_collection .member_completer is not None
@@ -561,6 +612,8 @@ def update_completers(self, catalog: Catalog) -> None:
561
612
562
613
@work (thread = True , exclusive = True , exit_on_error = False , group = "schema_updaters" )
563
614
def update_schema_data (self ) -> None :
615
+ if self .connection is None :
616
+ return
564
617
catalog = self .connection .get_catalog ()
565
618
self .post_message (NewCatalog (catalog = catalog ))
566
619
@@ -570,6 +623,8 @@ def _validate_selection(self) -> str:
570
623
return the empty string.
571
624
"""
572
625
selection = self .editor .selected_text
626
+ if self .connection is None :
627
+ return selection
573
628
if selection :
574
629
try :
575
630
return self .connection .validate_sql (selection )
0 commit comments