drizzle-team/drizzle-orm · error · Error

values() must be called with at least one value

Error message

values() must be called with at least one value

What it means

Thrown by SQLiteInsertBuilder.values() (insert.ts:61) when the values argument — after normalisation to an array — has length zero. SQLite cannot insert nothing, and drizzle treats an empty values call as a programming error rather than emit a no-op statement.

Source

Thrown at drizzle-orm/src/sqlite-core/query-builders/insert.ts:61

	TRunResult,
> {
	static readonly [entityKind]: string = 'SQLiteInsertBuilder';

	constructor(
		protected table: TTable,
		protected session: SQLiteSession<any, any, any, any>,
		protected dialect: SQLiteDialect,
		private withList?: Subquery[],
	) {}

	values(value: SQLiteInsertValue<TTable>): SQLiteInsertBase<TTable, TResultType, TRunResult>;
	values(values: SQLiteInsertValue<TTable>[]): SQLiteInsertBase<TTable, TResultType, TRunResult>;
	values(
		values: SQLiteInsertValue<TTable> | SQLiteInsertValue<TTable>[],
	): SQLiteInsertBase<TTable, TResultType, TRunResult> {
		values = Array.isArray(values) ? values : [values];
		if (values.length === 0) {
			throw new Error('values() must be called with at least one value');
		}
		const mappedValues = values.map((entry) => {
			const result: Record<string, Param | SQL> = {};
			const cols = this.table[Table.Symbol.Columns];
			for (const colKey of Object.keys(entry)) {
				const colValue = entry[colKey as keyof typeof entry];
				result[colKey] = is(colValue, SQL) ? colValue : new Param(colValue, cols[colKey]);
			}
			return result;
		});

		// if (mappedValues.length > 1 && mappedValues.some((t) => Object.keys(t).length === 0)) {
		// 	throw new Error(
		// 		`One of the values you want to insert is empty. In SQLite you can insert only one empty object per statement. For this case Drizzle with use "INSERT INTO ... DEFAULT VALUES" syntax`,
		// 	);
		// }

		return new SQLiteInsertBase(this.table, mappedValues, this.session, this.dialect, this.withList);

View on GitHub (pinned to b7862528fd)

Solutions

  1. Guard the call: if (rows.length) await db.insert(t).values(rows);
  2. Short-circuit empty batches at the service layer and return early.
  3. Log when a batch is unexpectedly empty to surface upstream bugs.

Example fix

// before
await db.insert(users).values(batch); // throws if batch === []

// after
if (batch.length > 0) {
  await db.insert(users).values(batch);
}
Defensive patterns

Strategy: validation

Validate before calling

async function safeInsert(db, table, rows) {
  if (!rows.length) return { rowsInserted: 0 };
  return db.insert(table).values(rows);
}

Type guard

const isNonEmpty = <T>(a: T[]): a is [T, ...T[]] => a.length > 0;

Prevention

When it happens

Trigger: Calling db.insert(t).values([]) directly; passing an array variable that was filtered down to zero items; spreading a batch array that resolved to []. Passing a single object never triggers it; only an empty array does.

Common situations: Batch insert from a list that the caller did not pre-size; an empty CSV import; a 'sync' routine that has nothing to insert this run; debounced bulk insert with no incoming rows.

Related errors


AI-assisted analysis of drizzle-team/drizzle-orm@b7862528fd (2026-08-03). Data as JSON: /data/errors/0441743b2713a7e9.json. Report an issue: GitHub.