cube-js/cube · error

Query must be defined in options

Error message

Query must be defined in options

What it means

A validation guard in ClickHouseDriver.unload: unloading a table in the ClickHouse driver is implemented by running a caller-supplied SELECT (`options.query.sql`) and inserting its results, since ClickHouse has no native server-side unload API. Throwing when options.query is missing means unloadWithoutTempTable strategy was requested without defining the source query; callers must pass a query in UnloadOptions.

Source

Thrown at packages/cubejs-clickhouse-driver/src/ClickHouseDriver.ts:608

    await this.command(query);
  }

  public override async createTable(quotedTableName: string, columns: TableColumn[]) {
    const createTableSql = this.createTableSql(quotedTableName, columns);
    try {
      await this.command(createTableSql);
    } catch (e) {
      // TODO replace string formatting with proper cause
      throw new Error(`Create table failed: ${e}`);
    }
  }

  /**
   * We use unloadWithoutTempTable strategy
   */
  public async unload(_tableName: string, options: UnloadOptions): Promise<DownloadTableCSVData> {
    if (!options.query?.sql) {
      throw new Error('Query must be defined in options');
    }

    return this.unloadFromQuery(
      options.query?.sql,
      options.query?.params,
      options
    );
  }

  public async unloadFromQuery(sql: string, params: unknown[], _options: UnloadOptions): Promise<DownloadTableCSVData> {
    if (!this.config.exportBucket) {
      throw new Error('Unload is not configured');
    }

    const types = await this.queryColumnTypes(`(${sql})`, params);
    const { bucketName, path } = this.parseBucketUrl(this.config.exportBucket.bucketName);
    const exportPrefix = path ? `${path}/${uuidv4()}` : uuidv4();

View on GitHub (pinned to 7d981676b3)

Solutions

  1. Ensure the caller passes options.query = { sql, params } when invoking unload()
  2. Verify the pre-aggregation unload strategy configuration so Cube's orchestrator populates UnloadOptions.query
  3. If calling manually, use unloadFromQuery(sql, params, options) instead when you have raw SQL
  4. Add a guard in calling code to fail fast with a clear message before invoking unload()

Example fix

// before
await driver.unload('my_preagg', {} as UnloadOptions);
// after
await driver.unload('my_preagg', { query: { sql: 'SELECT ...', params: [] } } as UnloadOptions);
Defensive patterns

Strategy: validation

Validate before calling

const canUnload = (options?: UnloadOptions): options is UnloadOptions & { query: { sql: string; params: unknown[] } } =>
  Boolean(options?.query?.sql);

Type guard

const hasUnloadQuery = (o: UnloadOptions): o is UnloadOptions & { query: { sql: string; params: unknown[] } } =>
  typeof o?.query?.sql === 'string' && o.query.sql.length > 0;

Try / catch

try { return await driver.unload(table, options); }
catch (e) {
  if (String(e.message) === 'Query must be defined in options') throw new Error('UnloadOptions.query.sql is required for unload', { cause: e });
  throw e;
}

Prevention

When it happens

Trigger: Calling driver.unload(tableName, options) with options lacking a query object, or with query defined but sql undefined/empty — typically from an orchestrator/pre-aggregation path that did not populate UnloadOptions.query.

Common situations: Custom code calling unload() directly with only a table name; drivers/configurations where the unload strategy (unloadWithoutTempTable) is set but the caller builds UnloadOptions without query; null propagation from upstream query builders.

Understand the failure class

Background: "Must pass :limit option" / "Missing required option" — required option errors explained — this error's family across 41 libraries.

Related errors


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