Skip to content

feat: approximate-by-default measures with exact_expression and an inline exact chip - #10032

Merged
nishantmonu51 merged 10 commits into
mainfrom
nishant/approx-exact-measures
Oct 23, 2026
Merged

nishantmonu51 merged 10 commits into
mainfrom
nishant/approx-exact-measures

Conversation

@nishantmonu51

Copy link
Copy Markdown
Collaborator

Measures can define an exact_expression next to their approximate expression, and dashboards let viewers switch a measure to its exact value on demand.

  • Runtime: exact_expression on measures (parsed, validated, in the reference docs); exact on MetricsViewAggregationMeasure and exact_measure_names on MetricsViewTimeSeriesRequest. The executor resolves the exact set once per query (closure over referenced measures) and passes it to every rewrite and AST; rollups are skipped for exact queries; rill.metrics.exact_measures is a kill switch. The AI query_metrics_view tool exposes the flag.
  • Explore: a ≈/= chip next to measures with an exact variant on big numbers, leaderboard and dimension table headers, the TDD header and table, and pivot column headers. A chip switches its own surface only (the big-number row, one leaderboard, the dimension table, the TDD table or the pivot). The selection is tab memory, never written to the URL, and resets when filters, time controls or the view change. One-shot exports follow what is on screen.
  • Canvas: the same chip per KPI and per table, pivot and leaderboard column, plus one chip next to a chart's title that switches every measure of the chart. The selection is per widget and resets when the canvas URL params change.
  • Docs: exact_expression in the measures reference, a guide page for the chip, and the canvas widget docs.
  • Tests: Go tests for parsing, AST, executor, rollups, validation and queries; vitest for the selection logic and the surfaces; a web-local e2e spec on a new AdBids_exact_metrics fixture.

Checklist:

  • Covered by tests
  • Ran it and it works as intended
  • Reviewed the diff before requesting a review
  • Checked for unhandled edge cases
  • Linked the issues it closes
  • Checked if the docs need to be updated. If so, create a separate Linear DOCS issue
  • Intend to cherry-pick into the release branch
  • I'm proud of this work!

…inline exact chip

Measures can define an `exact_expression` next to their approximate `expression`.
Queries flag measures with `exact`; the runtime resolves the exact set once per
query, skips rollups for it and honours the `rill.metrics.exact_measures` kill
switch. Explore and canvas dashboards show a `≈`/`=` chip next to such measures
and keep the selection in tab memory, per surface (one chip per chart in canvas).
@nishantmonu51
nishantmonu51 requested a review from pjain1 October 17, 2026 17:35
@AdityaHegde
AdityaHegde self-requested a review October 20, 2026 05:41

@AdityaHegde AdityaHegde left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High level review, just one comment about executor code.

return nil, err
}

exactMeasures, err := e.resolveExactMeasures(qry)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of passing around exact measures everywhere how about running a rewrite at once place? Not sure if there is a good path for this.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: rewriteQueryExactMeasures resolves the set once at the start of Query and Export; nothing is passed around any more (details in the exact.go threads).

- match the Connect `MetricsViewAggregation` endpoint instead of the REST path
- give the `publishers` measure a deterministic default expression
- rename the fixture to `AdBids_precision_metrics` so that it sorts after `AdBids_metrics` and does not become the canvas editor's default metrics view
…measures

# Conflicts:
#	proto/gen/rill/runtime/v1/resources.pb.go
…measures

# Conflicts:
#	proto/gen/rill/runtime/v1/resources.pb.go
#	runtime/server/downloads.go
… passing the set around

- `rewriteQueryExactMeasures` runs after validation in `Query` and `Export`: it resolves the exact set, writes it back into the query's `exact` flags (filter subqueries included) and applies the kill switch
- `NewAST` resolves the set from the query it receives; derived queries (percent-of-total totals, two-phase base query, Druid TopN inner query, where-subqueries) copy the flags with the measures
- removes `NewASTWithExactMeasures`, `resolveExactMeasures`, the private `query` and the extra parameter on the four rewrites
- the set is closed under the derived-measure rule the frontend already uses: a measure calculated from an exact measure makes its other exact-capable inputs exact
- tests for the fixpoint, `FlagExactMeasures` and a two-phase base query sharing a filter subquery
Comment thread runtime/metricsview/exact.go Resolved
Comment thread runtime/metricsview/exact.go Resolved
…e-resolving them per AST

- `Query.ExactMeasures` (not part of the wire format) holds the set resolved once by `rewriteQueryExactMeasures`
- derived queries (percent-of-total totals, two-phase base query, Druid TopN inner query, where-subqueries) copy it like their other context fields; the rollup check reads it
- `NewAST` reads it and resolves from the flags only for queries that did not pass through the executor (schema probe, search)
- removes `FlagExactMeasures`; the percent-of-total base measure is built as on main again

@AdityaHegde AdityaHegde left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TDD's totals rows seem to be dependant on chart total's toggle rather than the toggle in TDD table.

@AdityaHegde AdityaHegde left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few nits remaining, approving

@pjain1

pjain1 commented Oct 22, 2026

Copy link
Copy Markdown
Member

Do we need to allow exact behaviour for derived and ad hoc measures? Not supporting it will remove the parsing of sql which seems unnecessary for an optional feature or if we need to support exact on individual measures rather than at query level enabling for all measures.

typ = runtimev1.MetricsViewSpec_MEASURE_TYPE_TIME_COMPARISON
default:
return fmt.Errorf(`invalid measure type %q (allowed values: simple, derived, time_comparison)`, measure.Type)
}

@pjain1 pjain1 Oct 22, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

disallow it on non simple measures? add a check here after switch like this

if measure.ExactExpression != "" && (typ != runtimev1.MetricsViewSpec_MEASURE_TYPE_SIMPLE || window != nil) {
         return fmt.Errorf(`measure %q: "exact_expression" can only be set on simple measures`, measure.Name)
 }

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, with windowed measures excluded as well.

- `exact_expression` is only allowed on simple measures without a window (parser error otherwise)
- the exact set is the flagged measures that define an exact_expression; no closure over referenced measures, no fixpoint, no parsing of ad hoc expressions. A derived measure is calculated from the measures as the query computes them
- UI: derived and ad hoc measures show no chip; the dashed "inherited" chip state and its message are removed
- the search paths run `rewriteQueryExactMeasures`
- the Time Dimension Detail page is one surface: its chart, totals and table follow the chip in its header
- the canvas chart builds its exact request set from its selection alone, without reading the metrics view spec or converting ad hoc measures
- docs, schema description and the proto comment on `MetricsViewAggregationMeasure.exact` updated, bindings and reference docs regenerated
@nishantmonu51

Copy link
Copy Markdown
Collaborator Author

@pjain1 agreed and done in a83302a: exact_expression is only allowed on simple measures (parser check, windows excluded too), the runtime no longer follows referenced measures or parses ad hoc expressions, and the UI drops the dashed state: derived and ad hoc measures show no chip and are calculated from the measures as the query computes them.

@AdityaHegde the TDD totals row is fixed in the same push: the Time Dimension Detail page is one surface now, so its chart, totals row and table all follow the chip in the TDD header.

@nishantmonu51
nishantmonu51 merged commit 9c1fdd2 into main Oct 23, 2026
17 checks passed
@nishantmonu51
nishantmonu51 deleted the nishant/approx-exact-measures branch October 23, 2026 17:04
nishantmonu51 added a commit that referenced this pull request Oct 29, 2026
…inline exact chip (#10032)

* feat: approximate-by-default measures with `exact_expression` and an inline exact chip

Measures can define an `exact_expression` next to their approximate `expression`.
Queries flag measures with `exact`; the runtime resolves the exact set once per
query, skips rollups for it and honours the `rill.metrics.exact_measures` kill
switch. Explore and canvas dashboards show a `≈`/`=` chip next to such measures
and keep the selection in tab memory, per surface (one chip per chart in canvas).

* test: fix the exact measures e2e spec and its fixture

- match the Connect `MetricsViewAggregation` endpoint instead of the REST path
- give the `publishers` measure a deterministic default expression
- rename the fixture to `AdBids_precision_metrics` so that it sorts after `AdBids_metrics` and does not become the canvas editor's default metrics view

* refactor: resolve exact measures once with a query rewrite instead of passing the set around

- `rewriteQueryExactMeasures` runs after validation in `Query` and `Export`: it resolves the exact set, writes it back into the query's `exact` flags (filter subqueries included) and applies the kill switch
- `NewAST` resolves the set from the query it receives; derived queries (percent-of-total totals, two-phase base query, Druid TopN inner query, where-subqueries) copy the flags with the measures
- removes `NewASTWithExactMeasures`, `resolveExactMeasures`, the private `query` and the extra parameter on the four rewrites
- the set is closed under the derived-measure rule the frontend already uses: a measure calculated from an exact measure makes its other exact-capable inputs exact
- tests for the fixpoint, `FlagExactMeasures` and a two-phase base query sharing a filter subquery

* refactor: store the resolved exact measures on the query instead of re-resolving them per AST

- `Query.ExactMeasures` (not part of the wire format) holds the set resolved once by `rewriteQueryExactMeasures`
- derived queries (percent-of-total totals, two-phase base query, Druid TopN inner query, where-subqueries) copy it like their other context fields; the rollup check reads it
- `NewAST` reads it and resolves from the flags only for queries that did not pass through the executor (schema probe, search)
- removes `FlagExactMeasures`; the percent-of-total base measure is built as on main again

* refactor: drop exact behavior for derived and ad hoc measures

- `exact_expression` is only allowed on simple measures without a window (parser error otherwise)
- the exact set is the flagged measures that define an exact_expression; no closure over referenced measures, no fixpoint, no parsing of ad hoc expressions. A derived measure is calculated from the measures as the query computes them
- UI: derived and ad hoc measures show no chip; the dashed "inherited" chip state and its message are removed
- the search paths run `rewriteQueryExactMeasures`
- the Time Dimension Detail page is one surface: its chart, totals and table follow the chip in its header
- the canvas chart builds its exact request set from its selection alone, without reading the metrics view spec or converting ad hoc measures
- docs, schema description and the proto comment on `MetricsViewAggregationMeasure.exact` updated, bindings and reference docs regenerated

* deprecate exact field on query level

* perf: skip serializing the query context when no exact measure is selected

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Parag Jain <pjain1@apache.org>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 9c1fdd2)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants