drizzle-team/drizzle-orm · error · TransactionRollbackError

Rollback

Error message

Rollback

What it means

TransactionRollbackError (message 'Rollback') thrown by SingleStoreTransaction.rollback() at session.ts:259. This is the documented mechanism to abort a transaction: calling tx.rollback() inside the transaction callback unwinds the transaction so the driver issues a ROLLBACK. It is intentional control flow, not an unexpected fault.

Source

Thrown at drizzle-orm/src/singlestore-core/session.ts:259

export abstract class SingleStoreTransaction<
	TQueryResult extends SingleStoreQueryResultHKT,
	TPreparedQueryHKT extends PreparedQueryHKTBase,
	TFullSchema extends Record<string, unknown> = Record<string, never>,
	TSchema extends TablesRelationalConfig = Record<string, never>,
> extends SingleStoreDatabase<TQueryResult, TPreparedQueryHKT, TFullSchema, TSchema> {
	static override readonly [entityKind]: string = 'SingleStoreTransaction';

	constructor(
		dialect: SingleStoreDialect,
		session: SingleStoreSession,
		protected schema: RelationalSchemaConfig<TSchema> | undefined,
		protected readonly nestedIndex: number,
	) {
		super(dialect, session, schema);
	}

	rollback(): never {
		throw new TransactionRollbackError();
	}

	/** Nested transactions (aka savepoints) only work with InnoDB engine. */
	abstract override transaction<T>(
		transaction: (tx: SingleStoreTransaction<TQueryResult, TPreparedQueryHKT, TFullSchema, TSchema>) => Promise<T>,
	): Promise<T>;
}

export interface PreparedQueryHKTBase extends SingleStorePreparedQueryHKT {
	type: SingleStorePreparedQuery<Assume<this['config'], SingleStorePreparedQueryConfig>>;
}

View on GitHub (pinned to b7862528fd)

Solutions

  1. Catch TransactionRollbackError around the db.transaction call to treat it as an expected abort rather than a crash.
  2. Ensure business-logic aborts call tx.rollback() and nothing else throws raw errors for the same purpose.
  3. Do not swallow the error silently; log/handle it according to your rollback semantics.

Example fix

// before
await db.transaction(async (tx) => {
  await tx.insert(users).values(payload);
  if (!valid) tx.rollback();
});
// after
try {
  await db.transaction(async (tx) => {
    await tx.insert(users).values(payload);
    if (!valid) tx.rollback();
  });
} catch (e) {
  if (e instanceof TransactionRollbackError) { /* expected abort */ }
  else throw e;
}
Defensive patterns

Strategy: try-catch

Type guard

import { TransactionRollbackError } from 'drizzle-orm/errors';
function isRollback(e): e is TransactionRollbackError { return e instanceof TransactionRollbackError; }

Try / catch

import { TransactionRollbackError } from 'drizzle-orm/errors';
try {
  await db.transaction(async (tx) => { if (!ok) tx.rollback(); });
} catch (e) {
  if (e instanceof TransactionRollbackError) { /* expected */ return; }
  throw e;
}

Prevention

When it happens

Trigger: Inside db.transaction(async (tx) => { ... tx.rollback(); }), typically after a domain rule violation or business-logic check that should void the unit of work; also thrown by nested savepoint rollback.

Common situations: Multi-step writes where a later validation fails and earlier writes must be undone; intentional abort on conflicting concurrent state; tests that verify rollback behavior.

Related errors


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