System architecture
How the control plane, engine, Query Runner, and warehouse sit across trust boundaries.
Updated
datahashi compiles a query in one place. It runs SQL in a different place. The engine compiles a Semantic Query into SQL. It never holds the warehouse password. It never opens a warehouse connection.
Parts
flowchart TD
B["Browser / Console"] -->|"HTTPS (sessions)"| CP["Control Plane<br/>hashi-api"]
CP -->|"Admin API (svctrust JWT)"| E["Query Engine<br/>hashi-engine"]
C["Consumers<br/>agents, BI, app"] -->|"REST / MCP / SQL Wire"| E
E -->|"parameterized SQL + runner key"| QR["Query Runner<br/>query-runner"]
QR -->|"native connection"| W[("Data Warehouse")]
Each layer owns a different job:
- Control plane (
hashi-api). It manages workspaces, model authoring, member login, and credentials. It never runs SQL. - Query engine (
hashi-engine). It verifies the caller. It resolves the Join Graph. It applies data roles and row-level security. It checks rollups in DuckDB. It compiles dialect SQL. It never opens a warehouse socket. - Query Runner (
query-runner). It runs next to your data. It stores the warehouse DSN. It applies table, field, and value policy. It runs SQL. It writes an on-disk audit log. - Data warehouse. PostgreSQL, MySQL, ClickHouse, Redshift, SQL Server, Databricks, Snowflake, BigQuery, or DuckDB. It accepts connections from Query Runner only.
Query lifecycle
A request moves through four stages:
sequenceDiagram
autonumber
participant C as Consumer
participant E as Engine
participant R as Query Runner
participant W as Warehouse
C->>E: POST /v1/query
Note over E: Authenticate token<br/>Resolve Join Graph<br/>Check DuckDB rollups<br/>Apply caller data roles<br/>Compile dialect SQL
E->>R: POST /v1/sql
Note over R: Audit statement
R->>W: Execute SQL
W-->>R: Rows
R-->>E: Streaming rows
E-->>C: JSON / CSV result
Trust boundaries
| Boundary | Authentication | What crosses the wire |
|---|---|---|
| Consumer → Engine | Bearer JWT or static API key | Structured Semantic Query (metrics, dimensions, filters) |
| Control Plane → Engine | Short-lived RS256 service JWT (svctrust) |
Published model artifacts, tenant configs, credential bindings |
| Engine → Query Runner | Bearer runner key (Authorization: Bearer <token>) |
Parameterized SQL string and argument list |
| Query Runner → Warehouse | Native database auth (DSN / IAM credentials) | Dialect SQL statements over encrypted driver connections |
Why compile and execute are separate
Other semantic layers need your warehouse password. If someone gets that password, they can read your warehouse.
datahashi does not take the password:
- No password on the engine. The hosted engine never receives your warehouse password. It never stores it.
- Kill switch. Pause or stop Query Runner. All query execution stops. You do not rotate API keys.
- No public warehouse. Your warehouse needs no public IP. Query Runner lives in your private network.
- Local audit log. Every SQL statement is written to a JSONL file that you control.
Rollup storage
flowchart TD
Router{"Pre-agg Router"}
Router -->|"Hit"| Duck["DuckDB Storage<br/>reads Parquet from ENGINE_ROLLUP_STORE_DIR"]
Router -->|"Miss"| QR["Query Runner<br/>sends compiled SQL to the warehouse"]
When you declare rollups, the engine checks the query against stored Parquet files.
- On a hit, DuckDB runs the query against Parquet files in
ENGINE_ROLLUP_STORE_DIR. - On a miss, the engine compiles dialect SQL. Query Runner runs it on the warehouse.
Next: Semantic model · Query Runner · Rollups.