Skip to content

Release notes

0.1.13

New features

  • Levels of detail. An aggregate can now be computed at a grain other than the query's. A field pins a fixed grain, such as COUNT(DISTINCT event_date) per user, and becomes an axis, so "users active exactly N days" is one query. A metric pins include, exclude or fixed relative to the query's grouping: an average per user reported by channel, or a share whose total no filter narrows. See D-LOD.
  • Two facts aligned on a level of detail. When each fact has its own fixed field — how many days a user had a session, how many days they ordered — a conformed pairing (D-CONFORM) between the two makes them one axis, so a metric from either fact can be grouped by it.
  • A level of detail over another dataset. A fixed field's base can now aggregate a related dataset, such as each flight's open alerts, when the keys meet through a declared many-to-one relationship or a conformed pairing. A row with nothing on the other side counts 0, so > 0 and = 0 in a where answer "has one" and "has none". See D-LOD across datasets.
  • A level of detail with a window of its own. When a fixed field's base is a parameterized filter metric, the query's params bind it, so "January actives with no February login" is one query: the model declares the window as parameters and the query supplies the days. See A level-of-detail field's base.
  • Detail queries read levels of detail. dosi select can project, filter and order by a fixed field of its --from dataset, and --param binds that field's window. It also gains --distinct and --context-filter, which scopes the rollup the way a time range does. See Level-of-detail fields.

Enhancements

  • The DATUS extension version is now 1.11. Models that declare an older version load as before.
  • A string parameter may leave its domain open instead of listing allowed, for a date spelled the way its column stores it or a code the model cannot enumerate. It compiles with a datus_param_unbounded warning and still binds as a quoted literal. See Parameterized metrics.
  • When every value a fixed rollup computes comes from a filter metric, such as a field with a window of its own, the filter is now also the rollup's WHERE, so a warehouse can prune partitions instead of scanning the whole table. The values are unchanged.
  • list_dimensions for a metric now reports the metric's time column once, as metric_time, instead of listing the column beside it. Other time columns are still listed, and the column remains a legal group_by.

Bug fixes

  • dosi validate --osi-basic reported a model as invalid when the upstream Ossie validator could not run at all, for instance because its Python libraries were missing. Anything that stops the validator from running is a notice, not a verdict, and the validator now provisions its own dependencies through uv when it is installed.
  • ROUND in a field or metric expression rounded halves to even on PostgreSQL, MySQL, TiDB and ClickHouse, so ROUND(0.5) was 0 there and 1 everywhere else, and a binned dimension put its .5 rows in a different bucket. It now rounds halves away from zero on every warehouse.
  • A DATE_TRUNC('MONTH', ...) written into a model expression failed on MySQL and TiDB, which have no such function. It is now rewritten for them the way a query's own time grain is.
  • On ClickHouse, a time dimension stored as text failed the whole query when any of its values was NULL. NULL values now land in the NULL group, as on other warehouses.
  • list_dimensions for a metric hid the columns of a D-CONFORM pairing as foreign keys and listed a conformed dimension once per spelling. A paired column is now judged like any other field and listed once, under the metric's spelling.
  • A count metric used as a member of a compose with a window member read 0, not NULL, for a group its fact had no rows for, so a weighted score such as (… ) / store_count became 0 / 0 — NaN on DuckDB, an error on PostgreSQL. Only a count that is itself selected fills 0 (S-FAN-6); a count inside an expression now keeps NULL under a window stage too.

Behavior changes

  • A metric or detail query that carries a key its type does not define is now refused with unknown_query_key, naming the keys it does define. Before, the key was silently ignored, so a misspelled context_filter or a where meant as where_sql returned unfiltered numbers.

Known issues

  • A division with arithmetic on either side, such as (a + b) / c or a / NULLIF(b, 0), still runs with the warehouse's own /: PostgreSQL, Trino and Redshift truncate integer division, and MySQL and TiDB keep four decimal places. Only a plain SUM(a) / SUM(b) is converted to floating point; elsewhere write CAST(... AS DOUBLE) on the numerator.
  • 0.1.13 still reads only the older document format, the semantic_model: list. See the 0.1.11 note below.

0.1.12

New features

  • Ranked scorecards. A window metric can now rank a ratio or a composed metric, and it can count its whole partition with frame.scope: "partition" (the N beside a rank). Windows can stack up to three levels, such as ranking markets within a brand and then weighting scores within a market. order.nulls pins where NULLs sort, which otherwise differs between warehouses. See D-WINDOW.
  • Filtering on metrics. where_sql can now name a selected metric (revenue > 1000). It filters the result rows, like SQL HAVING. The new context_filter (CLI --context-filter, REST, MCP and Python) always filters the input rows. Use it to rank within a subset. See Filters.
  • How a metric is built, and what a query must group by. The metric catalog in the CLI, REST, MCP describe_metric and Python now has two new fields. derive_expr gives a composed metric's formula. required_dimensions lists the fields a query has to group by for the metric's windows to mean what they define.

Enhancements

  • A composed metric whose members use different time columns on the same table used to be refused with MetricTimeConflict. Now each time column gets its own branch, so every metric returns what it returns when queried alone. See D-TIME.
  • Oracle: a missing or incompatible Instant Client now fails at once with DPI-1047 and a link to the setup guide, instead of a 30-second pool timeout. The Oracle connector guide has been rewritten around installation and troubleshooting.
  • The DATUS extension version is now 1.9. Models that declare an older version load as before.

Bug fixes

  • A window's partition could change depending on the other metrics in the query. Adding a cumulative metric, for example, made another metric's daily rank span every day.
  • A dimension-table measure placed on two fact tables in one window query read the wrong fact's numbers on DuckDB and was rejected by StarRocks.
  • The dosi lineage view legend now draws each edge kind the same way the graph does.

Behavior changes

  • In a window query, a where_sql condition on group-by fields now filters the result instead of the rows being ranked. The rows you keep also keep the ranks and partition counts of the whole population. To get the 0.1.11 numbers, move the condition to context_filter.
  • A window whose partition.exclude names a field the query does not group by is now refused with window_exclude_not_grouped. Before, every row ranked first in a partition of one. Add the field to group_by; required_dimensions lists the fields a query needs.
  • A dimension count beside a fact measure, such as COUNT(DISTINCT stores.store_id) in repairs per store, now counts every store, not only the stores that have a repair. The ratio's value changes. To count only the entities seen in the fact, count the fact's key: COUNT(DISTINCT repairs.store_id). See Fan-out protection.

Known issues

  • A division with arithmetic on either side, such as (a + b) / c or a / NULLIF(b, 0), runs with the warehouse's own /. PostgreSQL, Trino and Redshift truncate integer division, and MySQL and TiDB keep four decimal places. Only a plain SUM(a) / SUM(b) is converted to floating point for you. Until that changes, write CAST(... AS DOUBLE) on the numerator.
  • 0.1.12 still reads only the older document format, the semantic_model: list. See the 0.1.11 note below.

0.1.11

New features

  • Recommended dimensions for each metric. dosi list dimensions --metric lists only the fields worth grouping that metric by, recommended ones first. The REST API, MCP tool, Python Engine.dimensions(metric=) and the lineage graph return the same list. Dosi leaves out keys, join columns and the columns a metric aggregates. A field can override that with is_dimension (D-DIM): false to hide an id or free-text column, true to include a numeric column. The list is only a recommendation: --group-by still accepts any reachable field, and the generated SQL is unchanged. dosi validate suggests where a declaration is missing or contradicts the model; --strict turns those suggestions into errors.
  • Ratios across two fact tables. Two fact tables that share a date or other column without one belonging to the other, such as logins and payments, can now be paired with cardinality: many_to_many (D-CONFORM). A metric like payers per active player then groups and filters by the shared column, and Dosi never joins the two tables directly.
  • dosi lineage. dosi lineage view opens the metric lineage graph in your browser, with derived metrics laid out left to right in the order they are built. dosi lineage dump prints the graph as JSON. See Lineage.
  • Ontologies (experimental). --ontology loads an Apache Ossie ontology, with or without a core model. You can query by its concepts, browse them with dosi describe and dosi paths, and see them on the lineage graph. Flags and output may still change.

Enhancements

  • Attribution now requires dimensions. Without them it stops before querying the warehouse and suggests the metric's recommended dimensions, ready to paste. See Which dimensions?.
  • The DATUS extension version is now 1.8. Models that declare an older version load as before.

Bug fixes

  • Arrow Flight reads from StarRocks failed every time. On StarRocks 4.0.14, also set arrow_flight_proxy_enabled = false. See Arrow-native results.

Known issue

  • Upstream Ossie changed its document format after this release: a document now holds one model, with name and datasets at the top level instead of a semantic_model: list. 0.1.11 cannot load the new format. A later release reads both. With --osi-basic, use the upstream validator at the commit named in dosi validate; upstream main rejects every model 0.1.11 accepts.

0.1.10

New features

  • Detail queries. dosi select returns rows instead of aggregated metrics — "the 100 largest orders from Enterprise customers". Name the fields you want; Dosi works out the joins, and refuses rather than hand you duplicated rows. See Detail queries.
  • Time columns stored as text or numbers. A time column no longer has to be a real DATE. Declare what it holds — 20240131, 2024-01, 2024年01月31日 — and Dosi groups and filters it correctly, without losing partition pruning. Queries are unchanged: you always write 2024-01-31. See D-FORMAT.
  • A composed metric can build on another composed metric. A formula is written once and reused, instead of restated in every metric that uses it — and a restatement that had drifted used to compile silently.

Enhancements

  • Models using the standard OSI datatype property now load, Apache Ossie's TPC-DS reference model included.
  • An --order key names a metric or a field you grouped by, never a column name the engine generated.

Bug fixes

  • A parameter whose values were Chinese, or any non-ASCII text, produced the same column name for every value; binding several at once failed the query.
  • Time columns stored as integers failed on MySQL, TiDB and ClickHouse when rolled up to month or year.

0.1.9

Dosi is now under the Elastic License 2.0: free to use and redistribute, with no hosted-service resale. It replaces the proprietary licence 0.1.5 introduced, which granted no rights at all without a separate agreement.

New features

  • BigQuery runs queries. It was compile-only — Dosi generated the SQL and refused to execute it. It now passes the full test corpus against a live project. See BigQuery.
  • Metric lineage (Python only) — Engine.lineage() returns the graph behind your metrics: table → dataset → metric → derived metric. Pass redact_sql=True if the graph leaves your trust boundary.
  • Model authoring contract (Python only) — render_datus_authoring_spec() returns the rules this engine enforces, with a digest, so a tool that generates models can check it is authoring against the same version.
  • Installing from a mirror. install.sh takes DOSI_SOURCE=github|oss|cloudflare. Use DOSI_SOURCE=oss from mainland China, where the GitHub download runs at tens of KB/s and often stalls. Archives are checksum-verified either way. The Cloudflare mirror is not populated in this release.

Enhancements

  • install.sh works on macOS, where it used to refuse any non-Linux host.
  • Validation returns what each metric actually compiled to, and Python connection settings can be passed in memory without the plaintext-secret warning — which only ever made sense for a file on disk.
  • A new BigQuery docs page, plus a 200k-row example database at https://dosi.datus.ai/orders-200k.duckdb.

Bug fixes

  • A rolling window of 1 period, or a preceding bound of 0, generated SQL that Doris and StarRocks reject outright, so an ordinary --param n=1,3,6 failed on those two engines.

0.1.8

No wheel was published for this version, so dosi-engine users go from 0.1.7 straight to 0.1.9, which includes everything here. The binaries shipped for Linux x86_64, Linux aarch64 and Apple Silicon only — on an Intel Mac, install 0.1.9.

New features

  • Attribution: why did this metric change? Give Dosi a metric, the dimensions to look at and two time windows; it runs the queries itself and returns a ranked breakdown of where the change came from, each segment with a ready-made filter for the next drill-down. The method comes from the metric's own algebra, and a metric it cannot decompose returns a structured "unsupported" with the reason rather than a wrong number. See Explain metric changes.
  • Parameterized metrics. A metric's window width, offset, rank bucket or filter value can be a query-time parameter: one moving_avg(n) replaces r7 / r30 / r90, and one query can ask for several values at once, a column each. See Parameterized metrics.

Enhancements

  • dosi list metrics says whether a metric can be attributed, and how, so you can check before running an analysis.
  • A query can carry several named time windows and get them all back from a single statement.

Behaviour change

  • If you call Engine.attribute() from the 0.1.7 wheel: the share fields can now be absent for a segment whose denominator is not positive, so code that reads them unconditionally needs to handle that.

0.1.7

This release is split: the binaries and the wheel were built three days apart, from different commits, so the wheel contains more.

New features

  • A second, smaller download — dosi-<version>-<target>-lean.tar.gz is the executables alone. It compiles queries and reaches every warehouse; local DuckDB execution uses a duckdb CLI on your PATH.
  • Derived metrics (D-DERIVE) M2 — windows over derived metrics, and composing over window members.
  • SQLite files are readable through DuckDB.
  • An error-code reference, with a catalog of what each rejection means.
  • Attribution, first version (wheel only) — Engine.attribute(). 0.1.8 extends it; read its behaviour change above before upgrading.

Enhancements

  • DuckDB starts about 200 ms faster per process — a one-shot dosi query --execute went from 249 ms to 41 ms of CPU. Query speed itself is unchanged; the cost was start-up.

Bug fixes

  • Redshift now renders as Redshift. It had been falling through to the generic generator and emitting Postgres SQL.
  • The wheel builds under cargo 1.93 and later.

0.1.6

Wheel only — dosi and dosi-server users go from 0.1.5 to 0.1.7.

New features

  • A Linux aarch64 wheel, so Graviton and arm64 containers no longer need a source build.
  • SQLite files are readable through DuckDB, which makes a local .sqlite file a usable connection for tests and small models.
  • CONNECTION_TYPES is exported, so a caller can list which warehouses a given wheel was built with.

0.1.5

The first public release of Dosi. From here the project ships as binaries and wheels only; no source archive is published. Install with curl -fsSL https://dosi.datus.ai/install.sh | sh for the dosi and dosi-server executables, or pip install dosi-engine for the Python bindings.

It loads an OSI semantic model, validates it, compiles a metric query to SQL for your warehouse, and runs it. Fifteen warehouses — DuckDB · StarRocks · ClickHouse · Doris · TiDB · Trino · Postgres · MySQL · Snowflake · BigQuery · Databricks · Redshift · Hologres · GaussDB · Oracle. Four ways in: the dosi CLI, REST, MCP, and the Python wheel. Two modes: --osi-datus honours the Datus extensions, --osi-basic is strict standard OSI.


Nothing before 0.1.5 was published.