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
22 changes: 11 additions & 11 deletions docs/CONFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ regenerate with `ls -d fixtures/<corpus>/*/ | wc -l` for directory-shaped corpor
| [`fixtures/render-conformance/`](../fixtures/render-conformance/) | 15 | ✓ | ✓ | inherits via Java | ✓ | ✓ |
| [`fixtures/extract-conformance/`](../fixtures/extract-conformance/) | 48 | ✓ | ✓ | inherits the shared JVM engine | ✓ | ✓ |
| [`fixtures/output-prompt-conformance/`](../fixtures/output-prompt-conformance/) | 17 | ✓ | ✓ | ✓ | ✓ | ✓ |
| [`fixtures/persistence-conformance/`](../fixtures/persistence-conformance/) | 42 (36 query + 6 migration) | all 42 (the 9 `report-*` also on SQLite, MySQL and D1) | 36 query (migrations TS-only, ADR-0015); the 9 `report-*` also on MySQL | 36 query (via Exposed); the 9 `report-*` also on SQLite and MySQL | 36 query; the 9 `report-*` also on SQLite and MySQL | 36 query (Postgres only: pg8000) |
| [`fixtures/api-contract-conformance/`](../fixtures/api-contract-conformance/) | 82 (31 core + 10 tph + 9 m2m + 2 jsonb + 2 write-through + 12 projection + 16 report) | ✓ (Fastify reference + generated lane); `report/` also on SQLite, MySQL and D1 | ✓ (embedded HTTP + JDBC) | ✓ (embedded HTTP + Exposed) | ✓ (HttpListener + Npgsql); `report/` also on SQLite and MySQL | ✓ (FastAPI + pg8000) |
| [`fixtures/persistence-conformance/`](../fixtures/persistence-conformance/) | 52 (46 query + 6 migration) | all 52 (the 19 `report-*` also on SQLite, MySQL and D1) | 46 query (migrations TS-only, ADR-0015); the 19 `report-*` also on MySQL | 46 query (via Exposed); the 19 `report-*` also on SQLite and MySQL | 46 query; the 19 `report-*` also on SQLite and MySQL | 46 query (Postgres only: pg8000) |
| [`fixtures/api-contract-conformance/`](../fixtures/api-contract-conformance/) | 88 (31 core + 10 tph + 9 m2m + 2 jsonb + 2 write-through + 12 projection + 22 report) | ✓ (Fastify reference + generated lane); `report/` also on SQLite, MySQL and D1 | ✓ (embedded HTTP + JDBC) | ✓ (embedded HTTP + Exposed) | ✓ (HttpListener + Npgsql); `report/` also on SQLite and MySQL | ✓ (FastAPI + pg8000) |
| [`fixtures/validation-conformance/`](../fixtures/validation-conformance/) | 42 cases | ✓ (generated Zod + run-time `runValidators`) | ✓ | ✓ | ✓ | ✓ (generated Pydantic + run-time `run_validators`) |
| [`fixtures/registry-conformance/`](../fixtures/registry-conformance/) | 1 canonical manifest | ✓ (reference emitter) | ✓ | ✓ | ✓ | ✓ |
| [`fixtures/object-model-conformance/`](../fixtures/object-model-conformance/) | 1 shared metadata fixture (per-port scenarios) | ✓ | ✓ | ✓ | ✓ | ✓ |
Expand Down Expand Up @@ -136,13 +136,13 @@ manifest contains without moving these boundaries.
(`scripts/ci-local.sh --only cube`, which needs Docker) loads the canonical golden and the 34
cases that hold a tree into a pinned Cube (`cubejs/cube:v1.7.43`) and requires each to
compile, the two MySQL cases included: compiling a model runs no SQL, so the lane's Postgres
data source serves them too. For the nine canonical reports it compares the Cube query with the
data source serves them too. For the canonical reports it compares the Cube query with the
report view (a `@spine` report through its Cube view, its empty spine rows included), and for
the `escaping` and `measure-default` cases it reads the SQL back from Cube's `/v1/sql`.
A second live file (`cube-model-mysql.live.ts`) runs the MySQL path: the canonical golden
generated with `dialect: "mysql"` is loaded into the same pinned Cube over a real MySQL 8.4
holding the report views `buildReportViews` lowers (`fixtures/cube-model/canonical/seed.mysql.sql`),
and each of the nine reports' Cube query is compared with `SELECT *` from its view, with the
and each of the twenty-one served reports' Cube query is compared with `SELECT *` from its view, with the
pre-aggregation used asserted. That is the smaller of the two honest options: the two MySQL
*cases* (escaping, a quoted identifier) are still compiled, not executed, and the goldens hold
their SQL. The lane runs Cube in development mode, so Cube's production mode with a separate
Expand Down Expand Up @@ -298,12 +298,12 @@ trailing-newline preservation, and unicode multibyte handling.
All 31 fixtures → [features/migrations-and-drift.md](features/migrations-and-drift.md)
(template drift section — `Renderer.verify`).

### `fixtures/persistence-conformance/` (42 — 36 query + 6 migration)
### `fixtures/persistence-conformance/` (52 — 46 query + 6 migration)

- `migrations/*` (6) → [features/migrations-and-drift.md](features/migrations-and-drift.md) (schema migration section)
- `queries/*` (36) → [features/source-kinds.md](features/source-kinds.md) (query semantics against `source.rdb`)
- `queries/*` (46) → [features/source-kinds.md](features/source-kinds.md) (query semantics against `source.rdb`)

**Engines beyond Postgres (FR-044 reports).** The nine `queries/report-*.yaml` scenarios also
**Engines beyond Postgres (FR-044 reports).** The nineteen `queries/report-*.yaml` scenarios also
run on SQLite and MySQL 8.4 wherever a port's runtime has the engine, and on D1's local runtime
in TypeScript (no cloud account): TypeScript on all four, C# and Kotlin on SQLite and MySQL, Java
on MySQL. **Stated limitations, not gaps:** Java's OMDB has no SQLite driver, and Python's runtime
Expand All @@ -317,9 +317,9 @@ REAL, a MySQL `DECIMAL` keeps its scale, a MySQL `DATETIME` is the UTC wall cloc
`report-relative-date` carries a per-engine seed (`seed-data-engine`). `op: relate` and the
write ops stay Postgres-only. See [features/reporting.md](features/reporting.md#what-the-corpus-gates).

### `fixtures/api-contract-conformance/` (82)
### `fixtures/api-contract-conformance/` (88)

All 82 scenarios → [features/api-contract.md](features/api-contract.md) (cross-port
All 88 scenarios → [features/api-contract.md](features/api-contract.md) (cross-port
REST API URL grammar + JSON wire format). Verifies every backend's emitted CRUD
routes answer identically over HTTP — list / get / create / patch+put / delete,
plus pagination (`limit`/`offset`), sort (`sort=field:dir`), the `withCount=1`
Expand All @@ -337,7 +337,7 @@ each corpus alone could not reach, and 1 pinning the collection-URL spelling),
`write-through/` (2, table-write + view-read entities), `projection/` (12, a
read-only view answers reads and refuses writes with 405; a projection with no declared
identity has no item route; a projection keyed on a field not named `id` is addressed by
it, whether its identity names `@fields` or derives them; decimal and float fields filter) and `report/` (16,
it, whether its identity names `@fields` or derives them; decimal and float fields filter) and `report/` (22,
FR-044: a view-backed `object.report` is listed, filtered, sorted and paged on
its derived fields, answers `POST` with 405 and mounts no `/{id}`; a `@spine` report keeps
a row with no facts, and a measure with `@default` filters and sorts as its default). All 5 ports — TS, Java,
Expand Down Expand Up @@ -523,7 +523,7 @@ files into a real Cube.
## Orphaned fixtures (tested but not yet documented)

The fixtures in the nine corpora mapped above (metamodel 374 + yaml 16 + verify 31
+ render 15 + persistence 42 + api-contract 82 + source-resolution 25 + scope 10 +
+ render 15 + persistence 52 + api-contract 88 + source-resolution 25 + scope 10 +
dependency 23) each map to a feature doc, and so do the two requirement corpora and the
`cube-model` corpus, whose case lists live in their own READMEs. None are orphaned today. The remaining
corpora in the totals table gate tooling contracts (registry manifests, provider
Expand Down
2 changes: 1 addition & 1 deletion docs/features/api-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ the empty rows, `isNull=true` matches nothing, and a sort on it orders the same
engine.

Gated by [`fixtures/api-contract-conformance/report/`](../../fixtures/api-contract-conformance/report/)
(16 scenarios), **generated lane only, on all five ports**, for the reason `projection/`
(22 scenarios), **generated lane only, on all five ports**, for the reason `projection/`
gives. The columns, their types and the per-port generated files are in
[reporting.md](reporting.md#how-a-report-is-served).

Expand Down
12 changes: 7 additions & 5 deletions docs/features/cube-export.md
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,8 @@ there.
| Grains | Cube offers every granularity on a time dimension. | `@grains` is carried as `meta.grains`, not enforced. |
| Ad-hoc queries | Cube lets a caller pick any members. | Nothing here checks the numbers for a combination no report declares. |
| Rollup freshness | Cube refreshes a rollup on its refresh key, and the exporter writes none, so Cube's default applies. | A rollup can trail the table until it refreshes. The view is never stale. A report with a relative date gets no rollup for this reason. |
| Empty scope | A report with no dimension whose scope reads no rows returns one row from the view: the count `0` and each measure's `@default`. Cube answers from the rollup, which is empty, and sums it to null for every measure, the count and the default included. | `ProgramsEmptyScope` is the one canonical report where the rows differ; the live check requires both answers, so the difference stays an executed check. |
| Several grains | A report that lists one time dimension at several grains gets a rollup naming that dimension once per grain. A Cube query names a time dimension once, at one granularity, so Cube 1.7.43 matches no query to that rollup. | The rollup is declared and never used; Cube answers from the source table and the rows equal the view's. `ProgramsOverTime` is the canonical case. |
| Encodings | Cube's REST API sends numbers as strings and time buckets as `YYYY-MM-DDTHH:MM:SS.sss`. Where a rollup sends `60`, the view sends `60.0000000000000000`, and where Cube sends `2026-05-01T00:00:00.000`, the view sends `2026-05-01`. | Clients normalize them. The live check does, before it compares rows. |

## Checking it
Expand Down Expand Up @@ -584,13 +586,13 @@ container and the network are removed on every exit path. It checks six things o

1. The generated canonical model is the reviewed golden.
2. Cube compiles it and lists every cube and member the files declare.
3. For each of the nine canonical reports, the Cube query above, answered from the report's
3. For each of the canonical reports, the Cube query above, answered from the report's
rollup when the model has one (the lane asserts the rollup name in `usedPreAggregations`),
returns the rows of `SELECT * FROM <view>`, after the normalization the Encodings row above
describes. The two `@spine` reports, `ProgramRoster` and `ProgramLongWeeks`, are queried
through their Cube views, and their 7 rows each include the programs with no weeks (and, for
`ProgramLongWeeks`, the two whose weeks are all short); `FitnessTotalsFilled`, a report of
defaulted measures, is answered from its rollup.
describes. The three `@spine` reports, `ProgramRoster`, `ProgramLongWeeks` and
`ProgramSessions`, are queried through their Cube views, and their 11 rows each include the
programs with no weeks (and, for `ProgramLongWeeks`, the two whose weeks are all short);
`FitnessTotalsFilled`, a report of defaulted measures, is answered from its rollup.
4. Every case of the mapping corpus that has an expected tree (34 of the 44: 32 Postgres and
2 MySQL) compiles in the same Cube, and each `title` and `description` it declares comes back
from `/v1/meta` as declared. Compiling runs no SQL, so the MySQL cases compile against the
Expand Down
14 changes: 8 additions & 6 deletions docs/features/reporting.md
Original file line number Diff line number Diff line change
Expand Up @@ -621,19 +621,19 @@ the anti-join by hand, and make two reports.

### What the corpus gates

Nine shared scenarios under `fixtures/persistence-conformance/queries/report-*.yaml` read the
Nineteen shared scenarios under `fixtures/persistence-conformance/queries/report-*.yaml` read the
canonical reports through every port's runtime (list and count, filter, sort, an empty table,
the Monday boundary, an hour bucket, a relative window, a `@spine` report with a program that
has no weeks, a `@spine` report whose segment leaves a program no facts, and defaulted measures
over an empty table). The derived columns are pinned by
over an empty table; and, beyond those, every `@agg` kind over int, long, float, double, decimal, currency, date and instant columns, a nullable int-backed enum, a boolean and a uuid as dimensions, `day`, `quarter` and `year` grains together, every filter operator on a measure, a report `@segment` and `@filter` together, a two-hop `@via`, a self-referencing dimension, a report declared from a TPH base, and a negative `@default`). The derived columns are pinned by
`fixtures/persistence-conformance/report-shapes.json`, produced by TypeScript and byte-matched
by every port. The SQL is produced by TypeScript only, so the other ports read the view the
TypeScript migrate engine produced and never lower a report themselves. TypeScript's own value
tests read the `@spine` and `@default` views and their column types on Postgres, SQLite and
MySQL, and on Postgres and SQLite also a fact whose reference is null, an `isNull` condition
and a two-hop spine.

The nine scenarios run on more than Postgres, wherever a port's runtime has the engine:
The nineteen report scenarios run on more than Postgres, wherever a port's runtime has the engine:

| | Postgres | SQLite | MySQL 8.4 | D1 (local runtime) |
|---|---|---|---|---|
Expand All @@ -651,21 +651,23 @@ stay the Postgres wire ones, never loosened: the engine's own spelling (a REAL r
a scale-keeping `DECIMAL` and a zoneless `DATETIME` on MySQL) is mapped on the actual side by
each runner, and `report-relative-date` spells its seed per engine (`seed-data-engine`). D1
has no separate corpus: it is SQLite behind Cloudflare's binding, so TypeScript runs the same
scenarios and the same sixteen REST scenarios through Miniflare's local D1 (`meta migrate
scenarios and the same twenty-two REST scenarios through Miniflare's local D1 (`meta migrate
--dialect d1`), with no cloud account.

A port without the engine is a stated limitation, not a gap to fill by building a driver:
Java's OMDB and Python's `ObjectManager` ship the drivers they ship, and a view-reading test
does not justify a new one.

The REST surface is gated by sixteen scenarios under
The REST surface is gated by twenty-two scenarios under
[`fixtures/api-contract-conformance/report/`](../../fixtures/api-contract-conformance/report/),
run in the **generated lane on all five ports**: list (a dimension with a segment-scoped sum
that is null for one group), a time dimension at a grain (`YYYY-MM-DD`), a no-dimension
totals report with the `withCount` envelope, a filter on a dimension and on a measure, a sort
on a measure and an enum dimension, paging over groups, the three field-naming `400` envelopes,
`405` on `POST`, `404` on every verb at `/{id}`, and a `@spine` report whose product with no
sale keeps its row, with a filter and a sort on its defaulted measure. The corpus model
sale keeps its row, with a filter and a sort on its defaulted measure, a dimension reached through
two references, one time dimension listed at two grains, a filter on a string (`like`), a boolean and a
uuid dimension, and a filter on a measure that carries its own `@filter`. The corpus model
carries one sourceless report, so a port that serves every report it finds fails. No scenario asserts a decimal's
spelling or a timestamp literal. TypeScript and C# run the scenarios against the real views on
Postgres, SQLite and MySQL (`report/schema.sqlite.sql`, `report/schema.mysql.sql`), and
Expand Down
Loading
Loading