Skip to content

Tesseract replace_date_range_for_rolling_window_without_granularity ignores offset parameter, breaking offset: 'start' rollingWindow measures #11103

Description

@zhengyuzhao-1

Description

When Tesseract SQL Planner is enabled (CUBEJS_TESSERACT_SQL_PLANNER=true), the Rust function replace_date_range_for_rolling_window_without_granularity does not receive or handle the offset parameter from rolling window definitions. This causes all trailing: 'unbounded' rolling windows to be treated identically, generating {col} <= {end_date} regardless of offset value.

For offset: 'end' measures (e.g., ending balance), this is correct: billdate <= end_date.

For offset: 'start' measures (e.g., beginning balance), the generated SQL is wrong: it should be billdate < start_date, but Tesseract also produces billdate <= end_date.

Steps to Reproduce

  1. Create a cube with a measure using rollingWindow: { trailing: 'unbounded', offset: 'start' }:
cube(`test_cube`, {
  sql_table: `test_table`,
  dimensions: {
    billdate: { sql: `billdate`, type: `time` },
  },
  measures: {
    beg_balance: {
      sql: `amount`, type: `sum`,
      rollingWindow: { trailing: `unbounded`, offset: `start` },
    },
    end_balance: {
      sql: `amount`, type: `sum`,
      rollingWindow: { trailing: `unbounded`, offset: `end` },
    },
  },
});
  1. Enable Tesseract: CUBEJS_TESSERACT_SQL_PLANNER=true

  2. Query both measures with a date range:

{
  "measures": ["test_cube.beg_balance", "test_cube.end_balance"],
  "timeDimensions": [{
    "dimension": "test_cube.billdate",
    "dateRange": ["2026-01-01 00:00:00", "2026-06-17 23:59:59"]
  }]
}

Actual Behavior

Both measures generate the same WHERE clause:

WHERE ("test_cube"."billdate" <= $1::timestamptz)

with $1 = 2026-06-17T23:59:59.000Z (the end of the date range).

Expected Behavior

The offset: 'end' measure should generate:

WHERE ("test_cube"."billdate" <= $1::timestamptz)  -- end_date

The offset: 'start' measure should generate:

WHERE ("test_cube"."billdate" < $1::timestamptz)   -- start_date

with $1 = 2026-01-01T00:00:00.000Z (the start of the date range).

Root Cause

The JavaScript path (BaseQuery.rollingWindowDateJoinCondition in @cubejs-backend/schema-compiler) correctly handles the offset parameter:

offset === 'end'   → {col} <= {dateTo}
offset === 'start' → {col} <  {dateFrom}

However, the Rust counterpart replace_date_range_for_rolling_window_without_granularity in @cubejs-backend/native does not receive the offset argument. It only receives trailing/leading and the date range. For trailing: 'unbounded', it unconditionally generates BeforeOrOnDate(to), always using the end of the date range.

Note: replace_date_range_for_rolling_window_with_granularity does receive and handle offset correctly — the bug is specific to the non-granularity path.

Environment

  • Cube.js: 1.6.58
  • @cubejs-backend/native: 1.6.58
  • @cubejs-backend/schema-compiler: 1.6.58
  • OS: macOS
  • Database: PostgreSQL

Workaround

A runtime patch intercepts BaseQuery.prototype.buildSqlAndParamsRust to post-process Tesseract's SQL output, replacing {col} <= {end_date} with {col} < {start_date} for offset: 'start' measures. This is fragile and should not be necessary.

Impact

Any measure using rollingWindow: { trailing: 'unbounded', offset: 'start' } (beginning-of-period calculations) produces incorrect SQL when Tesseract is enabled. The JS path (without Tesseract) is not affected.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions