cube-js/cube · error · Error

QuestDB custom granularity has an unparseable origin: ${orig

Error message

QuestDB custom granularity has an unparseable origin: ${origin}

What it means

For custom granularities, QuestQuery.dateBinOriginShift parses the user-supplied origin timestamp with moment.utc to compute an offset from a fixed anchor. If the origin string cannot be parsed into a valid date, the driver throws instead of producing broken SQL.

Source

Thrown at packages/cubejs-questdb-driver/src/QuestQuery.ts:138

  public dateBin(interval: string, source: string, origin: string): string {
    const { stride, unit, count } = this.questFloorStride(interval);
    // timestamp_floor(stride, ts, origin) only buckets forward from `origin`, so
    // an origin later than the data collapses every row into a single bucket.
    // Shift `origin` back by a whole number of strides (which preserves the bin
    // phase, as flooring is periodic modulo the stride) to just before a fixed
    // anchor that precedes any realistic data.
    const shift = this.dateBinOriginShift(origin, unit, count);
    const shiftedOrigin = shift > 0
      ? `dateadd('${unit}', ${-shift}, cast('${origin}' as timestamp))`
      : `cast('${origin}' as timestamp)`;

    return `timestamp_floor('${stride}', ${source}, ${shiftedOrigin})`;
  }

  private dateBinOriginShift(origin: string, unit: string, count: number): number {
    const parsedOrigin = moment.utc(origin);
    if (!parsedOrigin.isValid()) {
      throw new Error(`QuestDB custom granularity has an unparseable origin: ${origin}`);
    }

    const anchor = moment.utc(DATE_BIN_ORIGIN_ANCHOR);
    const strides = Math.ceil(parsedOrigin.diff(anchor, QUEST_UNIT_TO_MOMENT[unit]) / count);

    const shift = strides > 0 ? strides * count : 0;
    if (shift > INT32_MAX) {
      throw new Error(
        `QuestDB cannot anchor custom granularity '${count} ${unit}': origin shift ${shift} exceeds dateadd()'s 32-bit range`
      );
    }

    return shift;
  }

  private questFloorStride(interval: string): { stride: string, unit: string, count: number } {
    const [duration, type] = this.parseInterval(interval);

View on GitHub (pinned to 7d981676b3)

Solutions

  1. Provide the origin as an ISO 8601 UTC string, e.g. '2024-01-01T00:00:00Z'
  2. Validate the origin value in your schema/config before compiling queries
  3. Remove the custom origin to fall back to the default anchor

Example fix

// before
origin: 'January 1st, 2024'
// after
origin: '2024-01-01T00:00:00Z'
Defensive patterns

Strategy: validation

Validate before calling

if (!moment.utc(origin, moment.ISO_8601, true).isValid()) throw new Error(`origin ${origin} must be a valid ISO 8601 UTC date`);

Type guard

function isValidIsoOrigin(s) { return typeof s === 'string' && moment.utc(s, moment.ISO_8601, true).isValid(); }

Try / catch

try { await cube.query(...); } catch (e) { if (e.message.includes('unparseable origin')) { /* correct the origin in the data model and retry */ } throw e; }

Prevention

When it happens

Trigger: Calling shift/dateBin with a custom-granularity origin that is not a valid UTC date string (empty string, wrong format, localized date text).

Common situations: A data model defining a custom granularity with an origin written in a non-ISO format (e.g. '01/02/2024' ambiguity aside, or 'Jan 2nd 2024' with locale issues); origin left undefined and stringified.

Related errors


AI-assisted analysis of cube-js/cube@7d981676b3 (2026-09-02). Data as JSON: /api/errors/bbe5694a581400e3. Report an issue: GitHub.