@@ -110,6 +110,15 @@ class SidequeryWorkbench(App):
110110 display: none;
111111 }
112112
113+ #sql-view {
114+ height: 1fr;
115+ display: none;
116+ }
117+
118+ #sql-display {
119+ height: 100%;
120+ }
121+
113122 #results-table {
114123 height: 100%;
115124 }
@@ -146,6 +155,7 @@ def __init__(self, directory: Path, show_sql: bool = False, demo_mode: bool = Fa
146155 self .directory = directory
147156 self .layer = None
148157 self .last_result = None
158+ self .last_rendered_sql = None
149159 self .demo_mode = demo_mode
150160
151161 def compose (self ) -> ComposeResult :
@@ -193,8 +203,11 @@ def compose(self) -> ComposeResult:
193203 with Horizontal (id = "view-buttons" ):
194204 yield Button ("Table" , id = "btn-table" , classes = "active" )
195205 yield Button ("Chart" , id = "btn-chart" )
206+ yield Button ("SQL" , id = "btn-sql" )
196207 with Vertical (id = "table-view" ):
197208 yield DataTable (id = "results-table" )
209+ with Vertical (id = "sql-view" ):
210+ yield TextArea ("" , language = "sql" , show_line_numbers = True , read_only = True , id = "sql-display" )
198211 with Vertical (id = "chart-view" ):
199212 with Horizontal (id = "chart-config" , classes = "config-row" ):
200213 yield Label ("X:" , classes = "config-label" )
@@ -234,6 +247,13 @@ def watch_theme(self, theme_name: str) -> None:
234247 for editor in self .query (".sql-editor" ).results (TextArea ):
235248 editor .theme = editor_theme
236249
250+ # Update SQL display
251+ try :
252+ sql_display = self .query_one ("#sql-display" , TextArea )
253+ sql_display .theme = editor_theme
254+ except Exception :
255+ pass # SQL display may not exist yet
256+
237257 def on_mount (self ) -> None :
238258 """Load semantic layer and populate tree."""
239259 # Show first query editor
@@ -243,16 +263,41 @@ def on_mount(self) -> None:
243263 # Setup database connection
244264 if self .demo_mode :
245265 # Create in-memory demo database
246- from sidemantic .examples .multi_format_demo .demo_data import create_demo_database
266+ try :
267+ # Try packaged import first
268+ from sidemantic .examples .multi_format_demo .demo_data import create_demo_database
269+ except ModuleNotFoundError :
270+ # Fall back to dev environment import
271+ import sys
272+ demo_data_path = self .directory / "demo_data.py"
273+ if demo_data_path .exists ():
274+ import importlib .util
275+ spec = importlib .util .spec_from_file_location ("demo_data" , demo_data_path )
276+ demo_data_module = importlib .util .module_from_spec (spec )
277+ sys .modules ["demo_data" ] = demo_data_module
278+ spec .loader .exec_module (demo_data_module )
279+ create_demo_database = demo_data_module .create_demo_database
280+ else :
281+ raise ImportError (f"Could not find demo_data.py at { demo_data_path } " )
247282
248283 # Create layer with in-memory DB
249284 self .layer = SemanticLayer (connection = "duckdb:///:memory:" )
250285 # Populate with demo data
251286 demo_conn = create_demo_database ()
252287 # Copy data from demo connection to layer's connection
253288 for table in ["customers" , "products" , "orders" ]:
254- table_data = demo_conn .execute (f"SELECT * FROM { table } " ).df () # noqa: F841
255- self .layer .conn .execute (f"CREATE TABLE { table } AS SELECT * FROM table_data" )
289+ # Get table data as regular Python objects (no pandas)
290+ rows = demo_conn .execute (f"SELECT * FROM { table } " ).fetchall ()
291+ columns = [desc [0 ] for desc in demo_conn .execute (f"SELECT * FROM { table } LIMIT 0" ).description ]
292+
293+ # Create table in target connection
294+ create_sql = demo_conn .execute (f"SELECT sql FROM duckdb_tables() WHERE table_name = '{ table } '" ).fetchone ()[0 ]
295+ self .layer .conn .execute (create_sql )
296+
297+ # Insert data if there are rows
298+ if rows :
299+ placeholders = ", " .join (["?" for _ in columns ])
300+ self .layer .conn .executemany (f"INSERT INTO { table } VALUES ({ placeholders } )" , rows )
256301 else :
257302 # Try to find database file
258303 db_path = None
@@ -487,8 +532,17 @@ def action_run_query(self) -> None:
487532 if not sql :
488533 return
489534
490- # Execute query
491- result = self .layer .sql (sql )
535+ # Execute query and get rendered SQL
536+ from sidemantic .sql .query_rewriter import QueryRewriter
537+
538+ rewriter = QueryRewriter (self .layer .graph , dialect = self .layer .dialect )
539+ rendered_sql = rewriter .rewrite (sql )
540+
541+ # Store rendered SQL
542+ self .last_rendered_sql = rendered_sql
543+
544+ # Execute the query
545+ result = self .layer .conn .execute (rendered_sql )
492546
493547 # Get column names and rows
494548 columns = [desc [0 ] for desc in result .description ]
@@ -497,6 +551,11 @@ def action_run_query(self) -> None:
497551 # Store for chart rendering
498552 self .last_result = {"columns" : columns , "rows" : rows }
499553
554+ # Update SQL display
555+ if rendered_sql :
556+ sql_display = self .query_one ("#sql-display" , TextArea )
557+ sql_display .text = rendered_sql
558+
500559 # Update table
501560 table .clear (columns = True )
502561 for col in columns :
@@ -628,20 +687,32 @@ def on_button_pressed(self, event: Button.Pressed) -> None:
628687 else :
629688 btn .remove_class ("active" )
630689
631- # Handle table/chart view switching
690+ # Handle table/chart/sql view switching
632691 table_view = self .query_one ("#table-view" )
633692 chart_view = self .query_one ("#chart-view" )
693+ sql_view = self .query_one ("#sql-view" )
634694
635695 if event .button .id == "btn-table" :
636696 table_view .styles .display = "block"
637697 chart_view .styles .display = "none"
698+ sql_view .styles .display = "none"
638699 self .query_one ("#btn-table" , Button ).add_class ("active" )
639700 self .query_one ("#btn-chart" , Button ).remove_class ("active" )
701+ self .query_one ("#btn-sql" , Button ).remove_class ("active" )
640702 elif event .button .id == "btn-chart" :
641703 table_view .styles .display = "none"
642704 chart_view .styles .display = "block"
705+ sql_view .styles .display = "none"
643706 self .query_one ("#btn-table" , Button ).remove_class ("active" )
644707 self .query_one ("#btn-chart" , Button ).add_class ("active" )
708+ self .query_one ("#btn-sql" , Button ).remove_class ("active" )
709+ elif event .button .id == "btn-sql" :
710+ table_view .styles .display = "none"
711+ chart_view .styles .display = "none"
712+ sql_view .styles .display = "block"
713+ self .query_one ("#btn-table" , Button ).remove_class ("active" )
714+ self .query_one ("#btn-chart" , Button ).remove_class ("active" )
715+ self .query_one ("#btn-sql" , Button ).add_class ("active" )
645716
646717 def on_select_changed (self , event : Select .Changed ) -> None :
647718 """Handle chart axis selection changes."""
@@ -1144,15 +1215,41 @@ def mcp_serve(
11441215
11451216 # If demo mode, populate with demo data
11461217 if demo :
1147- from sidemantic .examples .multi_format_demo .demo_data import create_demo_database
1218+ try :
1219+ # Try packaged import first
1220+ from sidemantic .examples .multi_format_demo .demo_data import create_demo_database
1221+ except ModuleNotFoundError :
1222+ # Fall back to dev environment import
1223+ import sys
1224+ import importlib .util
1225+ demo_data_path = directory / "demo_data.py"
1226+ if demo_data_path .exists ():
1227+ spec = importlib .util .spec_from_file_location ("demo_data" , demo_data_path )
1228+ demo_data_module = importlib .util .module_from_spec (spec )
1229+ sys .modules ["demo_data" ] = demo_data_module
1230+ spec .loader .exec_module (demo_data_module )
1231+ create_demo_database = demo_data_module .create_demo_database
1232+ else :
1233+ raise ImportError (f"Could not find demo_data.py at { demo_data_path } " )
1234+
11481235 from sidemantic .mcp_server import get_layer
11491236
11501237 layer = get_layer ()
11511238 demo_conn = create_demo_database ()
11521239 # Copy data from demo connection to layer's connection
11531240 for table in ["customers" , "products" , "orders" ]:
1154- table_data = demo_conn .execute (f"SELECT * FROM { table } " ).df () # noqa: F841
1155- layer .conn .execute (f"CREATE TABLE { table } AS SELECT * FROM table_data" )
1241+ # Get table data as regular Python objects (no pandas)
1242+ rows = demo_conn .execute (f"SELECT * FROM { table } " ).fetchall ()
1243+ columns = [desc [0 ] for desc in demo_conn .execute (f"SELECT * FROM { table } LIMIT 0" ).description ]
1244+
1245+ # Create table in target connection
1246+ create_sql = demo_conn .execute (f"SELECT sql FROM duckdb_tables() WHERE table_name = '{ table } '" ).fetchone ()[0 ]
1247+ layer .conn .execute (create_sql )
1248+
1249+ # Insert data if there are rows
1250+ if rows :
1251+ placeholders = ", " .join (["?" for _ in columns ])
1252+ layer .conn .executemany (f"INSERT INTO { table } VALUES ({ placeholders } )" , rows )
11561253
11571254 typer .echo (f"Starting MCP server for: { directory } " , err = True )
11581255 if db_path and db_path != ":memory:" :
0 commit comments