> Every UQL docs page, as Markdown: https://uql-orm.dev/llms.txt
> The same docs over MCP: https://uql-orm.dev/mcp
> Before writing UQL code, read the skill: https://uql-orm.dev/.well-known/agent-skills/uql-orm/SKILL.md

# Cursor Streaming

> Process millions of rows with a stable memory footprint using native driver-level cursors.

Source: https://uql-orm.dev/querying/streaming

`findManyStream()` is for result sets too large to hold in memory. Rather than filling a TypeScript array, it returns an `AsyncIterable` that hands you each row as it arrives from the database.

## Basic Usage

`findManyStream` takes the same query as `findMany`, populated relations and `$count` included, and runs no lifecycle hooks.

```ts
import { pool } from './uql.config.js';
import { User } from './shared/models/index.js';

const results = pool.findManyStream(User, {
  $select: { id: true, email: true },
  $where: { status: 'active' },
});

for await (const user of results) {
  // Process each user row-by-row
  console.log(`Processing: ${user.email}`);
}
```

It also works [straight on the pool](https://uql-orm.dev/querying/querier.md#choosing-poolx-vs-querierx), which is the one pool call whose connection outlives the call: it is held for the whole iteration and released when the loop ends, or when a `break`/`throw` closes the iterator. Abandoning the iterator without closing it leaks that connection, so keep it inside a `for await`.

```ts
import { pool } from './uql.config.js';

for await (const user of pool.findManyStream(User, {
  $where: { status: 'active' },
})) {
  console.log(user.email);
}
```

## Why use Streaming?

Memory stays flat regardless of result size, since rows are processed as they arrive rather than buffered into an array. You also start handling the first row before the database finishes producing the last one, and because iteration drives the cursor, the database only sends rows as fast as your loop consumes them.

## Native Driver Implementation

Each driver streams its own way:

| Driver | Implementation |
| - | - |
| **PostgreSQL** (`pg`), **CockroachDB**, **Neon** | Client cursor via `pg-query-stream`. |
| **Bun SQL** (`bun:sql`) on Postgres or CockroachDB | Server-side cursor: `DECLARE` / `FETCH FORWARD` / `CLOSE`. |
| **PGlite** | Server-side cursor, the same way. |
| **MySQL** (`mysql2`) | Result set streaming via `.stream()`. |
| **MariaDB** (`mariadb`) | Native `queryStream()`. |
| **MSSQL** (`mssql`) | The driver’s own stream, paused while the loop is behind. |
| **SQLite** (`better-sqlite3`, `node:sqlite`, `bun:sqlite`) | Iteration via `.iterate()`. |
| **Bun SQL** (`bun:sql`) on MySQL or MariaDB | Buffered: the whole result, then yielded row by row. |
| **MongoDB** (`mongodb`) | Native MongoDB `Cursor`. |
| **Turso Cloud** (`@tursodatabase/serverless`) | The statement’s cursor, as the server steps it. |
| **LibSQL** / **D1** | Buffered: the whole result, then yielded row by row. |

A **server-side cursor** is what a driver with no cursor API of its own gets: `bun:sql` exposes none ([oven-sh/bun#17181](https://github.com/oven-sh/bun/issues/17181)) and neither does PGlite’s client, so the rows are paged in SQL instead, 100 at a time. `DECLARE` is only legal inside a transaction, so the stream opens one when the caller has none and ends it with the loop; inside your own transaction it just declares the cursor and leaves the transaction alone. A **buffered** driver holds the full result in memory before yielding, so `findManyStream` is an API convenience there, not a memory one: those engines have no cursor the client can reach.

> **The connection is held for the whole loop**
>
> Streaming holds a database connection open for the duration of the loop. Keep the processing logic inside the `for await` loop fast; if each row needs heavy work, push it to a task queue and let the loop move on.

---

## Next Steps

- [Querier API](https://uql-orm.dev/querying/querier.md): `findMany` and the rest of the read API.
- [Deep Relations](https://uql-orm.dev/querying/relations.md): What `$populate` loads into each streamed row.
- [Transactions](https://uql-orm.dev/querying/transactions.md): Holding a connection open for the duration of a stream.
- [Aggregate Queries](https://uql-orm.dev/querying/aggregate.md): Let the database reduce the rows instead.
