Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ WrenAI is the open context layer that fills that gap. You model your business in

A Rust engine powered by [Apache DataFusion](https://datafusion.apache.org/) translates the modeled SQL and runs it against 20+ data sources (PostgreSQL, BigQuery, Snowflake, Spark, etc.). Use it as a Python SDK, a CLI, a WASM module in the browser, or as building blocks for agent skills.

**Pre-aggregation cubes** — model business metrics once (revenue, order count, retention) with measures, dimensions, and time grains. AI agents query cubes with a structured input instead of hand-writing `GROUP BY` / `DATE_TRUNC` SQL, cutting error rates substantially on small / local models. See the [Cube guide](./docs/core/guides/modeling/cube.md).

## Quick start

The fastest path is to let an AI coding agent (Claude Code, Cursor, Aider, etc.) drive the install:
Expand Down
15 changes: 15 additions & 0 deletions core/wren/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,21 @@ wren --sql 'SELECT order_id FROM "orders" LIMIT 10'

For the full CLI reference and per-datasource connection field reference, see [`docs/cli.md`](docs/cli.md) and [`docs/connections.md`](docs/connections.md).

**4a. (Optional) Aggregation queries with cubes** — define cubes under `cubes/`,
then query them with a structured input instead of writing `GROUP BY` SQL by hand:

```bash
wren cube list
wren cube describe order_metrics
wren cube query --cube order_metrics --measures revenue --time-dimension "created_at:month"
```

The translator produces `DATE_TRUNC` / `GROUP BY` / `WHERE` clauses for you and
runs them through the same engine path as `wren --sql`. See the
[Cube guide](../../docs/core/guides/modeling/cube.md) for full YAML structure
and the [CLI reference](../../docs/core/reference/cli.md#wren-cube--pre-aggregation-queries) for all
flags.

**5. (Optional) Configure security policy** — create `~/.wren/config.json`:

```json
Expand Down
21 changes: 21 additions & 0 deletions docs/core/get_started/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,27 @@ The more you ask, the smarter the system gets — each stored query improves fut

---

## Step 8 — Query a cube (optional)

If your MDL defines cubes, use the cube CLI for aggregation queries — agents
don't have to hand-write `GROUP BY` / `DATE_TRUNC` SQL:

```bash
wren cube list

wren cube query \
--cube order_metrics \
--measures revenue \
--time-dimension "created_at:month"
```

Cube queries are the recommended path for aggregation when a cube covers the
question. Lower error rate, especially on small / local models. See the
[Cube guide](../guides/modeling/cube.md) for the YAML structure and the
[CLI reference](../reference/cli.md#wren-cube--pre-aggregation-queries) for all flags.

---

## What's in the project

After setup, your project directory looks like this:
Expand Down
184 changes: 184 additions & 0 deletions docs/core/guides/modeling/cube.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# Cube

A **Cube** is a pre-aggregation semantic layer object that defines reusable
aggregations on top of a Model or View. Clients send a structured `CubeQuery`
(measures, dimensions, optional time bucket + filters); the engine produces
`SELECT … GROUP BY` SQL and runs it through the same path as `wren --sql`.

## When to use

Define a cube when you want to:

- run aggregation queries (`SUM`, `COUNT`, `AVG`) grouped by dimensions
- group by time (`year` / `quarter` / `month` / `week` / `day` / `hour` / `minute`)
- share business metrics between AI agents, BI dashboards, and the browser SDK
- expose drill-down hierarchies for dashboard navigation

Cubes are particularly useful for AI agents: instead of writing `GROUP BY` and
`DATE_TRUNC` SQL by hand (and getting it wrong on small / local models), an
agent picks a measure + dimension + granularity from the cube definition and
the translator builds the SQL.

## Structure

Each cube lives in its own file under `cubes/` as `cubes/<name>.yml`. The
YAML uses `snake_case`; `wren context build` converts to `camelCase` for
the engine.

```yaml
# cubes/order_metrics.yml
name: order_metrics
base_object: orders # name of a defined Model or View

measures:
- name: revenue
expression: "SUM(o_totalprice)"
type: DOUBLE
- name: order_count
expression: "COUNT(*)"
type: BIGINT
- name: avg_order_value
expression: "revenue / order_count" # ← derived measure (auto-inlined)
type: DOUBLE

dimensions:
- name: status
expression: "o_orderstatus"
type: VARCHAR

time_dimensions:
- name: created_at
expression: "o_orderdate"
type: DATE

hierarchies:
time_drill:
- created_at # add finer-grained levels here for drill-down
```

### JSON format (MDL manifest)

After `wren context build`, the same cube serialises to camelCase:

```json
{
"name": "order_metrics",
"baseObject": "orders",
"measures": [
{ "name": "revenue", "expression": "SUM(o_totalprice)", "type": "DOUBLE" }
],
"dimensions": [
{ "name": "status", "expression": "o_orderstatus", "type": "VARCHAR" }
],
"timeDimensions": [
{ "name": "created_at", "expression": "o_orderdate", "type": "DATE" }
],
"hierarchies": { "time_drill": ["created_at"] }
}
```

## Fields

| Field | Required | Description |
|-------|----------|-------------|
| `name` | Yes | Unique cube identifier (used by `wren cube describe`, `cubeQuery.cube`, …) |
| `base_object` | Yes | Name of a defined Model or View; becomes `FROM <base_object>` in the generated SQL |
| `measures` | Yes | List of `{ name, expression, type }`. `expression` may reference physical columns or other measure names (derived measure) |
| `dimensions` | No | List of `{ name, expression, type }` used for `GROUP BY` and filters |
| `time_dimensions` | No | List of `{ name, expression, type }`. Granularity is picked at query time, not in the cube definition |
| `hierarchies` | No | Map of `name → [dimension_names]`, for BI drill-down navigation. Levels must reference declared dimensions or time dimensions. |

## Time granularity

Supported values at query time: `year`, `quarter`, `month`, `week`, `day`,
`hour`, `minute`.

When a query specifies a time dimension with a granularity, the translator
emits `DATE_TRUNC(granularity, expr)` in the projection and `GROUP BY`. The
column alias is `<name>__<granularity>` (e.g., `created_at__month`). An
optional `dateRange: [start, end]` becomes a half-open `[start, end)` `WHERE`
clause.

## Derived measures

A measure's `expression` may reference other measures by name:

```yaml
- name: avg_order_value
expression: "revenue / order_count"
```

The translator inlines `revenue` and `order_count` before emitting SQL:

```text
avg_order_value → (SUM(o_totalprice)) / (COUNT(*))
```

Substitution is longest-prefix-first to avoid partial-token replacement
(e.g., `revenue_2` substitutes before `revenue`). **Only the transitive
closure of measures actually requested by the query is resolved** — a cycle
between unrelated measures elsewhere in the cube does not fail a query that
never references them.

Expressions containing `$` (Postgres `$1` placeholders or `$$tag$$`
dollar-quoted strings) are preserved literally — the translator does not
treat them as regex capture-group templates.

## Filter operators

`cubeQuery.filters` accepts these operators:

`eq` · `neq` · `in` · `not_in` · `gt` · `gte` · `lt` · `lte` ·
`contains` · `starts_with` · `is_null` · `is_not_null`

`in` / `not_in` take an array value. `is_null` / `is_not_null` take no value.
`contains` / `starts_with` produce `LIKE` patterns.

## Cube vs. View vs. Model

| Use case | Use |
|---|---|
| Expose raw rows (optionally with calculated fields) | [Model](./model.md) |
| Name a complex `SELECT` for reuse | [View](./view.md) |
| Predefined aggregation API (measures × dimensions) for agents / BI | **Cube** |

## CLI

- `wren cube list` — list every cube in the loaded MDL
- `wren cube describe <name>` — pretty-print the cube schema
- `wren cube query` — build a CubeQuery (CLI flags or `--from <json>`) and run it
- `wren cube query --sql-only ...` — print the generated SQL without executing

See the [CLI reference](../../reference/cli.md#wren-cube--pre-aggregation-queries).

## WASM (browser)

The same translator is exposed in `@wrenai/wren-core-wasm`:

```javascript
const cubes = engine.listCubes();
const rows = await engine.cubeQuery({
cube: "order_metrics",
measures: ["revenue"],
timeDimensions: [{ dimension: "created_at", granularity: "month" }],
});
```

See the [WASM Agent Guide](https://github.com/Canner/WrenAI/blob/main/core/wren-core-wasm/AGENT_GUIDE.md)
and the [`cube-explorer.html`](https://github.com/Canner/WrenAI/blob/main/core/wren-core-wasm/examples/cube-explorer.html)
demo for an interactive form-driven builder.

## Validation

Wren-core validates cubes during `AnalyzedWrenMDL::analyze` — i.e., when the
manifest is loaded into the engine, not just at query time:

- `base_object` must resolve to a defined Model or View
- Derived measures must not form a cycle within the transitive closure of
requested measures
- Levels in `hierarchies` must reference a declared `dimension` or
`time_dimension`

The CLI's `wren context validate` also runs structural checks on cube YAML
(unique names, `base_object` exists, hierarchy levels) before the manifest
reaches the engine, so common mistakes surface at edit time.
19 changes: 19 additions & 0 deletions docs/core/guides/modeling/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,24 @@ Views are useful when the object you want to expose is query-shaped rather than

See [View](./view.md).

### Cube

A **Cube** is a pre-aggregated semantic object: a `baseObject` (a Model or
View), plus declared measures, dimensions, time dimensions, and optional
hierarchies.

Use a cube when you need to:

- expose pre-defined aggregations (e.g., total revenue by month)
- give AI agents a structured aggregation API (no hand-written `GROUP BY`)
- define drill-down hierarchies (year → quarter → month) for BI dashboards
- share business metrics across CLI, browser (WASM), and downstream consumers

Cubes complement models: a model exposes the rows; a cube exposes the metrics
defined on top of those rows.

See [Cube](./cube.md).

### Memory

The **Memory** layer is a LanceDB-backed semantic index that gives AI agents targeted schema context and few-shot query examples — without sending the entire schema in every prompt.
Expand All @@ -98,6 +116,7 @@ Use this rule of thumb:
- Use a **Relationship** to define how models join to each other.
- Use a **Calculated Field** to define reusable expression logic inside a model.
- Use a **View** to publish a reusable query result.
- Use a **Cube** to publish a structured aggregation API (measures × dimensions).
- Use **Memory** to give AI agents targeted context and learning from past queries.

## Why this matters
Expand Down
69 changes: 69 additions & 0 deletions docs/core/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,72 @@ Drop all memory tables and start fresh.
wren memory reset # prompts for confirmation
wren memory reset --force # skip confirmation
```

---

## `wren cube` — Pre-aggregation Queries

For aggregation queries where the MDL defines cubes, use `wren cube` instead
of writing raw SQL. The translator produces correct `GROUP BY`, `DATE_TRUNC`,
and `WHERE` clauses from a structured input.

### `wren cube list`

List all cubes in the loaded MDL with their measures and dimensions.

```bash
wren cube list
```

### `wren cube describe <name>`

Pretty-print the full cube schema as JSON: `baseObject`, measures (with
expressions), dimensions, time dimensions, hierarchies.

```bash
wren cube describe order_metrics
```

### `wren cube query`

Build a CubeQuery and translate it to SQL via wren-core, then execute through
the same path as `wren --sql`. Two input modes:

**CLI flags:**

```bash
wren cube query \
--cube order_metrics \
--measures revenue,order_count \
--dimensions status \
--time-dimension "created_at:month:2024-01-01,2025-01-01" \
--filter "status:eq:completed" \
--limit 100
```

**JSON input** (`--from <file|->`):

```bash
cat query.json | wren cube query --from -
```

| Flag | Description |
|------|-------------|
| `--cube` | Cube name (required unless using `--from`) |
| `--measures` | Comma-separated measure names (required unless using `--from`) |
| `--dimensions` | Comma-separated dimension names |
| `--time-dimension` | `<name>:<granularity>[:start,end]` — one time dimension with optional date range |
| `--filter` | Repeatable. `<dimension>:<operator>[:value]`. For `in` / `not_in`, value is comma-separated. |
| `--limit` / `--offset` | Pagination |
| `--from <file\|->` | Load CubeQuery as JSON from a file or stdin |
| `--sql-only` | Print the generated SQL and exit without executing |
| `--mdl` | Path to MDL JSON (defaults to `<project>/target/mdl.json`) |
| `--output` | `table` (default), `json`, `csv` |

**Supported granularities:** `year`, `quarter`, `month`, `week`, `day`, `hour`, `minute`.

**Supported filter operators:** `eq`, `neq`, `in`, `not_in`, `gt`, `gte`, `lt`,
`lte`, `contains`, `starts_with`, `is_null`, `is_not_null`.

See the [Cube guide](../guides/modeling/cube.md) for YAML structure and
validation rules.
2 changes: 1 addition & 1 deletion skills/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
},
{
"name": "wren-usage",
"version": "2.2",
"version": "2.3",
"description": "Wren Engine CLI workflow guide for AI agents. Triggers on data questions, reports, metrics, revenue, trends, 'how many', 'show me', 'top N', 'compare', 'breakdown'. Answer data questions end-to-end using the wren CLI.",
"tags": [
"wren",
Expand Down
2 changes: 1 addition & 1 deletion skills/versions.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"wren-dlt-connector": "1.0",
"wren-generate-mdl": "2.2",
"wren-onboarding": "2.1",
"wren-usage": "2.2"
"wren-usage": "2.3"
}
11 changes: 9 additions & 2 deletions skills/wren-generate-mdl/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,19 @@ This creates:
```text
project/
├── wren_project.yml
├── models/
├── views/
├── models/ # business-facing tables/models
├── views/ # named SQL statements
├── cubes/ # pre-aggregation cubes (measures + dimensions)
├── relationships.yml
└── instructions.md
```

> **When to define cubes:** If the user asks aggregation questions like
> "revenue by month" or "top customers", define cubes alongside models —
> they give agents a structured query API instead of forcing them to
> hand-write `GROUP BY` / `DATE_TRUNC` SQL. See the
> [Cube guide](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/modeling/cube.md).

> **IMPORTANT: `catalog` and `schema` in `wren_project.yml`**
>
> These are Wren Engine's internal namespace — they are NOT the database's
Expand Down
Loading
Loading