cube-js/cube · error

Unable to describe table

Error message

Unable to describe table

What it means

queryColumnTypes() runs `DESCRIBE (<sql>)` against ClickHouse to infer column names/types, and throws when the query returns no columns (falsy result). It is called by the types()/downloadQueryResults metadata path, so a table-less or non-selectable query breaks type inference.

Source

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

  public async isUnloadSupported() {
    return !!this.config.exportBucket;
  }

  /**
   * Returns an array of queried fields meta info.
   */
  public async queryColumnTypes(sql: string, params: unknown[]): Promise<TableStructure> {
    // For DESCRIBE we expect that each row would have special structure
    // See https://clickhouse.com/docs/en/sql-reference/statements/describe-table
    // TODO complete this type
    type DescribeRow = {
      name: string,
      type: string
    };
    const columns = await this.query<DescribeRow>(`DESCRIBE ${sql}`, params);
    if (!columns) {
      throw new Error('Unable to describe table');
    }

    return columns.map((column) => ({
      name: column.name,
      type: this.toGenericType(column.type),
    }));
  }

  // This is only for use in tests
  public override async createTableRaw(query: string): Promise<void> {
    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) {

View on GitHub (pinned to 7d981676b3)

Solutions

  1. Grant the ClickHouse user DESCRIBE/metadata privileges on the underlying tables and database
  2. Run `DESCRIBE (<sql>)` manually in clickhouse-client to see why it returns nothing
  3. Verify the sql/params passed to types() reference existing tables/views
  4. Upgrade driver/ClickHouse — older versions could return empty metadata for certain engines
  5. If using a custom/mock driver implementation, ensure query() resolves with the rows array rather than undefined

Example fix

// before (test mock)
query: async () => undefined
// after
query: async () => [{ name: 'id', type: 'UInt64' }]
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the user can describe the target table before calling types()
await clickhouseClient.exec({ query: `DESCRIBE TABLE my_db.my_table` });

Type guard

const isDescribable = (cols: unknown): cols is Array<{ name: string; type: string }> =>
  Array.isArray(cols) && cols.length > 0 && cols.every(c => typeof c?.name === 'string' && typeof c?.type === 'string');

Try / catch

try { return await driver.downloadQueryResults(q, v); }
catch (e) {
  if (String(e.message) === 'Unable to describe table') throw new Error('Check DESCRIBE privileges and that the query targets existing tables', { cause: e });
  throw e;
}

Prevention

When it happens

Trigger: Calling types() (e.g. during pre-aggregation load checks) with a query whose DESCRIBE yields no rows/columns — e.g. an empty result set from DESCRIBE, permission-restricted metadata, or a subquery ClickHouse cannot describe.

Common situations: Querying a view or table the ClickHouse user lacks DESCRIBE privileges on; queries against empty/temporary tables in some ClickHouse versions returning no metadata; malformed sql wrapped by the caller; mocked drivers in tests returning undefined from query().

Related errors


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