Skip to content

/api/v1/analytics/query still drops per-measure and dataset-level filter on the ObjectQL path — engine.aggregate receives no filter at all #10413

Description

@os-warren

Found while implementing #10298 (PR #10411), which repairs this on NativeSQLStrategy. Filed rather than widened into that PR: the remedy here is a different one and touches a contract that card does not own.

What is measured

#10298 was reported and reproduced against the native-SQL path — the returned sql field is what made it visible. AnalyticsService has a second strategy, ObjectQLStrategy, which serves any deployment whose driver reports objectqlAggregate but not nativeSql (MongoDB, the memory driver, anything routed through engine.aggregate rather than raw SQL). That path drops the same declarations, and no sql echo makes it visible.

Measured on origin/main at 801296050, with a dataset carrying both a definition-level filter and a per-measure filter, and a service whose capabilities are { nativeSql: false, objectqlAggregate: true } so NativeSQLStrategy declines:

DatasetSchema.parse({
  name: 'opportunity_metrics', object: 'crm_opportunity',
  filter: { is_deleted: false },
  measures: [
    { name: 'opp_count',  label: 'Opportunities', aggregate: 'count' },
    { name: 'won_count',  label: 'Won Deals',     aggregate: 'count', filter: { stage: 'closed_won' } },
    { name: 'won_amount', label: 'Won Revenue',   aggregate: 'sum', field: 'amount',
      filter: { stage: 'closed_won' } },
  ],
});

svc.query({ cube: 'opportunity_metrics', measures: ['opp_count','won_count','won_amount'] }) reaches engine.aggregate as:

{
  "obj": "crm_opportunity",
  "options": {
    "aggregations": [
      { "field": "*",      "method": "count", "alias": "opp_count"  },
      { "field": "*",      "method": "count", "alias": "won_count"  },
      { "field": "amount", "method": "sum",   "alias": "won_amount" }
    ]
  }
}

No filter key at all — neither the dataset's is_deleted: false scope nor either measure's stage: 'closed_won'. won_count is a plain row count and won_amount sums every row, exactly the arithmetic #10298 reported from the SQL door. The dashboard door on the same deployment still answers the filtered numbers, because DatasetExecutor fans out per filtered measure and applies them itself — so the two-door disagreement #10298 describes survives on this path with #10411 merged.

Why it was not fixed in #10411

The native-SQL repair emits one conditional aggregate per filtered measure (COUNT(CASE WHEN … THEN … END)) inside a single statement. engine.aggregate's contract has no equivalent: an aggregation is { field, method, alias } and the only filter is one predicate for the whole call, so per-measure scoping cannot be expressed there at all.

That leaves two shapes, and choosing between them is a decision rather than a mechanical fix:

  • Widen the aggregate contract with a per-aggregation filter (@objectstack/objectql + the executeAggregate bridge on StrategyContext, which lives in packages/spec), then lower each measure filter into it. Correct once, for every driver — and a public-surface change.
  • Fan out inside ObjectQLStrategy, one engine.aggregate call per filtered measure merged by dimension key — which is what DatasetExecutor.runMeasurePass already does one layer up. No contract change, N extra reads per query, and a second implementation of a rule that already exists.

The dataset-level filter is the easier half and may not need the same decision: it is one predicate for the whole call, which engine.aggregate already accepts, so it could be merged into the strategy's existing filter object with no contract movement.

Reproduction

The probe above, run against packages/services/service-analytics/src/analytics-service.ts with a stub executeAggregate that records its arguments. The behaviour is in ObjectQLStrategy.execute, which builds aggregations from resolveMeasureAggregation(cube, measure) and its filter from normalizeAnalyticsFilterTree(query) — the caller's where and the time windows only; nothing consults the dataset registry.

Related: #10298 · PR #10411.

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions