Our engine never sees your warehouse password
datahashi

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:

  1. Control plane (hashi-api). It manages workspaces, model authoring, member login, and credentials. It never runs SQL.
  2. 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.
  3. 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.
  4. 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.