---
title: "Sessions and Transactions"
description: "Sessions and transactions form the execution layer that bridges high-level query builders and low-level database drivers across diverse relational dialects and runtimes. By defining unified session..."
last_updated: "2026-10-12T08:14:00.746407+00:00"
canonical_url: "https://www.doc0.dev/docs/e1b68fed-3c4e-4c95-b2ba-ebf050f78025/technical/core-architecture/sessions-and-transactions"
---

<details>
<summary>Relevant source files</summary>

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

- [drizzle-orm/src/libsql/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/libsql/session.ts)
- [drizzle-orm/src/sqlite-proxy/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/sqlite-proxy/session.ts)
- [drizzle-orm/src/singlestore/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/singlestore/session.ts)
- [drizzle-orm/src/bun-sql/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/bun-sql/session.ts)
- [drizzle-orm/src/netlify-db/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/netlify-db/session.ts)
- [drizzle-orm/src/planetscale-serverless/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/planetscale-serverless/session.ts)
- [drizzle-orm/src/neon-serverless/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/neon-serverless/session.ts)
- [drizzle-orm/src/tidb-serverless/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/tidb-serverless/session.ts)
- [drizzle-orm/src/mysql2/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/mysql2/session.ts)
- [drizzle-orm/src/pglite/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/pglite/session.ts)
- [drizzle-orm/src/postgres-js/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/postgres-js/session.ts)
- [drizzle-orm/src/node-postgres/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/node-postgres/session.ts)
- [drizzle-orm/src/singlestore-core/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/singlestore-core/session.ts)
- [drizzle-orm/src/aws-data-api/pg/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/aws-data-api/pg/session.ts)
- [drizzle-orm/src/gel/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/gel/session.ts)
- [drizzle-orm/src/mysql-core/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/mysql-core/session.ts)
- [drizzle-orm/src/op-sqlite/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/op-sqlite/session.ts)
- [drizzle-orm/src/vercel-postgres/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/vercel-postgres/session.ts)
- [drizzle-orm/src/pg-proxy/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/pg-proxy/session.ts)
- [drizzle-orm/src/d1/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/d1/session.ts)
- [drizzle-orm/src/neon-http/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/neon-http/session.ts)
- [drizzle-orm/src/pg-core/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/pg-core/session.ts)
- [drizzle-orm/src/bun-sqlite/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/bun-sqlite/session.ts)
- [drizzle-orm/src/pg-core/db.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/pg-core/db.ts)
- [drizzle-orm/src/sqlite-core/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/sqlite-core/session.ts)
- [drizzle-orm/src/better-sqlite3/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/better-sqlite3/session.ts)
- [drizzle-orm/src/sql-js/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/sql-js/session.ts)
- [drizzle-orm/src/sqlite-core/db.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/sqlite-core/db.ts)
- [drizzle-orm/src/prisma/sqlite/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/prisma/sqlite/session.ts)
- [drizzle-orm/src/gel-core/session.ts](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/gel-core/session.ts)
</details>

## Overview

Sessions and transactions form the execution layer that bridges high-level query builders and low-level database drivers across diverse relational dialects and runtimes. By defining unified session abstractions like PostgreSQL Core, MySQL Core, and SQLite Core, the system standardizes statement preparation, execution pipelines, and result mapping regardless of whether queries run synchronously in Node.js or asynchronously across edge and serverless environments.

Sources: [drizzle-orm/src/pg-core/session.ts:168-234](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/pg-core/session.ts#L168-L234), [drizzle-orm/src/mysql-core/session.ts:170-238](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/mysql-core/session.ts#L170-L238), [drizzle-orm/src/sqlite-core/session.ts:212-323](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/sqlite-core/session.ts#L212-L323)

## Session Architecture and Prepared Queries

### Statement Execution Methods and Runtimes

The execution interface centers around dialect-specific session abstractions—such as `LibSQLSession`, `SQLiteRemoteSession`, `BunSQLSession`, `NeonSession`, `PgliteSession`, `GelDbSession`, `SQLJsSession`, and `PrismaSQLiteSession`—which inherit from shared base classes like `SQLiteSession` and `PgSession`. These sessions encapsulate database clients, configuration options, and telemetry loggers (`NoopLogger` or custom implementations), providing uniform methods for query compilation, statement preparation, and execution dispatch across diverse underlying runtimes.

Prepared statements are managed by specialized subclasses including `LibSQLPreparedQuery`, `RemotePreparedQuery`, `BunSQLPreparedQuery`, `NeonPreparedQuery`, `GelDbPreparedQuery`, `PreparedQuery` (for sql.js), and `PrismaSQLitePreparedQuery`. These query objects accept query fragments and parameters, resolve placeholder values via `fillPlaceholders`, record execution metrics through [logging and tracing](https://www.doc0.dev/docs/e1b68fed-3c4e-4c95-b2ba-ebf050f78025/technical/core-architecture/logging-and-tracing) hooks, and map raw driver outputs into structured domain entities.

```mermaid
graph TD
    S["Session (`LibSQLSession`, `BunSQLSession`, etc.)"] -->|"prepareQuery()"| PQ["Prepared Query (`LibSQLPreparedQuery`, `BunSQLPreparedQuery`, etc.)"]
    PQ -->|"fillPlaceholders()"| P["Parameterized Query"]
    P -->|"logQuery()"| L["Logger / Tracer"]
    P -->|"queryWithCache() / driver execute"| D["Driver Client"]
    D -->|"Raw Rows"| M["mapResultRow() / customResultMapper"]
```

Sources: [drizzle-orm/src/libsql/session.ts:29-75](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/libsql/session.ts#L29-L75), [drizzle-orm/src/sqlite-proxy/session.ts:29-74](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/sqlite-proxy/session.ts#L29-L74), [drizzle-orm/src/bun-sql/session.ts:19-103](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/bun-sql/session.ts#L19-L103), [drizzle-orm/src/neon-serverless/session.ts:188-233](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/neon-serverless/session.ts#L188-L233), [drizzle-orm/src/pglite/session.ts:165-195](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/pglite/session.ts#L165-L195), [drizzle-orm/src/gel/session.ts:17-102](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/gel/session.ts#L17-L102), [drizzle-orm/src/sql-js/session.ts:24-66](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/sql-js/session.ts#L24-L66), [drizzle-orm/src/prisma/sqlite/session.ts:62-83](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/prisma/sqlite/session.ts#L62-L83)

Prepared query contracts support a standard suite of execution methods—`run`, `all`, `get`, `values`, and `execute`—whose availability and return formats vary by driver implementation. For instance, synchronous sql.js prepared queries handle direct statement preparation, step iteration, result object extraction, and explicit resource freeing via `stmt.free()`, whereas asynchronous Bun SQL and Neon queries wrap execution within telemetry spans and cache layers.

| Session Class | Driver Client Type | Execution Mode | Supported Query Operations |
| :--- | :--- | :--- | :--- |
| `LibSQLSession` | `@libsql/client` `Client` | Async | `prepareQuery`, `batch`, `migrate`, `transaction` |
| `SQLiteRemoteSession` | `RemoteCallback` | Async | `prepareQuery`, `batch`, `transaction` |
| `BunSQLSession` | Bun `SQL` | Async | `prepareQuery`, `query`, `transaction`, `unsafe` |
| `NeonSession` | Neon `Pool` / `Client` | Async | `prepareQuery`, `query`, `queryObjects`, `count`, `transaction` |
| `PgliteSession` | `PgliteClient` | Async | `prepareQuery`, `transaction`, `count` |
| `GelDbSession` | Gel `Client` / `Transaction` | Async | `prepareQuery`, `transaction`, `count` |
| `SQLJsSession` | sql.js `Database` | Sync | `prepareQuery`, `transaction` |
| `PrismaSQLiteSession` | Prisma Client extension | Async | `prepareQuery`, `transaction` (unimplemented) |

Sources: [drizzle-orm/src/libsql/session.ts:29-129](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/libsql/session.ts#L29-L129), [drizzle-orm/src/sqlite-proxy/session.ts:29-105](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/sqlite-proxy/session.ts#L29-L105), [drizzle-orm/src/bun-sql/session.ts:110-188](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/bun-sql/session.ts#L110-L188), [drizzle-orm/src/neon-serverless/session.ts:188-281](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/neon-serverless/session.ts#L188-L281), [drizzle-orm/src/pglite/session.ts:165-195](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/pglite/session.ts#L165-L195), [drizzle-orm/src/gel/session.ts:109-168](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/gel/session.ts#L109-L168), [drizzle-orm/src/sql-js/session.ts:24-66](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/sql-js/session.ts#L24-L66), [drizzle-orm/src/prisma/sqlite/session.ts:62-89](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/prisma/sqlite/session.ts#L62-L89)

> [!NOTE]
> In sql.js sessions, binary field values (`Uint8Array`) are automatically normalized via `normalizeFieldValue` into Node `Buffer` instances when available, or decoded using `TextDecoder` if `Buffer` is absent.

Sources: [drizzle-orm/src/sql-js/session.ts:197-210](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/sql-js/session.ts#L197-L210)

## Transaction Lifecycles and Rollback Semantics

### Demarcation and Scopes

Transaction boundaries are managed via driver-specific demarcation methods wrapped in session abstractions. In `BunSQLSession`, transactions use the native `client.begin()` method, supplying a transaction-scoped client to instantiate `BunSQLTransaction`. Similarly, `PgliteSession` delegates directly to the underlying `client.transaction()` API, while `AwsDataApiSession` explicitly dispatches `BeginTransactionCommand`, `CommitTransactionCommand`, and `RollbackTransactionCommand` via the AWS Data API client.

```typescript
override async transaction<T>(
	transaction: (tx: AwsDataApiTransaction<TFullSchema, TSchema>) => Promise<T>,
	config?: PgTransactionConfig | undefined,
): Promise<T> {
	const { transactionId } = await this.client.send(new BeginTransactionCommand(this.rawQuery));
	const session = new AwsDataApiSession(this.client, this.dialect, this.schema, this.options, transactionId);
	const tx = new AwsDataApiTransaction<TFullSchema, TSchema>(this.dialect, session, this.schema);
	if (config) {
		await tx.setTransaction(config);
	}
	try {
		const result = await transaction(tx);
		await this.client.send(new CommitTransactionCommand({ ...this.rawQuery, transactionId }));
		return result;
	} catch (e) {
		await this.client.send(new RollbackTransactionCommand({ ...this.rawQuery, transactionId }));
		throw e;
	}
}
```

Sources: [drizzle-orm/src/bun-sql/session.ts:170-187](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/bun-sql/session.ts#L170-L187), [drizzle-orm/src/pglite/session.ts:170-187](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/pglite/session.ts#L170-L187), [drizzle-orm/src/aws-data-api/pg/session.ts:238-256](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/aws-data-api/pg/session.ts#L238-L256)

Neon and SQL.js sessions handle transaction boundaries by executing explicit raw SQL statements (`begin`, `commit`, and `rollback`) wrapped in `try...catch` blocks. In `NeonSession`, pool connections check whether the client instance is a `Pool` to release the client back to the pool in a `finally` block.

```typescript
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](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/neon-serverless/session.ts#L260-L281), [drizzle-orm/src/sql-js/session.ts:51-65](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/sql-js/session.ts#L51-L65)

> [!NOTE]
> In `SQLJsSession`, transaction configuration behaviors (such as deferred, immediate, or exclusive) are appended dynamically to the `begin` statement using `config.behavior`.

Sources: [drizzle-orm/src/sql-js/session.ts:51-56](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/sql-js/session.ts#L51-L56)

### Nested Scopes and Savepoints

Nested transaction blocks within driver implementations rely on database savepoints rather than multi-tier native transactions. Each nested scope increments `nestedIndex` to generate unique savepoint identifiers matching the pattern `sp1`, `sp2`, and so forth.

| Transaction Class | Nested Implementation Mechanism | Savepoint Naming Pattern | Rollback on Error |
| :--- | :--- | :--- | :--- |
| `BunSQLTransaction` | `client.savepoint()` | Managed by Bun SQL | Automatic via driver block |
| `NeonTransaction` | `savepoint` / `rollback to savepoint` | `sp${this.nestedIndex + 1}` | Explicit SQL command |
| `TiDBServerlessTransaction` | `savepoint` / `rollback to savepoint` | `sp${this.nestedIndex + 1}` | Explicit SQL command |
| `PgliteTransaction` | `savepoint` / `rollback to savepoint` | `sp${this.nestedIndex + 1}` | Explicit SQL command |
| `AwsDataApiTransaction` | `savepoint` / `rollback to savepoint` | `sp${this.nestedIndex + 1}` | Explicit SQL command |
| `SQLJsTransaction` | `savepoint` / `rollback to savepoint` | `sp${this.nestedIndex + 1}` | Explicit SQL command |

Sources: [drizzle-orm/src/bun-sql/session.ts:206-219](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/bun-sql/session.ts#L206-L219), [drizzle-orm/src/neon-serverless/session.ts:290-302](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/neon-serverless/session.ts#L290-L302), [drizzle-orm/src/tidb-serverless/session.ts:209-228](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/tidb-serverless/session.ts#L209-L228), [drizzle-orm/src/pglite/session.ts:203-220](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/pglite/session.ts#L203-L220), [drizzle-orm/src/aws-data-api/pg/session.ts:265-284](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/aws-data-api/pg/session.ts#L265-L284), [drizzle-orm/src/sql-js/session.ts:74-86](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/sql-js/session.ts#L74-L86)

> [!CAUTION]
> When an error occurs inside a nested transaction block utilizing savepoints, the execution path triggers `rollback to savepoint spN` and releases or throws the error, preserving the outer transaction state.

Sources: [drizzle-orm/src/neon-serverless/session.ts:298-301](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/neon-serverless/session.ts#L298-L301), [drizzle-orm/src/sql-js/session.ts:82-85](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/sql-js/session.ts#L82-L85)

## Driver Connection Wrappers and Execution

### Connection Bridging and Client Execution

Drizzle abstracts disparate underlying driver connection interfaces into uniform session and prepared query instances across runtimes. In environments powered by Bun SQL (`BunSQLSession`) and Pglite (`PgliteSession`), queries are dispatched using driver-native `.unsafe()` and `.transaction()` wrappers while capturing telemetry through OpenTelemetry spans.

```typescript
async execute(placeholderValues: Record<string, unknown> | undefined = {}): Promise<T['execute']> {
	return tracer.startActiveSpan('drizzle.execute', async (span) => {
		const params = fillPlaceholders(this.params, placeholderValues);

		span?.setAttributes({
			'drizzle.query.text': this.queryString,
			'drizzle.query.params': JSON.stringify(params),
		});

		this.logger.logQuery(this.queryString, params);

		const { fields, queryString: query, client, joinsNotNullableMap, customResultMapper } = this;
		if (!fields && !customResultMapper) {
			return tracer.startActiveSpan('drizzle.driver.execute', async () => {
				return await this.queryWithCache(query, params, async () => {
					return await client.unsafe(query, params as any[]);
				});
			});
		}
```

Sources: [drizzle-orm/src/bun-sql/session.ts:40-58](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/bun-sql/session.ts#L40-L58), [drizzle-orm/src/pglite/session.ts:165-187](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/pglite/session.ts#L165-L187)

> [!NOTE]
> Client adapters like `PrismaSQLitePreparedQuery` bridge SQLite execution by mapping calls directly to Prisma's `$queryRawUnsafe` API, while throwing unsupported errors for raw value arrays and transaction blocks.

Sources: [drizzle-orm/src/prisma/sqlite/session.ts:33-56](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/prisma/sqlite/session.ts#L33-L56), [drizzle-orm/src/prisma/sqlite/session.ts:84-90](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/prisma/sqlite/session.ts#L84-L90)

### Synchronous and Asynchronous SQLite Execution Pipelines

SQLite runtimes split execution semantics between synchronous in-memory drivers like `sql.js` and asynchronous mobile/edge drivers like `OP-SQLite`. `SQLJsSession` utilizes explicit statement preparation, parameter binding via `BindParams`, stepping via `stmt.step()`, and manual resource freeing via `stmt.free()`.

```typescript
run(placeholderValues?: Record<string, unknown>): void {
	const stmt = this.client.prepare(this.query.sql);

	const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
	this.logger.logQuery(this.query.sql, params);
	const result = stmt.run(params as BindParams);

	stmt.free();

	return result;
}
```

Sources: [drizzle-orm/src/sql-js/session.ts:106-116](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/sql-js/session.ts#L106-L116), [drizzle-orm/src/sql-js/session.ts:118-143](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/sql-js/session.ts#L118-L143)

In contrast, `OPSQLiteSession` routes execution methods through asynchronous connection bindings:

| Execution Method | OP-SQLite Driver Call | Return Type |
| :--- | :--- | :--- |
| `run` | `client.executeAsync(query, params)` | `QueryResult` |
| `all` | `client.execute(query, params).rows?._array` | `T['all']` |
| `values` | `client.executeRawAsync(query, params)` | `T['values']` |

Sources: [drizzle-orm/src/op-sqlite/session.ts:135-142](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/op-sqlite/session.ts#L135-L142), [drizzle-orm/src/op-sqlite/session.ts:144-160](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/op-sqlite/session.ts#L144-L160), [drizzle-orm/src/op-sqlite/session.ts:187-193](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/op-sqlite/session.ts#L187-L193)

> [!WARNING]
> When decoding binary results in `SQLJsSession`, `normalizeFieldValue` checks `Uint8Array` instances and converts them using `Buffer.from()` if available, or `TextDecoder` otherwise, throwing an error if neither polyfill exists.

Sources: [drizzle-orm/src/sql-js/session.ts:197-210](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/sql-js/session.ts#L197-L210)

## HTTP and Serverless Transaction Emulation

### Stateless HTTP Gateways and WebSocket Upgrades

Stateless HTTP gateways and serverless environments present distinct coordination challenges for transaction semantics because HTTP protocols do not inherently maintain persistent connection state between requests. Drivers such as Neon HTTP (`NeonHttpSession`) execute queries over stateless HTTPS connections using `@neondatabase/serverless`, which inherently prevents multi-statement transaction execution blocks since each query can hit a different serverless container or worker instance. Consequently, calling `transaction()` on `NeonHttpSession` throws an error explicitly stating that transactions are unsupported in the `neon-http` driver.

```typescript
override async transaction<T>(
	_transaction: (tx: NeonTransaction<TFullSchema, TSchema>) => Promise<T>,
	// eslint-disable-next-line @typescript-eslint/no-unused-vars
	_config: PgTransactionConfig = {},
): Promise<T> {
	throw new Error('No transactions support in neon-http driver');
}
```

Sources: [drizzle-orm/src/neon-http/session.ts:248-254](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/neon-http/session.ts#L248-L254)

In contrast, hybrid adapters like Netlify DB (`NetlifyDbSession`) manage this limitation by bridging HTTP and WebSocket protocols. When a transaction is initiated, `NetlifyDbSession` acquires a dedicated `PoolClient` instance from an underlying connection pool, establishes a persistent WebSocket connection, executes `begin` alongside transaction configuration parameters, and executes the transactional callback inside `NetlifyDbTransaction`.

```typescript
	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 {
		poolClient.release();
	}
}
```

Sources: [drizzle-orm/src/netlify-db/session.ts:168-179](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/netlify-db/session.ts#L168-L179)

> [!WARNING]
> Netlify DB WebSocket sessions (`NetlifyDbWsSession`) explicitly throw an error if nested `transaction()` calls are invoked directly on the session instance, enforcing that inner scopes route through `NetlifyDbTransaction` savepoint handling instead.

Sources: [drizzle-orm/src/netlify-db/session.ts:259-265](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/netlify-db/session.ts#L259-L265)

### Client-Side Transaction Coordination

AWS Data API and remote proxy runtimes coordinate transactions by passing explicit transaction identifiers across stateless API requests. `AwsDataApiSession` manages transaction blocks by sending a `BeginTransactionCommand` to the AWS Data API client, receiving a unique `transactionId` string, and injecting that identifier into every subsequent prepared query statement executed within that scope.

```typescript
override async transaction<T>(
	transaction: (tx: AwsDataApiTransaction<TFullSchema, TSchema>) => Promise<T>,
	config?: PgTransactionConfig | undefined,
): Promise<T> {
	const { transactionId } = await this.client.send(new BeginTransactionCommand(this.rawQuery));
	const session = new AwsDataApiSession(this.client, this.dialect, this.schema, this.options, transactionId);
	const tx = new AwsDataApiTransaction<TFullSchema, TSchema>(this.dialect, session, this.schema);
	if (config) {
		await tx.setTransaction(config);
	}
	try {
		const result = await transaction(tx);
		await this.client.send(new CommitTransactionCommand({ ...this.rawQuery, transactionId }));
		return result;
	} catch (e) {
		await this.client.send(new RollbackTransactionCommand({ ...this.rawQuery, transactionId }));
		throw e;
	}
}
```

Sources: [drizzle-orm/src/aws-data-api/pg/session.ts:238-256](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/aws-data-api/pg/session.ts#L238-L256)

Remote proxy drivers (`PgRemoteSession`) bypass transaction handling entirely because the underlying remote callback transport lacks stateful transaction coordination, throwing an error on invocation.

| Driver Session Class | Transaction Support Strategy | Behavior on `transaction()` |
| :--- | :--- | :--- |
| `NeonHttpSession` | None (Stateless HTTP) | Throws `'No transactions support in neon-http driver'` |
| `NetlifyDbSession` | WebSocket Pool Client | Acquires `PoolClient`, runs `begin`/`commit`/`rollback`, releases client |
| `AwsDataApiSession` | API Transaction ID | Sends `BeginTransactionCommand`, tracks `transactionId`, sends `CommitTransactionCommand` or `RollbackTransactionCommand` |
| `PgRemoteSession` | Remote Proxy Callback | Throws `'Transactions are not supported by the Postgres Proxy driver'` |

Sources: [drizzle-orm/src/neon-http/session.ts:248-254](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/neon-http/session.ts#L248-L254), [drizzle-orm/src/netlify-db/session.ts:168-179](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/netlify-db/session.ts#L168-L179), [drizzle-orm/src/aws-data-api/pg/session.ts:238-256](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/aws-data-api/pg/session.ts#L238-L256), [drizzle-orm/src/pg-proxy/session.ts:70-75](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/pg-proxy/session.ts#L70-L75)

> [!NOTE]
> Netlify DB explicitly verifies that a WebSocket constructor is available globally before initializing serverless query execution, assigning `WebSocket` to `neonConfig.webSocketConstructor` if unassigned.

Sources: [drizzle-orm/src/netlify-db/session.ts:39-45](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/netlify-db/session.ts#L39-L45)

## Batch Pipelines and Proxy Sessions

### Batch Item Aggregation

Batch execution models in Drizzle aggregate arrays of query items into single pipeline requests dispatched to remote drivers or database clients. In `LibSQLSession`, the `batch()` method iterates over incoming `queries`, invokes `_prepare()` on each item to generate a prepared query, extracts the underlying SQL string and parameter arguments via `getQuery()`, and pushes them into an `InStatement` array.

```typescript
async batch<T extends BatchItem<'sqlite'>[] | readonly BatchItem<'sqlite'>[]>(queries: T) {
	const preparedQueries: PreparedQuery[] = [];
	const builtQueries: InStatement[] = [];

	for (const query of queries) {
		const preparedQuery = query._prepare();
		const builtQuery = preparedQuery.getQuery();
		preparedQueries.push(preparedQuery);
		builtQueries.push({ sql: builtQuery.sql, args: builtQuery.params as InArgs });
	}
```

Sources: [drizzle-orm/src/libsql/session.ts:77-86](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/libsql/session.ts#L77-L86)

Once the built statements are assembled, `LibSQLSession` invokes `this.client.batch(builtQueries)` and maps the resulting array of driver outputs back through each individual prepared query's `mapResult()` handler with array mode enabled.

```typescript
	const batchResults = await this.client.batch(builtQueries);
	return batchResults.map((result, i) => preparedQueries[i]!.mapResult(result, true));
}
```

Sources: [drizzle-orm/src/libsql/session.ts:88-90](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/libsql/session.ts#L88-L90)

Similarly, `LibSQLSession` provides a `migrate()` method that mirrors the exact structure of `batch()` but delegates to `this.client.migrate(builtQueries)` for migration-specific pipeline execution.

```typescript
async migrate<T extends BatchItem<'sqlite'>[] | readonly BatchItem<'sqlite'>[]>(queries: T) {
	const preparedQueries: PreparedQuery[] = [];
	const builtQueries: InStatement[] = [];

	for (const query of queries) {
		const preparedQuery = query._prepare();
		const builtQuery = preparedQuery.getQuery();
		preparedQueries.push(preparedQuery);
		builtQueries.push({ sql: builtQuery.sql, args: builtQuery.params as InArgs });
	}

	const batchResults = await this.client.migrate(builtQueries);
	return batchResults.map((result, i) => preparedQueries[i]!.mapResult(result, true));
}
```

Sources: [drizzle-orm/src/libsql/session.ts:92-105](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/libsql/session.ts#L92-L105)

### Remote Proxy Transports

Proxy and HTTP-backed drivers implement batch pipelines by marshalling query descriptors across remote transport boundaries. In `NeonHttpSession`, the `batch()` method processes incoming queries by creating `NeonQueryPromise` instances with `fullResults: true` and array mode determined by each prepared query's `isResponseInArrayMode()` configuration.

```typescript
async batch<U extends BatchItem<'pg'>, T extends Readonly<[U, ...U[]]>>(
	queries: T,
) {
	const preparedQueries: PreparedQuery[] = [];
	const builtQueries: NeonQueryPromise<any, true>[] = [];
	for (const query of queries) {
		const preparedQuery = query._prepare();
		const builtQuery = preparedQuery.getQuery();
		preparedQueries.push(preparedQuery);
		builtQueries.push(
			this.clientQuery(builtQuery.sql, builtQuery.params, {
				fullResults: true,
				arrayMode: preparedQuery.isResponseInArrayMode(),
			}),
		);
	}
```

Sources: [drizzle-orm/src/neon-http/session.ts:199-214](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/neon-http/session.ts#L199-L214)

The collected promises are then dispatched to Neon's HTTP client transaction endpoint using `this.client.transaction(builtQueries, queryConfig)` before mapping the batch results back to their respective prepared queries.

```typescript
const batchResults = await this.client.transaction(builtQueries, queryConfig);

return batchResults.map((result, i) => preparedQueries[i]!.mapResult(result, true)) as any;
```

Sources: [drizzle-orm/src/neon-http/session.ts:216-218](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/neon-http/session.ts#L216-L218)

For SQLite proxy sessions (`SQLiteRemoteSession`), batch aggregation maps query parameters alongside explicit execution methods (`run`, `all`, `values`, or `get`) into remote transport payloads before invoking `this.batchCLient`.

```typescript
async batch<T extends BatchItem<'sqlite'>[] | readonly BatchItem<'sqlite'>[]>(queries: T) {
	const preparedQueries: PreparedQuery[] = [];
	const builtQueries: { sql: string; params: any[]; method: 'run' | 'all' | 'values' | 'get' }[] = [];

	for (const query of queries) {
		const preparedQuery = query._prepare();
		const builtQuery = (preparedQuery as RemotePreparedQuery).getQuery();
		preparedQueries.push(preparedQuery);
		builtQueries.push({ sql: builtQuery.sql, params: builtQuery.params, method: builtQuery.method });
	}

	const batchResults = await (this.batchCLient as AsyncBatchRemoteCallback)(builtQueries);
	return batchResults.map((result, i) => preparedQueries[i]!.mapResult(result, true));
}
```

Sources: [drizzle-orm/src/sqlite-proxy/session.ts:76-89](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/sqlite-proxy/session.ts#L76-L89)

> [!NOTE]
> `SQLiteRemoteSession` extracts raw batch result rows using session-specific helpers like `extractRawAllValueFromBatchResult`, `extractRawGetValueFromBatchResult`, and `extractRawValuesValueFromBatchResult` tailored to `SqliteRemoteResult` structures.

Sources: [drizzle-orm/src/sqlite-proxy/session.ts:107-117](https://github.com/blade47/drizzle-orm/blob/48e5406027103a9fca6eb66417187c4a8b5c6aa3/drizzle-orm/src/sqlite-proxy/session.ts#L107-L117)

## Related

- [Query Builder Foundation](https://www.doc0.dev/docs/e1b68fed-3c4e-4c95-b2ba-ebf050f78025/technical/core-architecture/query-builder-foundation)
- [Drivers And Runtimes](https://www.doc0.dev/docs/e1b68fed-3c4e-4c95-b2ba-ebf050f78025/technical/drivers-and-runtimes/drivers-and-runtimes)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/e1b68fed-3c4e-4c95-b2ba-ebf050f78025/llms.txt) for all pages in this wiki.
