Skip to main content
doc0blade47/drizzle-ormdocumentation home
Sign in

Knex Kysely and Prisma Interop

Last updated Oct 12, 2026100% grounded
Relevant source files

The following files were used as context for generating this wiki page:

Overview

The Prisma interop layer bridges Drizzle's query builder and schema definitions with Prisma Client across PostgreSQL, MySQL, and SQLite dialects. By defining Prisma extensions with $drizzle, Drizzle maps SQL operations to Prisma's underlying raw execution methods such as $queryRawUnsafe.

Sources: drizzle-orm/src/prisma/sqlite/driver.ts:1-32, drizzle-orm/src/prisma/pg/session.ts:16-40, drizzle-orm/src/prisma/mysql/driver.ts:1-42

Prisma PostgreSQL Driver and Session Integration

Driver Initialization and Prisma Extension Setup

The PostgreSQL interop layer is initialized through the drizzle() function, which consumes a PrismaPgConfig configuration object excluding the schema definition. If config.logger is set to true, a DefaultLogger is instantiated; otherwise, custom logger instances or false are assigned. The function returns a Prisma extension defined via Prisma.defineExtension, attaching a PrismaPgDatabase instance to the client under the $drizzle property.

export type PrismaPgConfig = Omit<DrizzleConfig, 'schema'>;
 
export function drizzle(config: PrismaPgConfig = {}) {
	let logger: Logger | undefined;
	if (config.logger === true) {
		logger = new DefaultLogger();
	} else if (config.logger !== false) {
		logger = config.logger;
	}
 
	return Prisma.defineExtension((client) => {
		return client.$extends({
			name: 'drizzle',
			client: {
				$drizzle: new PrismaPgDatabase(client, logger),
			},
		});
	});
}

Sources: drizzle-orm/src/prisma/pg/driver.ts:22-40

PrismaPgDatabase extends PgDatabase with PrismaPgQueryResultHKT and an empty record type, constructing a PgDialect and a PrismaPgSession initialized with the Prisma client and session options containing the logger.

export class PrismaPgDatabase extends PgDatabase<PrismaPgQueryResultHKT, Record<string, never>> {
	static override readonly [entityKind]: string = 'PrismaPgDatabase';
 
	constructor(client: PrismaClient, logger: Logger | undefined) {
		const dialect = new PgDialect();
		super(dialect, new PrismaPgSession(dialect, client, { logger }), undefined);
	}
}

Sources: drizzle-orm/src/prisma/pg/driver.ts:13-20

Session Management and Prepared Query Execution

PrismaPgSession extends PgSession, configuring its logger to either the provided session option or a NoopLogger fallback. Executing a raw SQL query delegates to prepareQuery using this.dialect.sqlToQuery(query), subsequently calling .execute().

export class PrismaPgSession extends PgSession {
	static override readonly [entityKind]: string = 'PrismaPgSession';
 
	private readonly logger: Logger;
 
	constructor(
		dialect: PgDialect,
		private readonly prisma: PrismaClient,
		private readonly options: PrismaPgSessionOptions,
	) {
		super(dialect);
		this.logger = options.logger ?? new NoopLogger();
	}
 
	override execute<T>(query: SQL): Promise<T> {
		return this.prepareQuery<PreparedQueryConfig & { execute: T }>(this.dialect.sqlToQuery(query)).execute();
	}
 
	override prepareQuery<T extends PreparedQueryConfig = PreparedQueryConfig>(query: Query): PgPreparedQuery<T> {
		return new PrismaPgPreparedQuery(this.prisma, query, this.logger);
	}

Sources: drizzle-orm/src/prisma/pg/session.ts:46-66

Warning

Both PrismaPgSession.transaction and PrismaPgPreparedQuery.all throw explicit Method not implemented. errors when invoked, meaning transactions and array-mode responses are unsupported under the Prisma PostgreSQL interop session.

override all(): Promise<unknown> {
	throw new Error('Method not implemented.');
}

Sources: drizzle-orm/src/prisma/pg/session.ts:33-35, drizzle-orm/src/prisma/pg/session.ts:68-73

PrismaPgPreparedQuery extends PgPreparedQuery to execute statements against the underlying PrismaClient. When execute() is called, placeholders are filled via fillPlaceholders(), the query and parameters are logged, and execution proceeds through prisma.$queryRawUnsafe().

export class PrismaPgPreparedQuery<T> extends PgPreparedQuery<PreparedQueryConfig & { execute: T }> {
	static override readonly [entityKind]: string = 'PrismaPgPreparedQuery';
 
	constructor(
		private readonly prisma: PrismaClient,
		query: Query,
		private readonly logger: Logger,
	) {
		super(query, undefined, undefined, undefined);
	}
 
	override execute(placeholderValues?: Record<string, unknown>): Promise<T> {
		const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
		this.logger.logQuery(this.query.sql, params);
		return this.prisma.$queryRawUnsafe(this.query.sql, ...params);
	}

Sources: drizzle-orm/src/prisma/pg/session.ts:16-31

Prisma MySQL Session and Query Handling

Driver Initialization and Prisma Extension Setup

The MySQL interop module exposes PrismaMySqlDatabase, which extends MySqlDatabase with PrismaMySqlQueryResultHKT, PrismaMySqlPreparedQueryHKT, and an empty record type. Its constructor instantiates a MySqlDialect and a PrismaMySqlSession equipped with the provided PrismaClient and logger options.

export class PrismaMySqlDatabase
	extends MySqlDatabase<PrismaMySqlQueryResultHKT, PrismaMySqlPreparedQueryHKT, Record<string, never>>
{
	static override readonly [entityKind]: string = 'PrismaMySqlDatabase';
 
	constructor(client: PrismaClient, logger: Logger | undefined) {
		const dialect = new MySqlDialect();
		super(dialect, new PrismaMySqlSession(dialect, client, { logger }), undefined, 'default');
	}
}

Sources: drizzle-orm/src/prisma/mysql/driver.ts:13-22

The drizzle() entry point accepts a PrismaMySqlConfig type, omitting the schema property from DrizzleConfig. If config.logger is set to true, a DefaultLogger is created; otherwise, custom loggers or false are preserved. The function returns a Prisma extension via Prisma.defineExtension, appending the $drizzle database client instance to the extended Prisma client.

export type PrismaMySqlConfig = Omit<DrizzleConfig, 'schema'>;
 
export function drizzle(config: PrismaMySqlConfig = {}) {
	let logger: Logger | undefined;
	if (config.logger === true) {
		logger = new DefaultLogger();
	} else if (config.logger !== false) {
		logger = config.logger;
	}
 
	return Prisma.defineExtension((client) => {
		return client.$extends({
			name: 'drizzle',
			client: {
				$drizzle: new PrismaMySqlDatabase(client, logger),
			},
		});
	});
}

Sources: drizzle-orm/src/prisma/mysql/driver.ts:24-42

Session Management and Prepared Query Execution

PrismaMySqlSession extends MySqlSession, resolving its logger option to either the supplied logger or a NoopLogger fallback. The session implements raw execution by converting SQL objects via this.dialect.sqlToQuery(query) and delegating to prepareQuery().execute().

export class PrismaMySqlSession extends MySqlSession {
	static override readonly [entityKind]: string = 'PrismaMySqlSession';
 
	private readonly logger: Logger;
 
	constructor(
		dialect: MySqlDialect,
		private readonly prisma: PrismaClient,
		private readonly options: PrismaMySqlSessionOptions,
	) {
		super(dialect);
		this.logger = options.logger ?? new NoopLogger();
	}
 
	override execute<T>(query: SQL): Promise<T> {
		return this.prepareQuery<MySqlPreparedQueryConfig & { execute: T }>(this.dialect.sqlToQuery(query)).execute();
	}
 

Sources: drizzle-orm/src/prisma/mysql/session.ts:43-60

override prepareQuery<T extends MySqlPreparedQueryConfig = MySqlPreparedQueryConfig>(
	query: Query,
): MySqlPreparedQuery<T> {
	return new PrismaMySqlPreparedQuery(this.prisma, query, this.logger);
}

Sources: drizzle-orm/src/prisma/mysql/session.ts:65-69

Warning

PrismaMySqlSession does not implement all, transaction, or PrismaMySqlPreparedQuery.iterator. Invoking any of these methods throws an explicit Method not implemented. error at runtime.

override iterator(_placeholderValues?: Record<string, unknown> | undefined): AsyncGenerator<unknown, any, unknown> {
	throw new Error('Method not implemented.');
}

Sources: drizzle-orm/src/prisma/mysql/session.ts:19-21, drizzle-orm/src/prisma/mysql/session.ts:61-63, drizzle-orm/src/prisma/mysql/session.ts:71-84

PrismaMySqlPreparedQuery extends MySqlPreparedQuery to bind queries against PrismaClient. When execute() is called, parameter placeholders are populated using fillPlaceholders(), the query is logged, and the statement executes through prisma.$queryRawUnsafe().

export class PrismaMySqlPreparedQuery<T> extends MySqlPreparedQuery<MySqlPreparedQueryConfig & { execute: T }> {
	override iterator(_placeholderValues?: Record<string, unknown> | undefined): AsyncGenerator<unknown, any, unknown> {
		throw new Error('Method not implemented.');
	}
	static override readonly [entityKind]: string = 'PrismaMySqlPreparedQuery';
 
	constructor(
		private readonly prisma: PrismaClient,
		private readonly query: Query,
		private readonly logger: Logger,
	) {
		super(undefined, undefined, undefined);
	}
 
	override execute(placeholderValues?: Record<string, unknown>): Promise<T> {
		const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
		this.logger.logQuery(this.query.sql, params);
		return this.prisma.$queryRawUnsafe(this.query.sql, ...params);
	}
}

Sources: drizzle-orm/src/prisma/mysql/session.ts:18-37

Prisma SQLite Async Driver Architecture

Driver Initialization and Prisma Extension Setup

The SQLite interop module provides a drizzle() function that accepts a PrismaSQLiteConfig type, omitting the schema property from DrizzleConfig. It instantiates a SQLiteAsyncDialect and resolves the logger configuration: setting up a DefaultLogger when config.logger is true, preserving custom loggers, or omitting it otherwise.

export type PrismaSQLiteConfig = Omit<DrizzleConfig, 'schema'>;
 
export function drizzle(config: PrismaSQLiteConfig = {}) {
	const dialect = new SQLiteAsyncDialect();
	let logger: Logger | undefined;
	if (config.logger === true) {
		logger = new DefaultLogger();
	} else if (config.logger !== false) {
		logger = config.logger;
	}

Sources: drizzle-orm/src/prisma/sqlite/driver.ts:11-20

The function returns a Prisma extension via Prisma.defineExtension(), instantiating a PrismaSQLiteSession with the underlying client, dialect, and logger. It then injects the $drizzle database client initialized as a BaseSQLiteDatabase with an 'async' mode into the extended client.

return Prisma.defineExtension((client) => {
	const session = new PrismaSQLiteSession(client, dialect, { logger });
 
	return client.$extends({
		name: 'drizzle',
		client: {
			$drizzle: new BaseSQLiteDatabase('async', dialect, session, undefined) as PrismaSQLiteDatabase,
		},
	});
});

Sources: drizzle-orm/src/prisma/sqlite/driver.ts:22-31

Session Management and Prepared Query Execution

PrismaSQLiteSession extends SQLiteSession with 'async' execution mode, configuring its logger to either the provided options logger or a NoopLogger fallback. The session implements prepareQuery() to construct PrismaSQLitePreparedQuery instances and overrides transaction() to throw an unimplemented error.

export class PrismaSQLiteSession extends SQLiteSession<'async', unknown, Record<string, never>, Record<string, never>> {
	static override readonly [entityKind]: string = 'PrismaSQLiteSession';
 
	private readonly logger: Logger;
 
	constructor(
		private readonly prisma: PrismaClient,
		dialect: SQLiteAsyncDialect,
		options: PrismaSQLiteSessionOptions,
	) {
		super(dialect);
		this.logger = options.logger ?? new NoopLogger();
	}
 
	override prepareQuery<T extends Omit<PreparedQueryConfig, 'run'>>(
		query: Query,
		fields: SelectedFieldsOrdered | undefined,
		executeMethod: SQLiteExecuteMethod,
	): PrismaSQLitePreparedQuery<T> {
		return new PrismaSQLitePreparedQuery(this.prisma, query, this.logger, executeMethod);
	}
 
	override transaction<T>(
		_transaction: (tx: SQLiteTransaction<'async', unknown, Record<string, never>, Record<string, never>>) => Promise<T>,
		_config?: SQLiteTransactionConfig,
	): Promise<T> {
		throw new Error('Method not implemented.');
	}
}

Sources: drizzle-orm/src/prisma/sqlite/session.ts:62-90

Warning

PrismaSQLiteSession.transaction() and PrismaSQLitePreparedQuery.values() are not implemented. Invoking either method throws a Method not implemented. error at runtime.

override values(_placeholderValues?: Record<string, unknown> | undefined): Promise<never> {
	throw new Error('Method not implemented.');
}

Sources: drizzle-orm/src/prisma/sqlite/session.ts:49-51, drizzle-orm/src/prisma/sqlite/session.ts:84-89

PrismaSQLitePreparedQuery extends SQLitePreparedQuery to execute statements against PrismaClient. The all() method populates statement parameters via fillPlaceholders(), logs the query, and delegates to prisma.$queryRawUnsafe().

export class PrismaSQLitePreparedQuery<T extends PreparedQueryConfig = PreparedQueryConfig> extends SQLitePreparedQuery<
	{ type: 'async'; run: []; all: T['all']; get: T['get']; values: never; execute: T['execute'] }
> {
	static override readonly [entityKind]: string = 'PrismaSQLitePreparedQuery';
 
	constructor(
		private readonly prisma: PrismaClient,
		query: Query,
		private readonly logger: Logger,
		executeMethod: SQLiteExecuteMethod,
	) {
		super('async', executeMethod, query);
	}
 
	override all(placeholderValues?: Record<string, unknown>): Promise<T['all']> {
		const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
		this.logger.logQuery(this.query.sql, params);
		return this.prisma.$queryRawUnsafe(this.query.sql, ...params);
	}

Sources: drizzle-orm/src/prisma/sqlite/session.ts:19-37

The prepared query handles additional execution modalities by building upon all(). Specifically, run() executes the query and returns an empty array, get() retrieves the first row from the result set, and isResponseInArrayMode() explicitly returns false.

	override async run(placeholderValues?: Record<string, unknown> | undefined): Promise<[]> {
		await this.all(placeholderValues);
		return [];
	}
 
	override async get(placeholderValues?: Record<string, unknown> | undefined): Promise<T['get']> {
		const all = await this.all(placeholderValues) as unknown[];
		return all[0];
	}
 
	override values(_placeholderValues?: Record<string, unknown> | undefined): Promise<never> {
		throw new Error('Method not implemented.');
	}
 
	override isResponseInArrayMode(): boolean {
		return false;
	}
}

Sources: drizzle-orm/src/prisma/sqlite/session.ts:39-56

Serverless and Managed Adapter Interoperability

Neon and Netlify DB Driver Initialization

The serverless architecture adapts HTTP and WebSocket-based PostgreSQL clients for connectionless or pooled execution runtimes. The Neon serverless driver exports the NeonDriver and NeonDatabase classes, supporting initialization via connection strings, pool configurations, or pre-constructed client instances through drizzle().

export function drizzle<
	TSchema extends Record<string, unknown> = Record<string, never>,
	TClient extends NeonClient = Pool,
>(
	...params: [
		TClient | string,
	] | [
		TClient | string,
		DrizzleConfig<TSchema>,
	] | [
		(
			& DrizzleConfig<TSchema>
			& ({
				connection: string | PoolConfig;
			} | {
				client: TClient;
			})
			& {
				ws?: any;
			}
		),
	]
): NeonDatabase<TSchema> & {
	$client: NeonClient extends TClient ? Pool : TClient;
} {

Sources: drizzle-orm/src/neon-serverless/driver.ts:91-115

Similarly, the Netlify DB driver establishes type parsers during driver construction for temporal and array data types using types.setTypeParser().

export class NetlifyDbDriver {
	static readonly [entityKind]: string = 'NetlifyDbDriver';
 
	constructor(
		private httpClient: NeonHttpClient,
		private pool: Pool,
		private dialect: PgDialect,
		private options: NetlifyDbDriverOptions = {},
	) {
		this.initMappers();
	}
 
	createSession(
		schema: V1.RelationalSchemaConfig<V1.TablesRelationalConfig> | undefined,
	): NetlifyDbSession<Record<string, unknown>, V1.TablesRelationalConfig> {
		return new NetlifyDbSession(this.httpClient, this.pool, this.dialect, schema, {
			logger: this.options.logger,
			cache: this.options.cache,
		});
	}
 
	initMappers() {
		types.setTypeParser(types.builtins.TIMESTAMPTZ, (val: any) => val);
		types.setTypeParser(types.builtins.TIMESTAMP, (val: any) => val);
		types.setTypeParser(types.builtins.DATE, (val: any) => val);
		types.setTypeParser(types.builtins.INTERVAL, (val: any) => val);
		types.setTypeParser(1231, (val: any) => val);
		types.setTypeParser(1115, (val: any) => val);
		types.setTypeParser(1185, (val: any) => val);
		types.setTypeParser(1187, (val: any) => val);
		types.setTypeParser(1182, (val: any) => val);
	}
}

Sources: drizzle-orm/src/netlify-db/driver.ts:37-69

Type parsers registered during Netlify DB driver initialization map specific PostgreSQL built-in type OIDs directly to raw values without altering formats.

OID Constant / Built-inParser TargetSources
types.builtins.TIMESTAMPTZReturns raw valuedrizzle-orm/src/netlify-db/driver.ts:59
types.builtins.TIMESTAMPReturns raw valuedrizzle-orm/src/netlify-db/driver.ts:60
types.builtins.DATEReturns raw valuedrizzle-orm/src/netlify-db/driver.ts:61
types.builtins.INTERVALReturns raw valuedrizzle-orm/src/netlify-db/driver.ts:62
1231Returns raw valuedrizzle-orm/src/netlify-db/driver.ts:63
1115Returns raw valuedrizzle-orm/src/netlify-db/driver.ts:64
1185Returns raw valuedrizzle-orm/src/netlify-db/driver.ts:65
1187Returns raw valuedrizzle-orm/src/netlify-db/driver.ts:66
1182Returns raw valuedrizzle-orm/src/netlify-db/driver.ts:67

Sources: drizzle-orm/src/netlify-db/driver.ts:59-67

Session Execution and Transaction Semantics

NeonSession manages query preparation, logging, caching, and statement execution. When handling transactions, it inspects whether the underlying client is a Pool instance; if so, it acquires a dedicated client connection for the duration of the transaction block and releases it afterward in a finally block.

override async transaction<T>(
	transaction: (tx: NeonTransaction<TFullSchema, TSchema>) => Promise<T>,
	config: PgTransactionConfig = {},
): Promise<T> {
	const session = this.client instanceof Pool // eslint-disable-line no-instanceof/no-instanceof
		? new NeonSession(await this.client.connect(), this.dialect, this.schema, this.options)
		: this;
	const tx = new NeonTransaction<TFullSchema, TSchema>(this.dialect, session, this.schema);
	await tx.execute(sql`begin ${tx.getTransactionConfigSQL(config)}`);
	try {
		const result = await transaction(tx);
		await tx.execute(sql`commit`);
		return result;
	} catch (error) {
		await tx.execute(sql`rollback`);
		throw error;
	} finally {
		if (this.client instanceof Pool) { // eslint-disable-line no-instanceof/no-instanceof
			(session.client as PoolClient).release();
		}
	}
}

Sources: drizzle-orm/src/neon-serverless/session.ts:260-281

Warning

NeonSession.transaction() checks this.client instanceof Pool to branch between acquiring a pooled client connection or reusing the existing session client. Failing to release pool clients in custom implementations leaks connections.

const session = this.client instanceof Pool // eslint-disable-line no-instanceof/no-instanceof
	? new NeonSession(await this.client.connect(), this.dialect, this.schema, this.options)
	: this;

Sources: drizzle-orm/src/neon-serverless/session.ts:264-266, drizzle-orm/src/neon-serverless/session.ts:277-279

Nested transactions in both NeonTransaction and NetlifyDbTransaction utilize PostgreSQL savepoints with automatically incremented identifiers. Each nested transaction level generates a savepoint named via sp${this.nestedIndex + 1}, releasing or rolling back to that savepoint depending on execution success.

override async transaction<T>(transaction: (tx: NeonTransaction<TFullSchema, TSchema>) => Promise<T>): Promise<T> {
	const savepointName = `sp${this.nestedIndex + 1}`;
	const tx = new NeonTransaction<TFullSchema, TSchema>(this.dialect, this.session, this.schema, this.nestedIndex + 1);
	await tx.execute(sql.raw(`savepoint ${savepointName}`));
	try {
		const result = await transaction(tx);
		await tx.execute(sql.raw(`release savepoint ${savepointName}`));
		return result;
	} catch (e) {
		await tx.execute(sql.raw(`rollback to savepoint ${savepointName}`));
		throw e;
	}
}

Sources: drizzle-orm/src/neon-serverless/session.ts:290-302, drizzle-orm/src/netlify-db/session.ts:273-292

Cloud Runtime Driver Interop Patterns

Gel Driver and Client Initialization

The Gel driver interface supports initialization via connection strings, existing Client instances, or configuration objects containing connection options. The drizzle() factory function inspects its parameters to construct a GelDriver instance paired with a GelDialect.

Parameter VariantHandling BehaviorSources
string (DSN)Creates a client via createClient({ dsn })drizzle-orm/src/gel/driver.ts:109-113
Object (Config with client)Uses the provided client instance directlydrizzle-orm/src/gel/driver.ts:115-121
Object (Config with connection)Instantiates a client with connection optionsdrizzle-orm/src/gel/driver.ts:115-126
Direct ClientConstructs database instance using the clientdrizzle-orm/src/gel/driver.ts:128
drizzle.mock()Initializes a mock client objectdrizzle-orm/src/gel/driver.ts:131-138

Sources: drizzle-orm/src/gel/driver.ts:109-138

export function drizzle<
	TSchema extends Record<string, unknown> = Record<string, never>,
	TClient extends GelClient = Client,
>(
	...params:
		| [TClient | string]
		| [TClient | string, DrizzleConfig<TSchema>]
		| [
			& DrizzleConfig<TSchema>
			& (
				| {
					connection: string | ConnectOptions;
				}
				| {
					client: TClient;
				}
			),
		]

Sources: drizzle-orm/src/gel/driver.ts:88-105

AWS Data API Parameter Customization

The AWS Data API PostgreSQL driver specializes query execution through AwsPgDialect, which overrides parameter escaping and array handling. Positional parameters are formatted as :${num + 1}, and array columns in insert and update operations are explicitly cast to their SQL type definitions.

export class AwsPgDialect extends PgDialect {
	static override readonly [entityKind]: string = 'AwsPgDialect';
 
	override escapeParam(num: number): string {
		return `:${num + 1}`;
	}
 

Sources: drizzle-orm/src/aws-data-api/pg/driver.ts:50-56, drizzle-orm/src/aws-data-api/pg/driver.ts:67-72

Note

AwsPgDialect intercepts insert and update building to locate encoder instances matching PgArray. When array values are present, it wraps them in an explicit cast(... as ...) expression to satisfy strict AWS RDS Data API typing requirements.

	const colValue = value[fieldName];
	if (
		is(colValue, Param) && colValue.value !== undefined && is(colValue.encoder, PgArray)
		&& Array.isArray(colValue.value)
	) {
		value[fieldName] = sql`cast(${colValue} as ${sql.raw(colValue.encoder.getSQLType())})`;
	}
}

Sources: drizzle-orm/src/aws-data-api/pg/driver.ts:65-72, drizzle-orm/src/aws-data-api/pg/driver.ts:83-90

Was this page helpful?