How to connect to DuckDB

Install the DuckDB extra

pip install semolina[duckdb]
# or
uv add "semolina[duckdb]"

The DuckDB extra installs duckdb and pyarrow for local semantic view queries.

Configure manually

When credentials come from a vault or secrets manager, pass a config object to create_engine():

from adbc_poolhouse import DuckDBConfig

from semolina import register, create_engine

engine = create_engine(
    DuckDBConfig(database="/path/to/warehouse.db")
)
register("default", engine)

Run a query

Once an engine is registered, the query API works the same as any backend:

from semolina import SemanticView, Metric, Dimension


class Sales(SemanticView, view="sales"):
    revenue = Metric()
    country = Dimension()


cursor = (
    Sales.query()
    .metrics(Sales.revenue)
    .dimensions(Sales.country)
    .execute()
)
for row in cursor.fetchall_rows():
    print(row.country, row.revenue)

Generated SQL

DuckDB uses a semantic_view() table function instead of the AGG() / MEASURE() syntax used by Snowflake and Databricks:

SELECT *
FROM semantic_view(
    'sales',
    dimensions := ['country'],
    metrics := ['revenue']
)

For row-level (facts) queries:

SELECT *
FROM semantic_view(
    'sales',
    facts := ['unit_price', 'quantity']
)

The semantic_views extension loads automatically when a DuckDB engine is built through create_engine(). You do not need to run INSTALL or LOAD manually.

Note

Semolina requires the semantic_views community extension v0.8.0 or later. Earlier versions failed to resolve table references through the ADBC driver. The auto-install runs INSTALL semantic_views FROM community, which pulls the latest build from the community repository.

Note

If you create a semantic view inside a test fixture and query it on the same connection, call commit() after the CREATE SEMANTIC VIEW statement. ADBC connections default to autocommit=False, and the extension expands semantic_view() on a separate read connection that only sees committed state, so an uncommitted view appears as not found. Production code that creates views through a separate session, or that uses autocommit=True, does not need this workaround.

See also