@@ -57,7 +57,10 @@ def main(
5757def workbench (
5858 directory : Path = typer .Argument ("." , help = "Directory containing semantic layer files (defaults to current dir)" ),
5959 demo : bool = typer .Option (False , "--demo" , help = "Launch with demo data (multi-format example)" ),
60- db : Path = typer .Option (None , "--db" , help = "Path to DuckDB database file (optional)" ),
60+ connection : str = typer .Option (
61+ None , "--connection" , help = "Database connection string (e.g., postgres://host/db, bigquery://project/dataset)"
62+ ),
63+ db : Path = typer .Option (None , "--db" , help = "Path to DuckDB database file (shorthand for duckdb:/// connection)" ),
6164):
6265 """
6366 Interactive semantic layer workbench with SQL editor and charting.
@@ -67,7 +70,8 @@ def workbench(
6770 Examples:
6871 sidemantic workbench semantic_models/ # Your own models
6972 sidemantic workbench --demo # Try the demo
70- sidemantic workbench ./models --db data/warehouse.db # With database
73+ sidemantic workbench ./models --db data/warehouse.db # With DuckDB
74+ sidemantic workbench ./models --connection "postgres://localhost:5432/db"
7175 uvx sidemantic workbench --demo # Run demo without installing
7276 """
7377 from sidemantic .workbench import run_workbench
@@ -97,17 +101,18 @@ def workbench(
97101 raise typer .Exit (1 )
98102 else :
99103 # Build connection string from args or config
100- if db :
104+ connection_str = None
105+ if connection :
106+ # Explicit --connection arg provided
107+ connection_str = connection
108+ elif db :
101109 # Explicit --db arg provided
102- connection = f"duckdb:///{ db .absolute ()} "
110+ connection_str = f"duckdb:///{ db .absolute ()} "
103111 elif _loaded_config and _loaded_config .connection :
104112 # Use connection from config
105- connection = build_connection_string (_loaded_config )
106- else :
107- # Default behavior
108- connection = None
113+ connection_str = build_connection_string (_loaded_config )
109114
110- run_workbench (directory , connection = connection )
115+ run_workbench (directory , connection = connection_str )
111116
112117
113118@app .command ()
@@ -150,27 +155,46 @@ def query(
150155 directory : Path = typer .Argument (..., help = "Directory containing semantic layer files" ),
151156 sql : str = typer .Option (..., "--sql" , "-q" , help = "SQL query to execute" ),
152157 output : Path = typer .Option (None , "--output" , "-o" , help = "Output file (default: stdout)" ),
158+ connection : str = typer .Option (
159+ None , "--connection" , help = "Database connection string (e.g., postgres://host/db, bigquery://project/dataset)"
160+ ),
161+ db : Path = typer .Option (None , "--db" , help = "Path to DuckDB database file (shorthand for duckdb:/// connection)" ),
153162):
154163 """
155164 Execute a SQL query and output results as CSV.
156165
157- Example: sidemantic query /path/to/models --sql "SELECT orders.revenue FROM orders"
166+ Examples:
167+ sidemantic query models/ --sql "SELECT revenue FROM orders"
168+ sidemantic query models/ --sql "SELECT * FROM orders" --output results.csv
169+ sidemantic query models/ --connection "postgres://localhost:5432/db" --sql "SELECT revenue FROM orders"
170+ sidemantic query models/ --db data.duckdb --sql "SELECT revenue FROM orders"
158171 """
159172 if not directory .exists ():
160173 typer .echo (f"Error: Directory { directory } does not exist" , err = True )
161174 raise typer .Exit (1 )
162175
163176 try :
164- # Try to find database file
165- db_path = None
166- data_dir = directory / "data"
167- if data_dir .exists ():
168- db_files = list (data_dir .glob ("*.db" ))
169- if db_files :
170- db_path = f"duckdb:///{ db_files [0 ].absolute ()} "
177+ # Build connection string from args or config
178+ connection_str = None
179+ if connection :
180+ # Explicit --connection arg provided
181+ connection_str = connection
182+ elif db :
183+ # Explicit --db arg provided
184+ connection_str = f"duckdb:///{ db .absolute ()} "
185+ elif _loaded_config and _loaded_config .connection :
186+ # Use connection from config
187+ connection_str = build_connection_string (_loaded_config )
188+ else :
189+ # Try to find database file in data/
190+ data_dir = directory / "data"
191+ if data_dir .exists ():
192+ db_files = list (data_dir .glob ("*.db" ))
193+ if db_files :
194+ connection_str = f"duckdb:///{ db_files [0 ].absolute ()} "
171195
172196 # Load semantic layer
173- layer = SemanticLayer (connection = db_path )
197+ layer = SemanticLayer (connection = connection_str )
174198 load_from_directory (layer , str (directory ))
175199
176200 if not layer .graph .models :
@@ -352,7 +376,10 @@ def mcp_serve(
352376def serve (
353377 directory : Path = typer .Argument ("." , help = "Directory containing semantic layer files (defaults to current dir)" ),
354378 demo : bool = typer .Option (False , "--demo" , help = "Use demo data" ),
355- db : Path = typer .Option (None , "--db" , help = "Path to DuckDB database file (optional)" ),
379+ connection : str = typer .Option (
380+ None , "--connection" , help = "Database connection string (e.g., postgres://host/db, bigquery://project/dataset)"
381+ ),
382+ db : Path = typer .Option (None , "--db" , help = "Path to DuckDB database file (shorthand for duckdb:/// connection)" ),
356383 port : int = typer .Option (None , "--port" , "-p" , help = "Port to listen on (overrides config)" ),
357384 username : str = typer .Option (None , "--username" , "-u" , help = "Username for authentication (overrides config)" ),
358385 password : str = typer .Option (None , "--password" , help = "Password for authentication (overrides config)" ),
@@ -366,6 +393,8 @@ def serve(
366393 Examples:
367394 sidemantic serve ./models --port 5433
368395 sidemantic serve ./models --db data/warehouse.db
396+ sidemantic serve ./models --connection "postgres://localhost:5432/analytics"
397+ sidemantic serve ./models --connection "bigquery://project/dataset" --port 5433
369398 sidemantic serve --demo
370399 sidemantic serve ./models --username user --password secret
371400 """
@@ -399,24 +428,28 @@ def serve(
399428 typer .echo (f"Error: Directory { directory } does not exist" , err = True )
400429 raise typer .Exit (1 )
401430
402- # Resolve db from args or config
403- db_resolved = None
404- if db :
405- db_resolved = db
431+ # Build connection string from args or config
432+ connection_str = None
433+ if connection :
434+ # Explicit --connection arg provided
435+ connection_str = connection
436+ elif db :
437+ # Explicit --db arg provided
438+ connection_str = f"duckdb:///{ db .absolute ()} "
406439 elif _loaded_config and _loaded_config .connection :
407- from sidemantic .config import DuckDBConnection
408-
409- if isinstance (_loaded_config .connection , DuckDBConnection ):
410- db_resolved = Path (_loaded_config .connection .path ) if _loaded_config .connection .path != ":memory:" else None
440+ # Use connection from config
441+ connection_str = build_connection_string (_loaded_config )
442+ else :
443+ # Default to in-memory DuckDB
444+ connection_str = "duckdb:///:memory:"
411445
412446 # Resolve port, username, password from args or config
413447 port_resolved = port if port is not None else (_loaded_config .pg_server .port if _loaded_config else 5433 )
414448 username_resolved = username or (_loaded_config .pg_server .username if _loaded_config else None )
415449 password_resolved = password or (_loaded_config .pg_server .password if _loaded_config else None )
416450
417- # Create semantic layer with explicit db or in-memory
418- db_path = f"duckdb:///{ Path (db_resolved ).absolute ()} " if db_resolved else "duckdb:///:memory:"
419- layer = SemanticLayer (connection = db_path )
451+ # Create semantic layer with connection string
452+ layer = SemanticLayer (connection = connection_str )
420453
421454 # Load models
422455 load_from_directory (layer , str (directory ))
0 commit comments