cube-js/cube · error · Error
${this.constructor} driver supports only rows upload
Error message
${this.constructor} driver supports only rows upload What it means
uploadTableWithIndexes requires the source data to be row-form (tableData.rows). If tableData contains only columnar data (columns) — typically because the source driver returned results in column format — the QuestDB driver cannot convert it and throws. QuestDB ingestion in this driver is implemented only for row arrays.
Source
Thrown at packages/cubejs-questdb-driver/src/QuestDriver.ts:232
// eslint-disable-next-line camelcase
public async getTablesQuery(_schemaName: string): Promise<({ table_name?: string, TABLE_NAME?: string })[]> {
return this.query('SHOW TABLES', []);
}
public async tableColumnTypes(table: string): Promise<TableStructure> {
const response: any[] = await this.query(`SHOW COLUMNS FROM ${escapeStringLiteral(table)}`, []);
return response.map((row) => ({ name: row.column, type: this.toGenericType(row.type) }));
}
public async uploadTableWithIndexes(
table: string,
columns: TableStructure,
tableData: DownloadTableMemoryData,
indexesSql: IndexesSQL
) {
if (!tableData.rows) {
throw new Error(`${this.constructor} driver supports only rows upload`);
}
await this.createTable(table, columns);
try {
for (let i = 0; i < tableData.rows.length; i++) {
await this.query(
`INSERT INTO ${escapeStringLiteral(table)}
(${columns.map(c => this.quoteIdentifier(c.name)).join(', ')})
VALUES (${columns.map((c, paramIndex) => this.param(paramIndex)).join(', ')})`,
columns.map(c => this.toColumnValue(tableData.rows[i][c.name] as string, c.type))
);
}
// Make sure to commit the data to make it visible for later queries.
await this.query('COMMIT', []);
for (let i = 0; i < indexesSql.length; i++) {View on GitHub (pinned to 7d981676b3)
Solutions
- Ensure the source download uses downloadQueryResults with row format so tableData.rows is populated
- Convert columnar data to rows before calling: rows = columnNames.map((_, i) => Object.fromEntries(names.map(n => [n, cols[n][i]])))
- Upload without the column-only data path, or use a driver version that converts columns to rows automatically
Example fix
// before
await questDriver.uploadTableWithIndexes(table, structure, { columns: colData }, indexes);
// after
const rows = colData.ids.map((_, i) => ({ id: colData.ids[i], ts: colData.ts[i] }));
await questDriver.uploadTableWithIndexes(table, structure, { rows }, indexes); Defensive patterns
Strategy: validation
Validate before calling
if (!tableData.rows || tableData.rows.length === 0) throw new Error('QuestDB upload requires row-form tableData.rows'); Type guard
function hasRows(d) { return Array.isArray(d?.rows) && d.rows.length > 0; } Try / catch
try { await driver.uploadTableWithIndexes(table, columns, tableData, indexes); } catch (e) { if (e.message.includes('supports only rows upload')) { return uploadConvertedToRows(table, columns, tableData, indexes); } throw e; } Prevention
- Always source uploads from drivers/configurations that return row data
- Convert columnar results to rows before upload
- Write a unit test around uploadTable with both data shapes
When it happens
Trigger: Calling uploadTableWithIndexes (via loadTable / export queries / pre-aggregation upload) with a DownloadTableMemoryData built from a driver that sets `columns` but not `rows`.
Common situations: Cross-database pre-aggregation exports where the source driver downloads results column-wise; combining a column-oriented source driver with QuestDB as target without a rows conversion step.
Related errors
- Unable to detect type for field "${f.name}" with dataTypeID:
- Driver's .streamQuery() method is not implemented yet.
- ${this.constructor} driver supports only rows upload
- CUBEJS_DB_NAME can`t be empty.
- Please specify CUBEJS_DB_URL
AI-assisted analysis of cube-js/cube@7d981676b3 (2026-09-02).
Data as JSON: /api/errors/b8c3e7608460ca54.
Report an issue: GitHub.