diff --git a/apps/docs/content/docs/orm/reference/orm-client.mdx b/apps/docs/content/docs/orm/reference/orm-client.mdx
index 80a577e27a4..e8e3fc26037 100644
--- a/apps/docs/content/docs/orm/reference/orm-client.mdx
+++ b/apps/docs/content/docs/orm/reference/orm-client.mdx
@@ -6,15 +6,13 @@ metaTitle: Prisma ORM client reference
metaDescription: 'Reference for the Prisma ORM client''s query, mutation, filter, and aggregate methods.'
---
-The ORM client gives you model-level methods for reading and writing data across PostgreSQL and MongoDB. This page documents every method, its availability on each database, and the behavior that differs between the two.
+The ORM client gives you model-level methods for reading and writing data across PostgreSQL and MongoDB. This page documents every method, its availability on each database, and the behavior that differs between the two. Each method's Remarks say which databases it works on, how it behaves differently on one of them, and where TypeScript rejects something the database itself would accept.
-Availability is stated per method. When a method behaves the same on both databases, the Remarks say so; when it differs, exists on only one, or is type-checked but not enforced at runtime, the Remarks call that out.
-
-For task-oriented walkthroughs, see the Fundamentals guides: [Reading data](/orm/fundamentals/reading-data), [Writing data](/orm/fundamentals/writing-data), [Relations and joins](/orm/fundamentals/relations-and-joins), and [Transactions](/orm/fundamentals/transactions).
+This page assumes your tables already exist. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) has the packages to install and the `prisma orm init` command. It also covers `prisma db update`, which replaces `prisma migrate dev`, and `prisma contract emit`, which replaces `prisma generate`. [How migrations work](/orm/migrations/how-migrations-work) shows how the tables get created. For task-oriented walkthroughs, see the Fundamentals guides: [Reading data](/orm/fundamentals/reading-data), [Writing data](/orm/fundamentals/writing-data), [Relations and joins](/orm/fundamentals/relations-and-joins), and [Transactions](/orm/fundamentals/transactions).
## Example schema
-All examples on this page run against the following schema, and every example is transcribed from an executable test suite that runs against a live database. The grouped-aggregate examples use a separate `Customer` / `Order` schema, shown under [Grouped aggregates](#grouped-aggregates).
+All examples on this page run against the schema below, except the grouped-aggregate ones, which use a separate `Customer` / `Order` schema shown under [Grouped aggregates](#grouped-aggregates). `contract.prisma` replaces `schema.prisma`. This page calls it the contract. Running `npx prisma contract emit` compiles it into two generated files: `contract.json`, read at run time, and `contract.d.ts`, read by TypeScript. You import both, as the examples below do. Run `npx prisma contract emit` again after every change to `contract.prisma`. A new project keeps all three files in `src/prisma/`.
@@ -177,13 +175,35 @@ model Tutorial {
+Some parts of the schema the examples rely on:
+
+- The `types` block at the top gives a name to a type from an extension package, so your models can use that name. `Embedding1536` comes from the pgvector extension, and `pgvector.` is that package's prefix. See [PostgreSQL](#postgresql) for the extra line it needs when you create the client.
+- `@@map("user")` sets the table or collection name. Without it, the table name is the model name with a lowercase first letter.
+- On MongoDB, `id ObjectId @id @map("_id")` renames the field to `_id` everywhere: you filter on `_id`, and the returned document has an `_id` key. That is why the MongoDB examples never say `id`.
+- `Uuid` is a built-in type for a PostgreSQL `uuid` column. You do not import it. `@default(uuid())` fills the id in when you insert a row.
+- `@@type("pg/text@1")` on an `enum` block stores the enum as a text column. The `@1` is the version of that storage format, and only version 1 exists today, so write it exactly like that. The MongoDB form of the same attribute is `@@type("mongo/string@1")`. This page calls an enum written this way a text-backed enum.
+- A `native_enum` block stores the same members as a real PostgreSQL enum type instead. Give every member a value. A member with no value is rejected. This page calls an enum written this way a native enum. The two sort differently; see [`orderBy()`](#orderby).
+
+ ```prisma
+ native_enum Priority {
+ Low = "low"
+ High = "high"
+ Urgent = "urgent"
+ }
+ ```
+
+- An enum member's stored value is the string after `=`, or the member name when there is no `=`. That string is what you pass to `.eq()`. So `Urgent = "urgent"` is matched by `.eq('urgent')`, and a bare `admin` member is matched by `.eq('admin')`.
+- A variant is a model that reuses another model's fields and rows. `@@base(Task, "bug")` makes `Bug` a variant of `Task`. Its second argument, `"bug"`, is the value stored in the discriminator column for a `Bug` row. `@@discriminator(type)` names that column. `db.orm.public.Task.variant('Bug')` returns only the `Bug` rows. Pass the model name, `'Bug'`, not the string in `@@base`.
+- `PostTag` is required. To join `Post.tags Tag[]` and `Tag.posts Post[]`, write a third model with one foreign key to each side and an `@@id` of exactly those two columns. Prisma ORM finds it for you, and neither `Post` nor `Tag` names it. A pair of list fields with no such model is rejected. See [Relations and joins](/orm/fundamentals/relations-and-joins#many-to-many).
+- On PostgreSQL, a `DateTime` field comes back as a `Temporal.Instant`, the standard JavaScript object for a point in time. A filter on that field takes the same type, so you can pass a value straight back in.
+
## Setting up the client
-Create an ORM client with `postgres(...)` or `mongo(...)`, then query models through the client's `.orm` facet. The two databases have different entry points and different accessor conventions.
+Create a client with `postgres(...)` or `mongo(...)`, then read and write your models through `db.orm`. The same `db` also has a query builder, `db.sql` on PostgreSQL and `db.query` on MongoDB, plus `db.raw` for raw queries. This page covers none of those three. See [Advanced queries](/orm/fundamentals/advanced-queries). Create `db` once per process, and close it with `await db.close()` when the process shuts down. The two databases have different entry points, and they name the models differently.
### PostgreSQL
-Create a Postgres client with `postgres(...)`. Models hang off `db.orm.public` and use the contract's **root names**, which match your Prisma Schema Language (PSL) model names (`db.orm.public.User`, `db.orm.public.Post`). `public` is the default PostgreSQL schema namespace.
+Create a PostgreSQL client with `postgres(...)`. `db.orm` holds your models by model name, grouped by database schema: `db.orm.public.User`, `db.orm.public.Post`. `public` is the PostgreSQL schema your tables are in unless you put them in another one. Import types from `./contract.d`, which resolves to `contract.d.ts`.
```typescript
import postgres from '@prisma/orm-postgres/runtime';
@@ -195,13 +215,23 @@ const db = postgres({ contractJson, url: process.env.DATABASE_URL });
const users = await db.orm.public.User.all();
```
-If your contract uses types from an extension pack, pass the pack when you create the client. The example schema's `Embedding1536` (pgvector) type needs `extensions: [pgvector]`, with `pgvector` imported from `@prisma/orm-extension-pgvector/runtime`.
+If your contract uses a type from an extension package, pass that package to `extensions` when you create the client. The example schema's `Embedding1536` type comes from pgvector, so it needs this:
+
+```typescript
+import pgvector from '@prisma/orm-extension-pgvector/runtime';
-To attach domain methods to a model, register a custom `Collection` subclass when you build the client. That path uses the `orm(...)` factory instead of the client's built-in facet; see [Custom `Collection` subclass](#custom-collection-subclass).
+const db = postgres({
+ contractJson,
+ url: process.env.DATABASE_URL,
+ extensions: [pgvector],
+});
+```
+
+To give a model your own methods on top of the built-in ones, import `Collection` from `@prisma/orm-postgres/orm-client`, subclass it, and register the subclass with `orm(...)` from that same package. You still create the client with `postgres(...)`. `orm(...)` takes two things from it: the `runtime` argument, which is what `await client.connect()` gives you, and the `context` argument, which is `client.context`. The code is under [Custom `Collection` subclass](#custom-collection-subclass).
### MongoDB
-Create a Mongo client with `mongo(...)`. Models hang off `db.orm` and use the contract's **root names too, but those are the lowercase plural collection names** from the contract's `roots` map, not the PSL model names (`db.orm.users`, `db.orm.posts`, not `db.orm.User`).
+Create a MongoDB client with `mongo(...)`. `db.orm` holds your models by collection name, with no schema in between: `db.orm.users`, `db.orm.posts`, not `db.orm.User`. The collection name is the one you set with `@@map`, or the model name with a lowercase first letter when you did not set one. `dbName` is the MongoDB database name.
```typescript
import mongo from '@prisma/orm-mongo/runtime';
@@ -213,15 +243,20 @@ const db = mongo({ contractJson, url: process.env.MONGODB_URL, dbName:
const users = await db.orm.users.all();
```
-:::note[Accessor naming differs by database]
-PostgreSQL exposes models under their contract root names (matching PSL model names) and namespaced by schema: `db.orm.public.User`. MongoDB exposes them under the registered collection root names (lowercase plural), with no namespace: `db.orm.users`. The examples below follow each database's convention.
-:::
-
## Query-building methods
-These methods narrow a query. They return a collection you can chain further methods on, and you resolve the query with a [read terminal](#read-terminals) such as `all()` or `first()`. MongoDB supports a smaller set than PostgreSQL; each method's Remarks state its availability.
+These methods narrow a query. Each returns a collection. On this page, a collection is the query object you chain more methods on, such as `db.orm.public.User` or `db.orm.public.User.where(...)`. When this page says "a MongoDB collection", it means that query object. MongoDB itself calls its store of documents a collection too, and the name is the same one. You run the query with a [read method](#read-terminals) such as `all()` or `first()`. MongoDB supports fewer of these methods than PostgreSQL, and each method's Remarks say which databases it works on.
-Import the standalone filter combinators used in some examples from `@prisma/orm-postgres/orm-client` (PostgreSQL) or `@prisma/orm-mongo/query-ast/execution` (MongoDB). See [Filter conditions and operators](#filter-conditions-and-operators).
+Some examples import a filter helper you can use on its own. On PostgreSQL those come from `@prisma/orm-postgres/orm-client`, on MongoDB from `@prisma/orm-mongo/query-ast/execution`. See [Filter conditions and operators](#filter-conditions-and-operators).
+
+The examples below use these ids, which stand for rows inserted before the query runs:
+
+```typescript
+const aliceId = '00000000-0000-4000-8000-000000000001';
+const carolId = '00000000-0000-4000-8000-000000000003';
+const postId = '00000000-0000-4000-8000-000000000010';
+const acmeId = '00000000-0000-4000-8000-000000000020';
+```
### `where()`
@@ -230,21 +265,22 @@ Restrict a query to rows matching a filter.
#### Remarks
- Available for PostgreSQL and MongoDB, but the accepted filter shapes differ.
-- On PostgreSQL, `where()` accepts a callback with column-level operators (`u.email.eq(...)`) or a shorthand object of equality matches.
-- On MongoDB, `where()` accepts a shorthand object of equality matches or a `MongoFieldFilter` expression. The plain-object form supports equality only, not nested operators; for comparisons use `MongoFieldFilter` (see [MongoFieldFilter](#mongofieldfilter)).
-- Chaining multiple `where()` calls ANDs the filters together.
+- On PostgreSQL, `where()` takes either a callback that calls an operator on a column (`u.email.eq(...)`) or an object of field-and-value pairs that must match exactly.
+- On MongoDB, `where()` takes either an object of field-and-value pairs that must match exactly or a `MongoFieldFilter`. The object form can only test for equality. For anything else, such as greater-than, use `MongoFieldFilter` (see [MongoFieldFilter](#mongofieldfilter)).
+- Calling `where()` more than once on the same query requires a row to match all of the filters.
+- `.eq()` is one operator of many. [Filter conditions and operators](#filter-conditions-and-operators), further down this page, lists them all for both databases.
#### Options
-| Name | Type | Required | Description |
+| Argument | Type | Required | Description |
|---|---|---|---|
-| `filter` | Callback `(fields) => Expression`, shorthand object, or (MongoDB) a `MongoFieldFilter` | Yes | The condition rows must satisfy. |
+| `filter` | A callback that calls an operator on a column, a shorthand object, or (MongoDB) a `MongoFieldFilter` | Yes | The condition rows must satisfy. |
#### Return type
| Return type | Example | Description |
|---|---|---|
-| `Collection` | `db.orm.public.User.where(...)` | A collection narrowed by the filter, chainable and awaitable through a terminal. |
+| `Collection` | `db.orm.public.User.where(...)` | A collection narrowed by the filter. Chain more methods on it, then run it with a read or write method. |
#### Examples
@@ -288,17 +324,17 @@ For a task-oriented guide to filtering, see [Reading data](/orm/fundamentals/rea
### `select()`
-Project a row down to a subset of scalar fields.
+Return only the scalar fields you name.
#### Remarks
- Available for PostgreSQL and MongoDB.
- On PostgreSQL, `select()` narrows the returned row shape at the type level: fields you didn't select are absent from the result type.
-- On MongoDB, `select()` narrows the projected fields at runtime, but does not strip fields from the returned row type at compile time. The returned type is unchanged, even though the query projects fewer fields.
+- On MongoDB, `select()` changes what the database returns but not the TypeScript type. Only the fields you named come back. The type still lists every field on the model. Read a field you did not name and you get `undefined`, because the returned document does not carry it.
#### Options
-| Name | Type | Required | Description |
+| Argument | Type | Required | Description |
|---|---|---|---|
| `...fields` | Field names (`string`) | Yes | One or more scalar field names to keep. |
@@ -314,7 +350,7 @@ Project a row down to a subset of scalar fields.
```typescript tab="PostgreSQL"
const summaries = await db.orm.public.User.select('id', 'email').orderBy((u) => u.email.asc()).all();
-// summaries[0] is { id, email } — no displayName
+// summaries[0] is { id, email }, with no displayName
```
```typescript tab="MongoDB"
@@ -327,18 +363,17 @@ Eagerly load a relation onto the returned rows.
#### Remarks
-- Available for PostgreSQL and MongoDB, with different capabilities.
-- On PostgreSQL, `include(relationName, refineFn?)` loads to-one and to-many relations, and the optional callback receives a nested `Collection` you can filter, order, limit, and aggregate (see [Refinements, aggregates, and combine](#refinements-aggregates-and-combine)).
-- On MongoDB, `include(relationName)` adds a `$lookup` for a reference relation and takes only a relation name; there is no refinement-callback overload.
-- On MongoDB, a looked-up sub-document's `_id` comes back as a raw driver `ObjectId`, not decoded to a hex string the way top-level `_id` fields are. Compare it with `String(...)`.
-- On MongoDB, passing a second argument to `include()` does **not** throw. JavaScript does not arity-check, so the extra argument is silently ignored and a plain, unrefined `$lookup` runs. This differs from calling a genuinely absent method, which throws `TypeError`.
+- Available for PostgreSQL and MongoDB, with differences noted below.
+- On PostgreSQL, `include(relationName, refineFn?)` loads both to-one and to-many relations. The optional callback receives the related rows as a collection, so you can filter, order, limit, and aggregate them. See [Refinements, aggregates, and combine](#refinements-aggregates-and-combine).
+- On MongoDB, wrap the `_id` of a related document loaded by `include()` in `String(...)` before you compare it. It comes back as an `ObjectId` object, while a document's own `_id` comes back as a hex string.
+- On MongoDB, `include()` loads a relation that is stored as a reference. It takes the relation name and nothing else, and TypeScript rejects a second argument.
#### Options
-| Name | Type | Required | Description |
+| Argument | Type | Required | Description |
|---|---|---|---|
| `relationName` | `string` | Yes | The relation to load. |
-| `refineFn` | `(relation: Collection) => Collection \| reducer` | No | PostgreSQL only. Refines the loaded relation. |
+| `refineFn` | A callback that returns the narrowed relation or an aggregate of it | No | PostgreSQL only. Narrows the loaded relation, or reduces it to one value. |
#### Return type
@@ -366,38 +401,42 @@ const users = await db.orm.public.User.include('posts').where({ id: aliceId }).a
```typescript
const posts = await db.orm.posts.include('author').where({ title: 'Hello world' }).all();
-// posts[0].author._id is a raw ObjectId — compare with String(posts[0].author._id)
+// posts[0].author._id is an ObjectId; compare it as String(posts[0].author._id)
```
For a task-oriented guide to loading related records, see [Relations and joins](/orm/fundamentals/relations-and-joins).
### Refinements, aggregates, and combine
-On PostgreSQL, the callback you pass to `include()` receives the nested relation as a full `Collection`. You can filter, order, and paginate it, reduce it to a single value, or `combine()` several sub-views into one shape.
+On PostgreSQL, the callback you pass to `include()` receives the related rows as a collection. This page calls that callback a refinement, which is the word in the heading above. You can filter, order, and paginate the related rows in it. You can also reduce them to a single value, or use `combine()` to return several results at once. To count or sum a whole model instead of a relation, see [Grouped aggregates](#grouped-aggregates), where `aggregate((agg) => ({ n: agg.count() }))` counts every row.
#### Remarks
-- PostgreSQL only. MongoDB's `include()` takes no callback, and `count`/`sum`/`avg`/`min`/`max`/`combine` do not exist on the Mongo collection at all (calling them throws `TypeError`).
-- `count`, `sum`, `avg`, `min`, `max` and `combine` are only callable **inside** an `include()` callback. Called anywhere else on a PostgreSQL collection, they throw `Error`.
-- Which fields each of those functions accepts comes from the aggregate map in your emitted contract, not from a fixed rule. On PostgreSQL, `sum()` and `avg()` take numeric columns, and `min()` and `max()` also take textual, date/time-string, and Temporal-backed columns, so `min('createdAt')` type-checks. A field the contract's map does not claim for that operation is a compile error at the call site.
-- `count()` returns a `number` and has a zero-argument form; `countBigInt()` is the lossless `bigint` variant. `sum()` over an integer column returns `number` and raises `RUNTIME.DECODE_FAILED` outside ±(2^53 − 1); `avg()` over an integer column returns a `float8` `number`; `sumBigInt(field)` and `avgDecimal(field)` are the lossless forms.
-- Over a to-many relation with no rows, `sum()` and `avg()` resolve to `null`, not `0`.
+- PostgreSQL only. On MongoDB, `include()` takes no callback, and `count`, `sum`, `avg`, `min`, `max`, and `combine` do not exist on a collection.
+- On PostgreSQL, those six are only callable **inside** an `include()` callback. Called anywhere else, they throw an error whose `code` is `ORM.INCLUDE_INVALID`.
+- `sum()` and `avg()` take a numeric column: an integer, a floating-point number, or a decimal. They also take an interval or a `Time` column, the time-of-day type. They do not take a date or a timestamp, and TypeScript rejects a date or timestamp field name here.
+- `min()` and `max()` take more: numeric and text columns, dates, times, timestamps, intervals, IP addresses, and text arrays. They do not take a boolean, a `uuid`, binary data, a bit string, or JSON.
+- `count()` returns a `number`. With no argument it counts rows; with a field name it counts rows where that field is not null. `countBigInt()` returns a `bigint` instead, for a count too large for a JavaScript `number`.
+- `sum()` over an integer column returns a `number`. A total too large for a `number` throws an error whose `code` is `RUNTIME.DECODE_FAILED`. Use `sumBigInt(field)` for totals that large. It returns a `bigint`.
+- `avg()` over an integer column returns a JavaScript floating-point `number`. `avgDecimal(field)` returns the exact average as a decimal string. Pass that string to a decimal library, or call `Number(...)` on it when a floating-point value is fine.
+- When a to-many relation has no rows, `sum()`, `avg()`, `min()`, and `max()` come back as `null`, not `0`.
+- `combine(shape)` takes an object. You choose the keys. Call `posts.combine({ ... })`, and inside the object write `posts` again for each value. The `posts` inside the object is the same collection the callback received, with nothing chained on it yet. A value that chains more methods on `posts` comes back as an array of rows. A value that is an aggregate, such as `posts.count()`, comes back as that single value.
#### Options
-| Name | Type | Required | Description |
+| Argument | Type | Required | Description |
|---|---|---|---|
-| `filter` / `orderBy` / `limit` / `offset` | Chained on the nested collection | No | Refine which related rows load. |
+| `where()` / `orderBy()` / `limit()` / `offset()` | Chained on the nested collection | No | Refine which related rows load. |
| `count()` | Aggregate, no arguments | No | Reduces the relation to a row count. |
-| `sum(field)` / `avg(field)` | Aggregate over a numeric field | No | Reduces the relation to a numeric aggregate; `null` over an empty relation. `sumBigInt` / `avgDecimal` are the lossless forms. |
-| `min(field)` / `max(field)` | Aggregate over a field the contract's aggregate map claims | No | Reduces the relation to a minimum/maximum, in the column's own type. |
-| `combine(shape)` | Object of sub-views and aggregates | No | Projects several refinements into one shape. |
+| `sum(field)` / `avg(field)` | Aggregate over a numeric field | No | Reduces the relation to one number. |
+| `min(field)` / `max(field)` | Aggregate over a sortable field | No | Reduces the relation to its smallest or largest value, in the column's own type. |
+| `combine(shape)` | Object of named collections and aggregates | No | Returns several results under one relation key. |
#### Return type
| Return type | Example | Description |
|---|---|---|
-| Refined relation, scalar, or combined shape | `include('posts', (p) => p.count())` | The relation is replaced by the refinement's result. |
+| The narrowed relation, a single value, or an object of both | `include('posts', (p) => p.count())` | The relation key on each row holds the callback's result instead of the related rows. |
#### Examples
@@ -425,7 +464,7 @@ const users = await db.orm.public.User.include('posts', (posts) => posts.count()
##### `sum()` / `avg()` over a numeric relation (PostgreSQL)
-This example uses the `Customer` / `Order` models from the aggregate example schema shown in [Grouped aggregates](#grouped-aggregates).
+This example uses the `Customer` and `Order` models, which are in the separate schema shown under [Grouped aggregates](#grouped-aggregates).
```typescript
const customers = await db.orm.public.Customer.include('orders', (orders) => orders.sum('amount'))
@@ -439,15 +478,6 @@ const avgCustomers = await db.orm.public.Customer.include('orders', (orders) =>
// avgCustomers[0].orders is 300
```
-A customer with no orders reduces to `null`:
-
-```typescript
-const rows = await db.orm.public.Customer.include('orders', (orders) => orders.sum('amount'))
- .where({ id: emptyCustomerId })
- .all();
-// rows[0].orders is null
-```
-
##### `combine()` multiple sub-views (PostgreSQL)
```typescript
@@ -462,8 +492,10 @@ const users = await db.orm.public.User.include('posts', (posts) =>
// users[0].posts.total is 2; users[0].posts.recent is a one-element array
```
-:::note[Which fields `min()` / `max()` accept]
-`min()` and `max()` are typed from the aggregate map in your emitted contract. On PostgreSQL that map claims numeric and textual columns, and columns stored as date/time strings or backed by `Temporal`. So `min('createdAt')` over a `DateTime` column type-checks and returns a `Temporal.Instant`. `sum()` and `avg()` are narrower: PostgreSQL declares no `SUM`/`AVG` over a date or time-stamp column, so those calls are a compile error, naming the operation that is unavailable for that input.
+:::note[`min()` and `max()` over a date column]
+`min('createdAt')` over a `DateTime` column compiles. It returns a `Temporal.Instant`, the standard JavaScript object for a point in time. Not every JavaScript engine has `Temporal` yet. Check with `typeof Temporal === 'undefined'`, and if yours does not, install `temporal-polyfill` and add `import 'temporal-polyfill/full/global'`.
+
+`sum('createdAt')` and `avg('createdAt')` do not compile.
:::
### `orderBy()`
@@ -473,13 +505,13 @@ Sort the result set.
#### Remarks
- Available for PostgreSQL and MongoDB, with different argument shapes.
-- On PostgreSQL, `orderBy()` takes a callback returning a per-column `.asc()`/`.desc()` directive, or an array of such callbacks for multiple sort keys.
-- On MongoDB, `orderBy()` takes a plain object spec, `{ field: 1 }` for ascending or `{ field: -1 }` for descending.
-- On PostgreSQL, **native** enum columns sort in the enum's declaration order, not alphabetically. A native `Priority` enum declared `Low`, `High`, `Urgent` sorts in that order under `.asc()`. A text-backed enum sorts by its stored value instead, so declaration order does not apply; for semantic ranking, use numeric enum values or an explicit ranking expression.
+- On PostgreSQL, `orderBy()` takes a callback that returns `.asc()` or `.desc()` on one column. Pass an array of such callbacks to sort by more than one column.
+- On MongoDB, `orderBy()` takes an object instead: `{ field: 1 }` sorts ascending and `{ field: -1 }` sorts descending.
+- The example schema declares `Priority` text-backed, so on PostgreSQL it sorts by its stored text: `'high'`, then `'low'`, then `'urgent'`. A native enum column sorts in the order its members are declared instead, so the same members in a `native_enum Priority` block sort `Low`, `High`, `Urgent`. Use a native enum when the sort order matters.
#### Options
-| Name | Type | Required | Description |
+| Argument | Type | Required | Description |
|---|---|---|---|
| `sort` | Callback `(fields) => f.field.asc() \| .desc()`, an array of such callbacks (PostgreSQL), or a `{ field: 1 \| -1 }` object (MongoDB) | Yes | The sort key(s) and direction(s). |
@@ -510,7 +542,7 @@ const byPriorityThenDate = await db.orm.public.Post.orderBy([
(p) => p.priority.asc(),
(p) => p.createdAt.asc(),
]).all();
-// a text-backed priority sorts by stored value ('high', 'low', 'urgent'); a native enum sorts in declaration order
+// Priority is text-backed here, so it sorts as 'high', 'low', 'urgent'
```
### `limit()`
@@ -523,7 +555,7 @@ Limit the number of returned rows.
#### Options
-| Name | Type | Required | Description |
+| Argument | Type | Required | Description |
|---|---|---|---|
| `count` | `number` | Yes | Maximum number of rows to return. |
@@ -556,7 +588,7 @@ Offset into the ordered result set.
#### Options
-| Name | Type | Required | Description |
+| Argument | Type | Required | Description |
|---|---|---|---|
| `count` | `number` | Yes | Number of rows to skip. |
@@ -584,12 +616,13 @@ Resume pagination from a known position.
#### Remarks
-- PostgreSQL only. MongoDB's collection has no `cursor()`; calling it throws `TypeError`.
-- `cursor()` is typed to require a preceding `orderBy()`. This is a **type-only** guard: at runtime there is no such check. If you bypass the type system and call `cursor()` without an `orderBy()`, the cursor value is silently ignored and the full unfiltered result is returned. It does not throw.
+- PostgreSQL only. `cursor()` does not exist on a MongoDB collection.
+- Always call `orderBy()` before `cursor()`. TypeScript rejects the call if you do not. Nothing checks this while the query runs, so without the `orderBy()` the cursor is ignored and every row comes back.
+- The cursor object must name every column the `orderBy()` sorts on. Leave one out and running the query throws an error whose `code` is `ORM.CURSOR_VALUE_MISSING`. With two sort columns, give both keys. The cursor compares the sort columns in order: first by the first column, then by the second for rows that tie on the first.
#### Options
-| Name | Type | Required | Description |
+| Argument | Type | Required | Description |
|---|---|---|---|
| `values` | Object of the `orderBy()` key(s) and their values | Yes | The position to resume after. |
@@ -615,15 +648,17 @@ const page2 = await db.orm.public.Post.orderBy((p) => p.createdAt.asc())
### `distinct()`
-Emit `SELECT DISTINCT` on the given fields.
+Remove duplicate rows, comparing only the fields you name.
#### Remarks
-- PostgreSQL only. MongoDB's collection has no `distinct()`; calling it throws `TypeError`.
+- PostgreSQL only. `distinct()` does not exist on a MongoDB collection.
+- You do not need `select()`. `distinct('priority')` keeps one whole row per distinct `priority` value, whatever the query returns.
+- Which of the tied rows it keeps is not defined. Call `orderBy()` first to choose, as [`distinctOn()`](#distincton) does.
#### Options
-| Name | Type | Required | Description |
+| Argument | Type | Required | Description |
|---|---|---|---|
| `...fields` | Field names (`string`) | Yes | The fields to deduplicate on. |
@@ -638,7 +673,8 @@ Emit `SELECT DISTINCT` on the given fields.
##### Deduplicate on a field (PostgreSQL)
```typescript
-const priorities = await db.orm.public.Post.select('priority').distinct('priority').all();
+const priorities = await db.orm.public.Post.distinct('priority').all();
+// priorities holds one whole Post row per distinct priority value
```
### `distinctOn()`
@@ -647,12 +683,13 @@ Keep the first row per key according to `orderBy()`.
#### Remarks
-- PostgreSQL only. MongoDB's collection has no `distinctOn()`; calling it throws `TypeError`.
-- Requires a prior `orderBy()` to be meaningful. Like `cursor()`, this ordering requirement is a type-level guard, not a runtime check.
+- PostgreSQL only. `distinctOn()` does not exist on a MongoDB collection.
+- Call `orderBy()` first, so that "the first row per key" means something. TypeScript rejects the call if you do not.
+- Start the `orderBy()` with the same columns you pass to `distinctOn()`, as the example does. Sort by anything else first and the query fails in the database.
#### Options
-| Name | Type | Required | Description |
+| Argument | Type | Required | Description |
|---|---|---|---|
| `...fields` | Field names (`string`) | Yes | The key field(s) to keep the first row of. |
@@ -670,29 +707,31 @@ Keep the first row per key according to `orderBy()`.
const latestPerUser = await db.orm.public.Post.orderBy([(p) => p.userId.asc(), (p) => p.createdAt.desc()])
.distinctOn('userId')
.all();
+// latestPerUser holds one post per userId: the newest one, because of the orderBy
```
### `variant()`
-Narrow a polymorphic (PostgreSQL) or discriminated (MongoDB) model to one variant.
+Narrow a model to one of the variants declared with `@@base`.
#### Remarks
- Available for PostgreSQL and MongoDB.
-- On PostgreSQL, variants use multi-table inheritance (a base table plus a variant table). This affects some mutations; see [`createAndCount()`](#createandcount) and [`upsert()`](#upsert).
-- On MongoDB, variants are a single document shape discriminated by a field value, in one collection.
+- Pass the variant's model name, such as `'Bug'`, not the string in its `@@base` attribute. On MongoDB you pass the model name too, even though the accessor before it is the collection name.
+- On PostgreSQL, a variant that sets its own `@@map`, as `Bug` and `Feature` do, is stored in its own table. Read [`createAndCount()`](#createandcount) and [`upsert()`](#upsert) before you write to such a variant.
+- On MongoDB, every variant is a document in the one collection, told apart by the field named in `@@discriminator`.
#### Options
-| Name | Type | Required | Description |
+| Argument | Type | Required | Description |
|---|---|---|---|
-| `variantName` | `string` | Yes | The variant to narrow to. |
+| `variantName` | `string` | Yes | The model name of the variant. |
#### Return type
| Return type | Example | Description |
|---|---|---|
-| `Collection` (variant-narrowed) | `db.orm.public.Task.variant('Bug')` | A collection scoped to the variant. |
+| `Collection`, narrowed to the variant | `db.orm.public.Task.variant('Bug')` | A collection holding only rows of that variant. |
#### Examples
@@ -706,9 +745,19 @@ const bugs = await db.orm.public.Task.variant('Bug').all();
const tutorials = await db.orm.posts.variant('Tutorial').all();
```
-## Read terminals
+## Read methods [#read-terminals]
-Read terminals resolve a query. `all()` and `first()` are available on both databases; `aggregate()` and `groupBy()` are PostgreSQL only and are documented under [Grouped aggregates](#grouped-aggregates). For a task-oriented walkthrough, see [Reading data](/orm/fundamentals/reading-data).
+A read method runs the query and gives you the rows. `all()` and `first()` are available on both databases. `aggregate()` and `groupBy()` are PostgreSQL only and are documented under [Grouped aggregates](#grouped-aggregates).
+
+There is no `count()` method on either database. On PostgreSQL, count with `aggregate((a) => ({ n: a.count() }))`. On MongoDB, counting takes two steps. `db.query`, the [pipeline builder](/orm/fundamentals/advanced-queries#mongodb-pipeline-builder), builds the pipeline, and `runtime.query(...)` runs it:
+
+```typescript
+const built = db.query.from('posts').count('total').build();
+const runtime = await db.runtime();
+const [counted] = await runtime.query(built); // counted.total is the count
+```
+
+For a task-oriented walkthrough, see [Reading data](/orm/fundamentals/reading-data).
### `all()`
@@ -718,12 +767,12 @@ Resolve the query to every matching row.
- Available for PostgreSQL and MongoDB.
- `all()` returns an [`AsyncIterableResult`](#asynciterableresult): you can `await` it to collect an array, or use `for await` to stream rows one at a time.
-- Re-`await`ing (or calling `.toArray()` on) an already-buffered result is safe: the cached array is returned, with no re-query and no throw.
-- **Switching consumption mode** on a consumed result throws `RUNTIME.ITERATOR_CONSUMED` (`already been consumed`). See [Single consumption and mode switching](#single-consumption-and-mode-switching).
+- `await`ing a result you have already `await`ed is safe. You get the same array back, and the query does not run again. Each result can be used one way only, so awaiting it and then looping it with `for await`, or the reverse, throws an error whose `code` is `RUNTIME.ITERATOR_CONSUMED`. See [Single consumption and mode switching](#single-consumption-and-mode-switching).
+- `orderBy()` is written differently on each database. See [`orderBy()`](#orderby).
#### Options
-`all()` takes no arguments.
+`all()` takes no required arguments.
#### Return type
@@ -731,6 +780,8 @@ Resolve the query to every matching row.
|---|---|---|
| `AsyncIterableResult` | `await db.orm.public.User.all()` | Awaitable to `Row[]`, or iterable with `for await` for streaming. |
+`Row` is the model's row type, from the emitted `contract.d.ts`.
+
#### Examples
##### Await to collect an array
@@ -752,7 +803,7 @@ for await (const user of db.orm.public.User.orderBy((u) => u.email.asc()).all())
```
```typescript tab="MongoDB"
-for await (const post of db.orm.posts.orderBy({ createdAt: 1 }).all()) {
+for await (const post of db.orm.posts.orderBy({ createdAt: 1 }).all()) { // 1 ascending, -1 descending
console.log(post.title);
}
```
@@ -771,9 +822,8 @@ Resolve the query to the first matching row, or `null` if none matches.
#### Remarks
- Available for PostgreSQL and MongoDB.
-- On PostgreSQL, `first()` accepts an inline filter: a shorthand object or a callback (`first((p) => p.priority.eq('urgent'))`).
-- On MongoDB, `first()` has no filter argument; filter first with `where(...)`, then call `first()`.
-- Returns `null` when no row matches.
+- On PostgreSQL, `first()` accepts an inline filter: a shorthand object or a callback (`first((p) => p.priority.eq('urgent'))`). An inline filter is added to any earlier `where()` call, and both have to match.
+- On MongoDB, `first()` takes no filter argument. Filter with `where(...)` first, then call `first()`.
#### Options
@@ -802,39 +852,37 @@ const urgentPost = await db.orm.public.Post.first((p) => p.priority.eq('urgent')
const bob = await db.orm.users.where({ name: 'Bob' }).first();
```
-##### No match returns `null`
-
-```typescript tab="PostgreSQL"
-const nobody = await db.orm.public.User.first({ email: 'nobody@example.com' });
-// null
-```
-
-```typescript tab="MongoDB"
-const nobody = await db.orm.users.where({ email: 'nobody@example.com' }).first();
-// null
-```
-
For Prisma ORM 7 users, `findUnique` and `findFirst` map onto `first()`:
```diff
- const alice = await prisma.user.findUnique({ where: { email } });
+ const alice = await db.orm.public.User.first({ email });
+- const alice = await prisma.user.findUniqueOrThrow({ where: { email } });
++ const alice = await db.orm.public.User.where({ email }).all().firstOrThrow();
```
+`first()` returns `null` on no match and does not check that only one row matched. `findUniqueOrThrow` and `findFirstOrThrow` both map onto `all().firstOrThrow()`, as the last line above shows.
+
+`firstOrThrow()` is a method on the result that `all()` gives you, so calling it is not a second use of that result. It reads every matching row into memory and returns the first one, and it throws an error whose `code` is `RUNTIME.NO_ROWS` when nothing matched.
+
### Custom `Collection` subclass
-On PostgreSQL you can subclass `Collection` to attach domain methods, and register the subclass when you create the client.
+On PostgreSQL you can subclass `Collection` to add your own methods to a model, and register the subclass when you create the client.
#### Remarks
-- PostgreSQL only. The MongoDB ORM has no equivalent collection-subclassing mechanism.
-- Registering custom collections requires building the client with the `orm(...)` factory, not the `postgres()` client's built-in `.orm` facet. The `postgres()` client has no `collections` option, so its facet always resolves models to the base `Collection`. Pass a `collections` map to `orm({ runtime, context, collections })` to register subclasses.
+- PostgreSQL only. You cannot subclass a collection on MongoDB.
+- `db.orm` cannot use your subclass. Call the `orm(...)` function with a `collections` object to get a second accessor with the same methods as `db.orm.public`. Use that accessor for the models you subclassed, and keep `db.orm.public` for every other model. Your existing `db.orm.public.X` calls keep working.
+- `client` in the example below is what `postgres(...)` returns, the same object [Setting up the client](#setting-up-the-client) calls `db`. The example creates one so that it runs on its own.
+- `Collection` takes two type arguments: your contract type, and the name of the model this collection is for.
+- A method that returns `this.variant('Bug')` gives back a collection narrowed to the `Bug` model. It is chainable, so you finish it with `all()`, `first()`, or any other method.
#### Examples
##### Register a subclass with a domain method (PostgreSQL)
```typescript
+// postgres() comes from /runtime; Collection and orm() come from /orm-client.
import postgres from '@prisma/orm-postgres/runtime';
import { Collection, orm } from '@prisma/orm-postgres/orm-client';
import type { Contract } from './contract.d';
@@ -852,25 +900,30 @@ class TaskCollection extends Collection {
const client = postgres({ contractJson, url: process.env.DATABASE_URL });
const runtime = await client.connect();
-// The orm() factory accepts a `collections` map; the client's `.orm` facet does not.
-const db = orm({
+const tasks = orm({
runtime,
context: client.context,
collections: { Task: TaskCollection },
-}).public;
+}).public; // .public is the PostgreSQL schema
-const bugs = await db.Task.bugs().all();
-const features = await db.Task.features().all();
+const bugs = await tasks.Task.bugs().all();
+const features = await tasks.Task.features().all();
```
-## Mutation terminals
+## Write methods [#mutation-terminals]
+
+A write method changes the database. `create`, `createAll`, `createAndCount`, `update`, `updateAll`, `updateAndCount`, `delete`, `deleteAll`, `deleteAndCount`, and `upsert` are available on both databases. The differences between the two are listed under each method.
+
+For Prisma ORM 7 users: `createMany` is `createAll()` or `createAndCount()`, `updateMany` is `updateAll()` or `updateAndCount()`, `deleteMany` is `deleteAll()` or `deleteAndCount()`, and `count` is [`aggregate()`](#grouped-aggregates) on PostgreSQL, or the two steps shown under [Read methods](#read-terminals) on MongoDB. There is no `skipDuplicates` option.
-Mutations write to the database. `create`, `createAll`, `createAndCount`, `update`, `updateAll`, `updateAndCount`, `delete`, `deleteAll`, `deleteAndCount`, and `upsert` are available on both databases, with several behavioral differences called out per method. For a task-oriented walkthrough, see [Writing data](/orm/fundamentals/writing-data); for grouping several writes into one unit, see [Transactions](/orm/fundamentals/transactions).
+To catch one of the errors named below, wrap the call in `try` and `catch`, then compare `error.code` with the string given here. For a task-oriented walkthrough, see [Writing data](/orm/fundamentals/writing-data). On MongoDB, a write method rejects a chain that already has `orderBy()`, `limit()`, or `offset()` on it, and every write except `updateAll()` and `deleteAll()` also rejects `include()`. Both throw an error whose `code` is `ORM.OPERATION_UNSUPPORTED`. For grouping several writes into one unit, see [Transactions](/orm/fundamentals/transactions).
-:::warning[`where()` enforcement differs sharply between databases]
-Always call `where()` before `update`, `updateAll`, `updateAndCount`, `delete`, `deleteAll`, `deleteAndCount`, or `upsert`. On **MongoDB** all seven enforce this at runtime: calling them with no filter throws `Error: () requires a .where() filter`. On **PostgreSQL** only `update()` and `delete()` are typed to require a prior `where()`, and even there it is a **type-only** guard with no runtime check. If you bypass the type system, `update()` and `delete()` do not throw and do not mass-mutate; they narrow to a single row by identity (a `SELECT ... LIMIT 1` over the current, possibly empty, filters, then act on that one row). The `*All`/`*Count` variants on PostgreSQL compile with no `WHERE` clause and affect every row. Always call `where()` first.
+:::warning[Filter before you update or delete]
+Always call `where()` before an update or a delete. On MongoDB the six update and delete methods and `upsert()` check for a filter and throw an error whose `code` is `ORM.WHERE_MISSING`, with a message naming the method you called, such as `updateAll() requires a .where() filter. Call .where() before .updateAll()`. On PostgreSQL, these methods do not compile without a `where()`. Nothing checks at run time. If a call without a filter does reach the database, `update()` and `delete()` change a single row and you cannot predict which one, and `updateAll()` and `deleteAll()` change every row.
:::
+The examples below use `aliceId`, `bobId`, `carolId`, `postId`, `tagId`, `tutorialId`, and `userId` for the ids of rows that already exist. Substitute your own.
+
### `create()`
Insert a single row and return it.
@@ -878,14 +931,19 @@ Insert a single row and return it.
#### Remarks
- Available for PostgreSQL and MongoDB.
-- On MongoDB, `create()` returns the input data plus the server-assigned `_id`. It does not re-read the stored document, unlike PostgreSQL's `RETURNING`-backed `create()`.
-- On PostgreSQL, `create()` supports nested `create()` on a child-owned relation and nested `connect()` on a parent-owned relation, all within one transaction. These nested mutators are type-checked but their runtime behavior on MongoDB is unverified on the current test contract.
+- On PostgreSQL, you may leave out any field your contract gives a default, including the id. Passing an explicit id is accepted. Nullable fields can be left out too. Every other field is required.
+- On MongoDB, `_id` is the only field you can leave out. Give every other field a value, and write `null` for a field you want empty.
+- On MongoDB, `create()` returns the values you passed plus the `_id` the server assigned, not the stored document. Read the row back if you need a value the database filled in. On PostgreSQL the returned row is read from the database already.
+- On PostgreSQL, `create()` can create or link related rows in the same transaction. Nest `create()` on the side that does not hold the foreign key, and `connect()` on the side that does.
+- Nested `create()` and `connect()` are not available on MongoDB.
#### Options
| Name | Type | Required | Description |
|---|---|---|---|
-| `data` | Object of field values, optionally with relation mutators (`create`, `connect`) | Yes | The row to insert. |
+| The row's fields | Object of field values | Yes | The row to insert. Pass the object as the only argument. There is no `data` wrapper. |
+
+On PostgreSQL, that object can also carry a `create` or `connect` callback for a related row, as shown below. `create()` and `connect()` each take one object or an array of objects, on any relation. `disconnect()` takes an array, and on a many-to-many relation the array is required.
#### Return type
@@ -912,24 +970,21 @@ const user = await db.orm.users.create({
// user._id is the server-assigned id
```
-##### Nested `create()` on a child-owned relation (PostgreSQL)
+##### Nested `create()` on the side without the foreign key (PostgreSQL) [#nested-create-on-a-child-owned-relation-postgresql]
```typescript
const author = await db.orm.public.User.create({
- id: '00000000-0000-0000-0000-000000000099',
email: 'dana@example.com',
displayName: 'Dana',
kind: 'user',
- posts: (posts) =>
- posts.create([{ id: '10000000-0000-0000-0000-000000000099', title: 'Dana post one' }]),
+ posts: (posts) => posts.create([{ title: 'Dana post one' }]),
});
```
-##### Nested `connect()` on a parent-owned relation (PostgreSQL)
+##### Nested `connect()` on the side with the foreign key (PostgreSQL) [#nested-connect-on-a-parent-owned-relation-postgresql]
```typescript
const post = await db.orm.public.Post.create({
- id: '10000000-0000-0000-0000-000000000098',
title: 'Connected to Bob',
user: (user) => user.connect({ id: bobId }),
});
@@ -950,12 +1005,13 @@ Insert multiple rows and return them.
- Available for PostgreSQL and MongoDB.
- Returns an [`AsyncIterableResult`](#asynciterableresult): `await` for an array, or `for await` to stream inserted rows.
+- On PostgreSQL every row is inserted by one statement before the first row reaches you. Stopping the loop early does not undo an insert.
#### Options
| Name | Type | Required | Description |
|---|---|---|---|
-| `data` | Array of row objects | Yes | The rows to insert. |
+| The rows | Array of field objects | Yes | The rows to insert, passed as the only argument. There is no `data` wrapper. |
#### Return type
@@ -978,29 +1034,22 @@ const created = await db.orm.users.createAll([
]);
```
-##### Stream inserted rows (PostgreSQL)
-
-```typescript
-for await (const tag of db.orm.public.Tag.createAll([{ label: 'gamma' }, { label: 'delta' }])) {
- console.log(tag.label);
-}
-```
-
### `createAndCount()`
-Insert rows without materializing them, returning the count.
+Insert rows and return how many were inserted, without reading them back.
#### Remarks
- Available for PostgreSQL and MongoDB.
-- On PostgreSQL, `createAndCount()` is **not supported on a multi-table-inheritance variant**. The base and variant rows live in separate tables, so a single `RETURNING`-less `INSERT` can't populate both. It throws `Error: createAndCount() is not supported for MTI variant "" ... Use createAll() instead.`
-- On MongoDB, `createAndCount()` on a discriminated variant works normally. Mongo variants are one document shape in one collection, so there is no split-table restriction.
+- On PostgreSQL, `createAndCount()` does not work on a variant that is stored in its own table, meaning its `@@map` names a different table than its base model does. `Bug` in the [example schema](#example-schema) is one: it maps to `bug` while `Task` maps to `task`. Use `createAll()` instead.
+- Calling it on such a variant throws an error whose `code` is `ORM.OPERATION_UNSUPPORTED`, with the message `createAndCount() is not supported for MTI variant "Bug" on model "Task". Use createAll() instead.` MTI in the message means multi-table inheritance, the variant stored in its own table described above.
+- On MongoDB, a variant is one document in one collection, so `createAndCount()` works on a variant as usual.
#### Options
| Name | Type | Required | Description |
|---|---|---|---|
-| `data` | Array of row objects | Yes | The rows to insert. |
+| The rows | Array of field objects | Yes | The rows to insert, passed as the only argument. There is no `data` wrapper. |
#### Return type
@@ -1028,7 +1077,7 @@ const inserted = await db.orm.posts.variant('Tutorial').createAndCount([
duration: 10,
},
]);
-// 1 — no split-table restriction on Mongo variants
+// 1
```
### `update()`
@@ -1038,17 +1087,26 @@ Update the matched row and return it, or `null` if none matches.
#### Remarks
- Available for PostgreSQL and MongoDB. Requires a prior `where()` (see the warning at the top of this section).
-- On MongoDB, `update()` accepts a data object **or** a field-operations callback (`(t) => [t.field.inc(5), t.other.set(...)]`). The callback groups operators into one `findOneAndUpdate`; see [Field update operations](#field-update-operations).
-- On PostgreSQL, `update()` accepts a data object only. It has **no** field-operations callback overload: passing a function is silently a no-op (it resolves to `null`), not an error. A bare function has no enumerable own properties, so no column is targeted.
-- On PostgreSQL, `update()` supports nested `connect()` (relink a parent-owned foreign key) and nested `disconnect()` (unlink a many-to-many row without deleting it).
-- On MongoDB, `update()` is backed by `findOneAndUpdate` and is an atomic single-document update.
-- Returns `null` when no row matches.
+- On MongoDB, `update()` takes an object of field values, or a callback that returns field operations, such as `(p) => [p.content.set('Rewritten')]`. See [Field update operations](#field-update-operations).
+- On MongoDB, the update input covers the base model's fields only. Putting a field declared on a variant in the update, such as `duration` on `Tutorial`, makes TypeScript complain even though the update runs correctly. Put `// @ts-expect-error` on the line directly above the line TypeScript flags. When the complaint goes away, TypeScript reports the comment itself as unused, so delete it then. The same is true of `updateAll()` and `updateAndCount()`.
+
+ ```typescript
+ // @ts-expect-error duration is a Tutorial field, not a Post field
+ .update((t) => [t.duration.inc(10)]);
+ ```
+
+- Take care on PostgreSQL: `update()` takes an object of field values only, and passing a function instead writes nothing, reports nothing, and returns `null`.
+- On PostgreSQL, `update()` can also relink related rows. `connect()` points the foreign key at a different row, and `disconnect()` removes a many-to-many link without deleting either row.
+- Returns `null` when no row matches. On PostgreSQL it also returns `null` when the object you pass is empty and has no keys at all. An object whose values already match the row is different: that update runs and returns the row.
+- On PostgreSQL, [`select()`](#select) and [`include()`](#include) before `.update()` choose which fields and relations come back on the returned row. They change nothing about what is written.
#### Options
| Name | Type | Required | Description |
|---|---|---|---|
-| `data` | Data object, or (MongoDB) a field-operations callback | Yes | The changes to apply. On PostgreSQL, relation mutators (`connect`, `disconnect`) may be included. |
+| The changes | Object of field values, or on MongoDB a field-operations callback | Yes | What to set on the matched row, passed as the only argument. There is no `data` wrapper. |
+
+On PostgreSQL that object can also carry `connect` or `disconnect` for a related row, as shown below.
#### Return type
@@ -1072,9 +1130,8 @@ const updated = await db.orm.users.where({ _id: bobId }).update({ bio: 'Now with
```typescript
const updated = await db.orm.posts
- .variant('Tutorial')
- .where({ _id: tutorialId })
- .update((t) => [t.duration.inc(5), t.content.set('Updated content')]);
+ .where({ _id: postId })
+ .update((p) => [p.title.set('Updated title'), p.content.set('Rewritten')]);
```
##### Nested `connect()` relinks a foreign key (PostgreSQL)
@@ -1094,7 +1151,7 @@ const updated = await db.orm.public.Post.where({ id: postId })
.update({
tags: (tag) => tag.disconnect([{ id: tagId }]),
});
-// only the junction row is removed; the Tag row itself still exists
+// only the join-table row is removed. The Tag row itself still exists
```
For Prisma ORM 7 users, `update()` moves the filter into `where()` and drops the `data` wrapper:
@@ -1110,15 +1167,16 @@ Update every matching row and collect the results.
#### Remarks
-- Available for PostgreSQL and MongoDB. Call `where()` first: MongoDB enforces this at runtime; PostgreSQL does not (see the section warning above: without a filter these affect every row).
-- On PostgreSQL, `updateAll()` is a single `UPDATE ... RETURNING` statement, and is atomic.
-- On MongoDB, `updateAll()` is **not atomic**. It (1) reads the matching `_id`s, (2) runs an update against the original filter, then (3) re-reads by those captured `_id`s. A concurrent write between these steps could change which documents match or their values; the result reflects the `_id` set from step 1, not one atomic snapshot.
+- Available for PostgreSQL and MongoDB. Call `where()` first. Without a filter this changes every row. See the warning at the top of this section.
+- On PostgreSQL, `updateAll()` runs as one statement, so every matching row changes together.
+- On MongoDB, `updateAll()` is not a single operation, and two things follow. If a document begins matching your filter while the call is running, it is updated but not included in the returned rows. If someone else changes a matched document while the call is running, you get their version back, not yours.
+- Prisma ORM has no `db.transaction()` on MongoDB, so you cannot avoid this from the client. To control it, share one `MongoClient` with the MongoDB driver and group the writes in one of the driver's own sessions, shown under [Transactions on MongoDB](/orm/fundamentals/transactions#transactions-on-mongodb).
#### Options
| Name | Type | Required | Description |
|---|---|---|---|
-| `data` | Data object, or (MongoDB) a field-operations callback | Yes | The changes to apply to every matched row. |
+| The changes | Object of field values, or on MongoDB a field-operations callback | Yes | What to set on every matched row, passed as the only argument. There is no `data` wrapper. |
#### Return type
@@ -1136,7 +1194,7 @@ const updated = await db.orm.public.Post.where({ userId: aliceId }).updateAll({
```typescript tab="MongoDB"
const updated = await db.orm.users.where({ role: 'author' }).updateAll({ role: 'admin' });
-// non-atomic: ids are captured, updated, then re-read
+// see the remark above: this is not one operation
```
### `updateAndCount()`
@@ -1145,14 +1203,14 @@ Update every matching row and return the count.
#### Remarks
-- Available for PostgreSQL and MongoDB. Call `where()` first: MongoDB enforces this at runtime; PostgreSQL does not (see the section warning above: without a filter these affect every row).
-- On MongoDB, `updateAndCount()` accepts a data object or a field-operations callback.
+- Available for PostgreSQL and MongoDB. Call `where()` first. Without a filter this changes every row. See the warning at the top of this section.
+- On MongoDB the count leaves out documents that already held the new values. On PostgreSQL it counts every matched row.
#### Options
| Name | Type | Required | Description |
|---|---|---|---|
-| `data` | Data object, or (MongoDB) a field-operations callback | Yes | The changes to apply to every matched row. |
+| The changes | Object of field values, or on MongoDB a field-operations callback | Yes | What to set on every matched row, passed as the only argument. There is no `data` wrapper. |
#### Return type
@@ -1172,28 +1230,17 @@ const count = await db.orm.public.Post.where({ userId: carolId }).updateAndCount
const count = await db.orm.users.where({ role: 'author' }).updateAndCount({ role: 'admin' });
```
-##### Field-operations callback (MongoDB)
-
-```typescript
-const count = await db.orm.posts
- .variant('Tutorial')
- .where({ _id: tutorialId })
- .updateAndCount((t) => [t.duration.mul(2)]);
-```
-
### `delete()`
Remove the matched row and return it, or `null` if none matches.
#### Remarks
-- Available for PostgreSQL and MongoDB. Requires a prior `where()` (see the section warning above).
-- On MongoDB, `delete()` is backed by `findOneAndDelete`.
-- Returns `null` when no row matches.
+- Available for PostgreSQL and MongoDB. Requires a prior `where()` (see the section warning above). Returns `null` when no row matches.
#### Options
-`delete()` takes no arguments; filter with `where()` first.
+`delete()` takes no required arguments. Filter with `where()` first.
#### Return type
@@ -1227,11 +1274,12 @@ Remove every matching row and collect the results.
#### Remarks
-- Available for PostgreSQL and MongoDB. Call `where()` first: MongoDB enforces this at runtime; PostgreSQL does not (see the section warning above: without a filter these affect every row).
+- Available for PostgreSQL and MongoDB. Call `where()` first. Without a filter this deletes every row. See the warning at the top of this section.
+- Every matching row is deleted before the first row reaches you. Stopping the loop early does not save any of them.
#### Options
-`deleteAll()` takes no arguments; filter with `where()` first.
+`deleteAll()` takes no required arguments. Filter with `where()` first.
#### Return type
@@ -1257,12 +1305,11 @@ Remove every matching row and return the count.
#### Remarks
-- Available for PostgreSQL and MongoDB. Call `where()` first: MongoDB enforces this at runtime; PostgreSQL does not (see the section warning above: without a filter these affect every row).
-- Returns `0` when no row matches.
+- Available for PostgreSQL and MongoDB. Call `where()` first. Without a filter this deletes every row. See the warning at the top of this section. Returns `0` when no row matches.
#### Options
-`deleteAndCount()` takes no arguments; filter with `where()` first.
+`deleteAndCount()` takes no required arguments. Filter with `where()` first.
#### Return type
@@ -1288,19 +1335,24 @@ Insert a row if none matches, otherwise update the existing row.
#### Remarks
-- Available for PostgreSQL and MongoDB. Requires a prior `where()` on MongoDB (see the section warning).
-- On PostgreSQL, `upsert()` uses `conflictOn` to name the unique target. The `update` side is a plain data object.
-- On MongoDB, the `update` side may be a data object **or** a field-operations callback. On insert, `update` fields apply via `$set` and the remaining create-only fields via `$setOnInsert`, so `update` values win over `create` values on any overlapping field.
-- On PostgreSQL, `upsert()` is **not supported on a multi-table-inheritance variant** (same restriction as [`createAndCount()`](#createandcount)): it throws `Error: upsert() is not supported for MTI variant "" ...`.
-- On MongoDB, the returned `_id` on the update path comes back as a raw driver `ObjectId`, not a decoded hex string. Compare it with `String(...)`.
+- On MongoDB, call `where()` first. The filter decides which document is updated.
+- On PostgreSQL, `upsert()` never reads the filters. A `where()` before it is ignored, without an error. `conflictOn` decides which row counts as already existing.
+- `conflictOn` names the unique column that decides insert or update, such as `conflictOn: { label: 'typescript' }`. Only the column name is used. The value is required by the type and is then ignored, so pass any value of the right type.
+- For a unique constraint on several columns together, name every one of those columns, as in `conflictOn: { orgId: '', email: '' }`. The set you name has to match one `@@unique` constraint.
+- When a row is inserted on PostgreSQL, the `create` side is used as you wrote it.
+- When a row is inserted on MongoDB, a field that appears in both `create` and `update` takes the `update` value.
+- On MongoDB, the `update` side takes an object of field values or a field-operations callback.
+- On PostgreSQL, `upsert()` does not work on a variant stored in its own table, the same restriction as [`createAndCount()`](#createandcount). It throws an error whose `code` is `ORM.OPERATION_UNSUPPORTED`, with the message `upsert() is not supported for MTI variant "Bug" on model "Task". Use createAll() instead.`
+- There is no upsert for such a variant. Read the row with `first()`, then call `create()` or `update()`.
+- On MongoDB, the `_id` on the returned row is a hex string, not the driver's `ObjectId`.
#### Options
| Name | Type | Required | Description |
|---|---|---|---|
-| `create` | Data object | Yes | The row to insert if none matches. |
-| `update` | Data object, or (MongoDB) a field-operations callback | Yes | The changes to apply if a row matches. |
-| `conflictOn` | Object naming the unique target (PostgreSQL) | PostgreSQL | The conflict target that decides insert vs. update. |
+| `create` | Object of field values | Yes | The row to insert if none matches. |
+| `update` | Object of field values, or on MongoDB a field-operations callback | Yes | The changes to apply if a row matches. |
+| `conflictOn` | Object naming the unique column or columns, PostgreSQL only | No | What decides insert or update. Omit to use the primary key. |
#### Return type
@@ -1313,16 +1365,17 @@ Insert a row if none matches, otherwise update the existing row.
##### Insert or update (PostgreSQL)
```typescript
-// Insert path: no existing row has label 'brand-new', so the create side wins.
+// Insert: no existing row has label 'brand-new', so the row is inserted
+// exactly as the create side describes it.
const inserted = await db.orm.public.Tag.upsert({
- create: { id: '30000000-0000-0000-0000-000000000099', label: 'brand-new' },
+ create: { label: 'brand-new' },
update: { label: 'brand-new-updated' },
- conflictOn: { label: 'brand-new' },
+ conflictOn: { label: 'brand-new' }, // only the column name `label` is used
});
-// Update path: 'typescript' already exists, so the update runs against it.
+// Update: a row with label 'typescript' already exists, so the update runs against it.
const updated = await db.orm.public.Tag.upsert({
- create: { id: '30000000-0000-0000-0000-000000000098', label: 'typescript' },
+ create: { label: 'typescript' },
update: { label: 'typescript-renamed' },
conflictOn: { label: 'typescript' },
});
@@ -1341,7 +1394,7 @@ const user = await db.orm.users.where({ email: 'newperson@example.com' }).upsert
},
update: { bio: 'set on upsert' },
});
-// on insert, update fields win over create fields on overlap
+// on insert, bio is 'set on upsert': the `update` value wins on MongoDB
```
##### Field-operations callback on the update side (MongoDB)
@@ -1359,11 +1412,11 @@ const post = await db.orm.posts
difficulty: 'beginner',
duration: 0,
},
- update: (t) => [t.duration.inc(1)],
+ update: (t) => [t.content.set('Updated content')],
});
```
-For Prisma ORM 7 users, `upsert()` replaces the `where` conflict target with `conflictOn` (PostgreSQL):
+For Prisma ORM 7 users, `upsert()` replaces the `where` argument with `conflictOn` (PostgreSQL):
```diff
- const tag = await prisma.tag.upsert({
@@ -1373,14 +1426,14 @@ For Prisma ORM 7 users, `upsert()` replaces the `where` conflict target with `co
- });
+ const tag = await db.orm.public.Tag.upsert({
+ update: { label: 'typescript-renamed' },
-+ create: { id, label: 'typescript' },
++ create: { label: 'typescript' },
+ conflictOn: { label: 'typescript' },
+ });
```
## Grouped aggregates
-PostgreSQL supports aggregation over a result set, both flat (`aggregate()`) and grouped (`groupBy().aggregate()`), with `having()` to filter groups. MongoDB has no equivalent: `aggregate()` and `groupBy()` do not exist on the Mongo collection and calling them throws `TypeError`.
+These methods exist only on PostgreSQL collections. `aggregate()` treats every row the query matches as one group. `groupBy().aggregate()` aggregates per group, and `having()` filters those groups. A MongoDB model's collection has none of them. To aggregate on MongoDB, use the pipeline builder, which is Prisma ORM's API for MongoDB's aggregation pipeline. It is described in [MongoDB: Pipeline builder](/orm/fundamentals/advanced-queries#mongodb-pipeline-builder).
The examples below use a separate `Customer` / `Order` schema, where `Order.amount` is a numeric column:
@@ -1402,8 +1455,9 @@ model Customer {
model Order {
id Uuid @id @default(uuid())
customerId Uuid
+ channel String
amount Int
- quantity Int
+ quantity Int?
placedAt DateTime @default(now())
customer Customer @relation(fields: [customerId], references: [id])
@@ -1416,45 +1470,61 @@ model Order {
### `aggregate()`
-Compute aggregates over the current result set.
+Compute aggregates over the rows the query matches. `aggregate()` runs the query, so you `await` it directly instead of calling `.all()`.
#### Remarks
-- PostgreSQL only. Calling `aggregate()` on a Mongo collection throws `TypeError`.
-- `count()` needs no field argument and always resolves to a number (`0` over an empty set).
-- `sum()` and `avg()` resolve to `null`, not `0`, over an empty result set. This is standard SQL: aggregating zero rows with `SUM`/`AVG` yields `NULL`. Their TypeScript return type is `number | null` for this reason.
+- PostgreSQL only. Not available on MongoDB.
+- `count()` counts rows and returns a number, `0` when no rows match.
+- `count(field)` counts the rows where that field is not null.
+- To count or sum the related rows of each parent row, use the `include()` callback instead; see [Refinements, aggregates, and combine](#refinements-aggregates-and-combine).
+- `sum()`, `avg()`, `min()`, and `max()` return `null`, not `0`, when no rows match. Handle the `null`.
+- A `limit()` or `offset()` earlier in the chain is applied first. The aggregate then covers only the rows they kept.
+- What `sum()` and `avg()` give you back depends on the column type, as the table below shows.
+
+| Column type | `sum()` returns | `avg()` returns | Exact variant |
+|---|---|---|---|
+| `Int`, `BigInt` | `number \| null` | `number \| null` | `sumBigInt()`, `avgDecimal()` |
+| `Decimal` | `string \| null` | `string \| null` | `avgDecimal()` |
+
+Use `sumBigInt()` when the total can pass `Number.MAX_SAFE_INTEGER`. A `sum()` whose total passes it throws an error whose `code` is `RUNTIME.DECODE_FAILED`, thrown when the query result is read. Use `avgDecimal()` when you need the exact mean as a string, such as `'10.3333333333333333'`. Over a `Decimal` column `sum()` and `avg()` are already exact, and `sumBigInt()` is not available.
#### Options
-| Name | Type | Required | Description |
+| Argument | Type | Required | Description |
|---|---|---|---|
-| `selector` | Callback `(agg) => ({ alias: agg.fn(...) })` | Yes | The aggregates to compute, keyed by output alias. |
+| `selector` | Callback `(agg) => ({ total: agg.sum('amount') })` | Yes | The aggregates to compute. Each key you return becomes a key of the result object. |
-The selector's `agg` exposes `count()`, `sum(field)`, `avg(field)`, `min(field)`, `max(field)`.
+The callback's `agg` argument gives you `count()`, `count(field)`, `sum(field)`, `avg(field)`, `min(field)`, and `max(field)`, plus `countBigInt()`, `sumBigInt(field)`, and `avgDecimal(field)`. `agg.countBigInt()` counts the same rows as `agg.count()` and returns a `bigint`.
#### Return type
| Return type | Example | Description |
|---|---|---|
-| Object of aggregate results | `{ total: 10 }` | One object with the requested aliases. `sum`/`avg` are `number \| null`. |
+| Object | `{ total: 10 }` | One key per key you returned from the callback. `sum`, `avg`, `min`, and `max` can be `null`. |
#### Examples
##### Count (PostgreSQL)
```typescript
-const stats = await db.orm.public.Order.aggregate((agg) => ({ total: agg.count() }));
-// { total: 10 }
+const stats = await db.orm.public.Order.aggregate((agg) => ({
+ total: agg.count(),
+ withQuantity: agg.count('quantity'), // rows where quantity is not null
+}));
+// { total: 10, withQuantity: 8 }
```
##### Sum and average (PostgreSQL)
```typescript
-const stats = await db.orm.public.Order.where({ customerId: acmeId }).aggregate((agg) => ({
+const stats = await db.orm.public.Order.where({
+ customerId: '20000000-0000-0000-0000-000000000001',
+}).aggregate((agg) => ({
totalAmount: agg.sum('amount'),
avgAmount: agg.avg('amount'),
}));
-// { totalAmount: 1500, avgAmount: 300 }
+// { totalAmount: 900, avgAmount: 300 }
```
##### Min and max (PostgreSQL)
@@ -1467,6 +1537,17 @@ const stats = await db.orm.public.Order.aggregate((agg) => ({
// { cheapest: 10, priciest: 500 }
```
+##### Counts and totals past JavaScript's precision limit (PostgreSQL)
+
+```typescript
+const exact = await db.orm.public.Order.aggregate((agg) => ({
+ rows: agg.countBigInt(),
+ total: agg.sumBigInt('amount'),
+ average: agg.avgDecimal('amount'),
+}));
+// { rows: 10n, total: 1500n, average: '150.0000000000000000' }
+```
+
##### `null` over an empty set (PostgreSQL)
```typescript
@@ -1491,20 +1572,24 @@ Group rows by one or more fields, then aggregate per group.
#### Remarks
-- PostgreSQL only. Calling `groupBy()` on a Mongo collection throws `TypeError`.
-- `groupBy(...fields)` returns a `GroupedCollection`. Call `.aggregate(...)` on it to produce one row per distinct group key, carrying both the key field(s) and the aggregate aliases.
+- PostgreSQL only. Not available on MongoDB.
+- Pass field names as separate arguments: `groupBy('customerId', 'channel')`.
+- `groupBy(...)` returns a `GroupedCollection`. You cannot `await` it. Call `.aggregate(...)` on it, which runs the query and gives you one row per group. Each row holds the grouping fields, typed as they are on your model, plus the keys you returned from the aggregate callback.
+- Chain order: `where(...)` comes before `groupBy(...)` and filters the rows that get grouped. After `groupBy(...)` come `having(...)` and `orderBy(...)`, then `limit(...)` and `offset(...)`. `aggregate(...)` comes last.
+- `orderBy(...)` on a grouped collection sorts the groups by a field you grouped on. You cannot sort groups by an aggregate value. To do that, write the query with the SQL query builder instead. See [Advanced queries](/orm/fundamentals/advanced-queries).
+- `limit(n)` and `offset(n)` take a slice of the groups, and both require an `orderBy(...)` before them. Without one they are a TypeScript error.
#### Options
-| Name | Type | Required | Description |
+| Argument | Type | Required | Description |
|---|---|---|---|
-| `...fields` | Field names (`string`) | Yes | The grouping key(s). |
+| `...fields` | One or more field names (`string`) | Yes | The fields to group by. |
#### Return type
| Return type | Example | Description |
|---|---|---|
-| `GroupedCollection` | `db.orm.public.Order.groupBy('customerId')` | A grouped collection, resolved via `aggregate()`. |
+| `GroupedCollection` | `db.orm.public.Order.groupBy('customerId')` | Not a promise. Call `.aggregate(...)`. |
#### Examples
@@ -1518,6 +1603,15 @@ const perCustomer = await db.orm.public.Order.groupBy('customerId').aggregate((a
// one row per customer, each { customerId, orderCount, totalAmount }
```
+##### Sort and page the groups (PostgreSQL)
+
+```typescript
+const firstTwoChannels = await db.orm.public.Order.groupBy('channel')
+ .orderBy((group) => group.channel.desc()) // desc() sorts descending, asc() ascending
+ .limit(2)
+ .aggregate((agg) => ({ orderCount: agg.count() }));
+```
+
For Prisma ORM 7 users, `groupBy()` chains into `aggregate()` instead of taking a `by` array with aggregate keys:
```diff
@@ -1531,20 +1625,22 @@ Filter groups by an aggregate comparison.
#### Remarks
-- PostgreSQL only. Chained on a `GroupedCollection` between `groupBy()` and `aggregate()`.
-- The `having()` callback exposes `count()`, `sum(field)`, `avg(field)`, `min(field)`, `max(field)`, each returning comparison methods (`eq`, `neq`, `gt`, `lt`, `gte`, `lte`). It compiles to a SQL `HAVING` clause.
+- PostgreSQL only. Not available on MongoDB. Call it on the result of `groupBy()`.
+- The `having()` callback gives you `count()`, `count(field)`, `sum(field)`, `avg(field)`, `min(field)`, and `max(field)`. Call a comparison on the result: `h.sum('amount').gt(1000)`. The comparisons are `eq`, `neq`, `gt`, `lt`, `gte`, and `lte`.
+- For more than one condition, call `.having(...)` again. The conditions are combined with AND.
+- You cannot refer to a key you returned from `aggregate()` here. Write the aggregate out again in `having()`.
#### Options
-| Name | Type | Required | Description |
+| Argument | Type | Required | Description |
|---|---|---|---|
-| `predicate` | Callback `(h) => h.fn(field).cmp(value)` | Yes | The group-level condition. |
+| `predicate` | Callback `(h) => h.sum('amount').gt(1000)` | Yes | The condition each group must match. |
#### Return type
| Return type | Example | Description |
|---|---|---|
-| `GroupedCollection` | `db.orm.public.Order.groupBy('customerId').having(...)` | A grouped collection filtered by the aggregate predicate. |
+| `GroupedCollection` | `db.orm.public.Order.groupBy('customerId').having(...)` | Not a promise. Call `.aggregate(...)`. |
#### Examples
@@ -1556,30 +1652,27 @@ const bigSpenders = await db.orm.public.Order.groupBy('customerId')
.aggregate((agg) => ({ totalAmount: agg.sum('amount') }));
```
-##### Filter groups by row count (PostgreSQL)
-
-```typescript
-const groupsWithAtLeastFive = await db.orm.public.Order.groupBy('customerId')
- .having((h) => h.count().gte(5))
- .aggregate((agg) => ({ orderCount: agg.count() }));
-```
-
## Filter conditions and operators
-Filters build the predicate inside `where()` (and relation refinements). PostgreSQL and MongoDB use different filter surfaces.
+A condition says which rows match. You write one inside `where()`, and inside a relation filter such as `u.posts.some(...)`. If you call `where()` more than once, the conditions are ANDed. One call can take the object form and the next the callback form. PostgreSQL and MongoDB write conditions differently.
-- **PostgreSQL** uses column-level comparison methods (`u.email.eq(...)`) inside a callback, plus standalone combinator functions imported from `@prisma/orm-postgres/orm-client`.
-- **MongoDB** uses static factories on `MongoFieldFilter` imported from `@prisma/orm-mongo/query-ast/execution`, plus `.and()`/`.not()` instance methods and the standalone `MongoOrExpr.of(...)`.
+- **PostgreSQL** compares a field with a method on that field, such as `u.email.eq(...)`, inside a callback. You name the callback's argument yourself, so the `u` and `p` below are only names. To combine conditions, import `and`, `or`, `not`, and `all` from `@prisma/orm-postgres/orm-client`.
+- **MongoDB** has no callback form. `where()` takes either the shorthand object or a filter such as `MongoFieldFilter.eq('name', 'Alice')`. The full list is under [`MongoFieldFilter`](#mongofieldfilter). Import `MongoFieldFilter`, `MongoAndExpr`, and `MongoOrExpr` from `@prisma/orm-mongo/query-ast/execution`. To combine conditions, use `.and()`, `.not()`, and `MongoOrExpr.of([...])`, described under [Combinators](#combinators).
### Scalar comparisons (PostgreSQL)
-Column-level comparison methods on a field accessor.
+Comparison methods on a field.
#### Remarks
-- PostgreSQL. Each method is gated by a codec trait: `eq`/`neq`/`in`/`notIn` need equality; `gt`/`lt`/`gte`/`lte` need ordering; `like` needs the textual trait; `isNull`/`isNotNull` are available on every field.
-- `like()` matches a SQL pattern case-sensitively.
-- `ilike()` matches case-insensitively. It is a Postgres-adapter-registered operation attached to any textual-trait field, not a core comparison method.
+- PostgreSQL. Which comparison methods a field has depends on its type. Autocomplete shows which ones apply.
+- `isNull()` and `isNotNull()` are on every field.
+- `eq`, `neq`, `in`, and `notIn` are on a field whose values can be compared for equality.
+- `gt`, `lt`, `gte`, and `lte` are on a field whose values can be ordered.
+- `like` and `ilike` are on a text field.
+- `String` and enum fields have all of them. `Uuid` and `DateTime` have all but `like` and `ilike`. A `Boolean` field has only the equality methods, `isNull()`, and `isNotNull()`. A `Json` field has only `isNull()` and `isNotNull()`.
+- `like()` matches a SQL pattern case-sensitively. `ilike()` matches the same pattern case-insensitively.
+- `in([])` matches no rows. `notIn([])` matches every row.
#### Options
@@ -1603,8 +1696,10 @@ const notAlice = await db.orm.public.User.where((u) => u.email.neq('alice@exampl
##### Ordered comparisons (PostgreSQL)
```typescript
-const after = await db.orm.public.Post.where((p) => p.createdAt.gt(new Date('2024-01-02T10:00:00.000Z'))).all();
-const inclusive = await db.orm.public.Post.where((p) => p.createdAt.gte(new Date('2024-01-02T10:00:00.000Z'))).all();
+const after = await db.orm.public.Post.where((p) =>
+ p.createdAt.gt(Temporal.Instant.from('2024-01-02T10:00:00.000Z')),
+).all();
+// gte() is the same comparison, including the boundary value.
```
##### Pattern matching (PostgreSQL)
@@ -1619,18 +1714,20 @@ const caseInsensitive = await db.orm.public.User.where((u) => u.email.ilike('%@E
```typescript
const lowOrHigh = await db.orm.public.Post.where((p) => p.priority.in(['low', 'high'])).all();
const notLowOrHigh = await db.orm.public.Post.where((p) => p.priority.notIn(['low', 'high'])).all();
-const withoutEmbedding = await db.orm.public.Post.where((p) => p.embedding.isNull()).all();
+const withoutDescription = await db.orm.public.Task.where((t) => t.description.isNull()).all(); // Task.description is String? in the example schema
```
### Combinators
-Combine or negate predicates.
+Combine or negate conditions.
#### Remarks
-- On PostgreSQL, `and`, `or`, `not`, and `all` are **standalone functions** imported from `@prisma/orm-postgres/orm-client`. They build AST nodes directly and are independent of any model accessor.
-- On MongoDB, `.and(other)` and `.not()` are **instance methods** on any `MongoFieldFilter`. There is **no `.or()` instance method** and no `$nor` support. To OR filters, construct `MongoOrExpr.of([...])`.
-- On PostgreSQL, `all()` takes no arguments and returns a constant-true predicate, useful wherever a predicate is structurally required.
+- On PostgreSQL, import `and`, `or`, `not`, and `all` from `@prisma/orm-postgres/orm-client`. You call them as functions, as in `and(a, b)`. They are not methods on a field.
+- On PostgreSQL, `and(...)` and `or(...)` take any number of conditions, so `and(a, b, c)` is fine. `not(...)` takes one.
+- On PostgreSQL, `all()` takes no arguments and returns a condition that matches every row. This `all()` is not the `.all()` you call at the end of a chain to run the query.
+- On MongoDB, call `.and(other)` and `.not()` on a condition you already built, as in `MongoFieldFilter.eq('name', 'Alice').and(...)`. There is no `.or()` method. To OR conditions, build `MongoOrExpr.of([...])`.
+- On MongoDB, `.and(other)` takes exactly one condition. For three, chain it: `a.and(b).and(c)`. You can also write all three at once with `MongoAndExpr.of([a, b, c])`. `MongoAndExpr` and `MongoOrExpr` come from the same module as `MongoFieldFilter`.
#### Examples
@@ -1638,16 +1735,24 @@ Combine or negate predicates.
```typescript
import { and, or, not } from '@prisma/orm-postgres/orm-client';
-
-const both = await db.orm.public.Post.where((p) => and(p.priority.eq('low'), p.userId.eq(carolId))).all();
+const both = await db.orm.public.Post.where((p) =>
+ and(p.priority.eq('low'), p.userId.eq('00000000-0000-0000-0000-000000000003')),
+).all();
const either = await db.orm.public.Post.where((p) => or(p.priority.eq('urgent'), p.priority.eq('high'))).all();
const negated = await db.orm.public.Post.where((p) => not(p.priority.eq('low'))).all();
```
+##### `all` (PostgreSQL)
+
+```typescript
+import { all } from '@prisma/orm-postgres/orm-client';
+const everyPost = await db.orm.public.Post.where(() => all()).all();
+```
+
##### `and` / `or` / `not` (MongoDB)
```typescript
-import { MongoFieldFilter, MongoOrExpr } from '@prisma/orm-mongo/query-ast/execution';
+import { MongoAndExpr, MongoFieldFilter, MongoOrExpr } from '@prisma/orm-mongo/query-ast/execution';
const both = await db.orm.users
.where(MongoFieldFilter.eq('role', 'author').and(MongoFieldFilter.eq('name', 'Alice')))
@@ -1656,11 +1761,22 @@ const either = await db.orm.users
.where(MongoOrExpr.of([MongoFieldFilter.eq('name', 'Alice'), MongoFieldFilter.eq('name', 'Bob')]))
.all();
const negated = await db.orm.users.where(MongoFieldFilter.eq('name', 'Alice').not()).all();
+const bothAtOnce = await db.orm.users
+ .where(MongoAndExpr.of([MongoFieldFilter.eq('role', 'author'), MongoFieldFilter.eq('name', 'Alice')]))
+ .all();
```
-:::note[No `.or()` instance method or `$nor` on MongoDB]
-`MongoFieldFilter` expressions have `.and(...)` and `.not()` instance methods, but no `.or(...)`. Accessing `.or` is `undefined` and calling it throws `TypeError`. Use `MongoOrExpr.of([...])` for disjunction. There is no `$nor` combinator anywhere in the library.
-:::
+##### AND of two ORs (MongoDB)
+
+```typescript
+const authorsNamedAliceOrBob = await db.orm.users
+ .where(
+ MongoOrExpr.of([MongoFieldFilter.eq('name', 'Alice'), MongoFieldFilter.eq('name', 'Bob')]).and(
+ MongoOrExpr.of([MongoFieldFilter.eq('role', 'author'), MongoFieldFilter.eq('role', 'editor')]),
+ ),
+ )
+ .all();
+```
### Relation filters (PostgreSQL)
@@ -1668,10 +1784,12 @@ Filter parents by their related rows.
#### Remarks
-- PostgreSQL. `some()`, `every()`, and `none()` are methods on a to-many relation accessor and compile to correlated `EXISTS` / `NOT EXISTS` subqueries.
-- `some()` with no predicate matches parents with at least one related row.
-- `every()` is vacuously true for a parent with zero related rows.
-- A to-one relation is filtered from the child side with the same `some(...)` shape (the compiled SQL is a correlated subquery regardless of cardinality).
+- PostgreSQL. `some()`, `every()`, and `none()` are methods on a to-many relation, which is a relation that holds many related rows, such as `User.posts`.
+- Each of the three takes either a callback or a plain object of equality matches: `u.posts.some((p) => p.priority.eq('urgent'))` and `u.posts.some({ priority: 'urgent' })` mean the same thing.
+- `some()` with no argument matches parents with at least one related row.
+- A user with no posts matches `every()`.
+- `not(u.posts.some(...))` and `u.posts.none(...)` match the same rows. Prefer `none(...)`.
+- All three are also methods on a to-one relation, such as `Post.user`, which holds a single related row. There `some()` means the related row matches, and `none()` means it does not. `every()` matches when the related row matches, and also when there is no related row.
#### Examples
@@ -1688,6 +1806,14 @@ const noUrgentPost = await db.orm.public.User.where((u) => u.posts.none((p) => p
```typescript
const postsByAlice = await db.orm.public.Post.where((p) => p.user.some({ email: 'alice@example.com' })).all();
+const postsByAnyoneElse = await db.orm.public.Post.where((p) => p.user.none({ email: 'alice@example.com' })).all();
+```
+
+For Prisma ORM 7 users, `some()` and `none()` on a to-one relation replace `is` and `isNot`:
+
+```diff
+- const postsByAlice = await prisma.post.findMany({ where: { user: { is: { email: 'alice@example.com' } } } });
++ const postsByAlice = await db.orm.public.Post.where((p) => p.user.some({ email: 'alice@example.com' })).all();
```
### Shorthand object filter
@@ -1697,14 +1823,19 @@ A plain object of equality matches, ANDed together.
#### Remarks
- Available for PostgreSQL and MongoDB.
-- Each key must support equality. Multiple keys are combined with implicit AND.
+- Multiple keys are combined with implicit AND. A key set to `undefined` is skipped.
+- On PostgreSQL, a key set to `null` becomes an IS NULL check. On MongoDB it matches documents where the field is null and documents where the field is missing.
+- On PostgreSQL, the shorthand object cannot filter a field whose type cannot be compared for equality, such as `Json`. Using that field as a key compiles, but fails at run time with an error whose `code` is `ORM.FILTER_UNSUPPORTED`. Use the callback form, which has whatever methods that type does support. On a `Json` field those are `isNull()` and `isNotNull()`.
#### Examples
##### Shorthand object
```typescript tab="PostgreSQL"
-const row = await db.orm.public.Post.where({ userId: aliceId, priority: 'high' }).first();
+const row = await db.orm.public.Post.where({
+ userId: '00000000-0000-0000-0000-000000000001',
+ priority: 'high',
+}).first();
```
```typescript tab="MongoDB"
@@ -1713,27 +1844,29 @@ const alice = await db.orm.users.where({ name: 'Alice', role: 'author' }).first(
### `MongoFieldFilter`
-Static factories that build MongoDB filter expressions.
+Helpers that build one MongoDB filter condition. Use them for anything other than equality on a top-level field, which `.where({ name: 'Alice' })` already covers. Chaining two of them, as in `.where(a).where(b)`, joins them with AND. For OR, see [Combinators](#combinators).
#### Remarks
-- MongoDB. Imported from `@prisma/orm-mongo/query-ast/execution`.
-- Exactly eleven factories exist: `of`, `eq`, `neq`, `gt`, `lt`, `gte`, `lte`, `in`, `nin`, `isNull`, `isNotNull`. The inequality factory is `neq`, **not** `ne` (`MongoFieldFilter.ne` is `undefined`).
-- `isNull` / `isNotNull` are sugar for equals-null / not-equals-null, not a `$exists` check.
-- The factories `exists`, `regex`, `elemMatch`, `all`, and `size` do **not** exist on `MongoFieldFilter`. There is a separate `MongoExistsExpr` class for `$exists`, but no regex, `elemMatch`, `$all`, or `$size` support anywhere in the library.
+- MongoDB. Import from `@prisma/orm-mongo/query-ast/execution`. `MongoExistsExpr` is in the same module.
+- The helpers are `of`, `eq`, `neq`, `gt`, `lt`, `gte`, `lte`, `in`, `nin`, `isNull`, and `isNotNull`. Each returns one filter, which you can pass straight to `.where()` or hold in a variable. There is no `ne`; use `neq`.
+- `of(field, operator, value)`: pass the MongoDB operator as a string, including the `$`, for example `'$regex'`. Use it for operators the named helpers do not cover, such as `MongoFieldFilter.of('tags', '$size', 3)` on an array field `tags`. A misspelled operator such as `'$regexp'` is not caught until the query runs.
+- `isNull(field)` matches documents where the field is `null` and documents where the field is missing. `isNotNull(field)` matches everything else. To test only whether a field is present, use `MongoExistsExpr.exists(field)` or `MongoExistsExpr.notExists(field)`. Those two are all `MongoExistsExpr` offers.
+- There is no `regex`, `elemMatch`, `all`, or `size` helper. Write those operators through `of`.
#### Options
| Method | Type | Description |
|---|---|---|
+| `of(field, operator, value)` | Field + MongoDB operator string + value | Any operator, including the `$`, such as `'$regex'`. |
| `eq(field, value)` / `neq(field, value)` | Field + value | Equality / inequality. |
-| `gt` / `lt` / `gte` / `lte` `(field, value)` | Field + ordered value | Ordered comparisons. |
+| `gt(field, value)` / `lt(field, value)` / `gte(field, value)` / `lte(field, value)` | Field + value | Greater than, less than, greater or equal, less or equal. |
| `in(field, values)` / `nin(field, values)` | Field + array | Membership / exclusion. |
-| `isNull(field)` / `isNotNull(field)` | Field | Null / not-null (equals-null semantics). |
+| `isNull(field)` / `isNotNull(field)` | Field | `isNull` matches a `null` value or a missing field. `isNotNull` matches everything else. |
#### Examples
-##### Comparison factories (MongoDB)
+##### Comparison helpers (MongoDB)
```typescript
import { MongoFieldFilter } from '@prisma/orm-mongo/query-ast/execution';
@@ -1756,15 +1889,23 @@ const noBio = await db.orm.users.where(MongoFieldFilter.isNull('bio')).all();
const hasBio = await db.orm.users.where(MongoFieldFilter.isNotNull('bio')).all();
```
+##### An operator written as a string, and `$exists` (MongoDB)
+
+```typescript
+import { MongoExistsExpr, MongoFieldFilter } from '@prisma/orm-mongo/query-ast/execution';
+
+const matching = await db.orm.posts.where(MongoFieldFilter.of('title', '$regex', '^Hello')).all();
+const withBioField = await db.orm.users.where(MongoExistsExpr.exists('bio')).all();
+```
+
### Dot-notation into an embedded object (MongoDB)
-Filter into a field of an embedded value object using a dot path.
+Filter into a field of an object stored inside the document, rather than in its own collection, using a dot path.
#### Remarks
-- MongoDB. Runtime behavior is verified; the type surface does not declare embedded paths, so the object-shorthand form needs a cast.
-- `MongoWhereFilter` is typed against the model's own top-level field keys, so `{ 'address.city': ... }` is a compile error without a cast. At runtime there is no such restriction: the object key is used verbatim as the Mongo field path.
-- For a type-clean alternative, use `MongoFieldFilter.eq('address.city', ...)`, which takes a bare `string` and needs no cast.
+- MongoDB. Use `MongoFieldFilter.eq('address.city', 'San Francisco')` for a dotted path. It takes the path as a plain `string` and needs no cast.
+- You can also write `.where({ 'address.city': 'San Francisco' })`. That object form runs correctly, but it does not type-check. `.where()` only knows your model's top-level field names, so you must cast.
#### Examples
@@ -1773,157 +1914,166 @@ Filter into a field of an embedded value object using a dot path.
```typescript
import { MongoFieldFilter } from '@prisma/orm-mongo/query-ast/execution';
-// Type-clean form: MongoFieldFilter takes a bare string path.
const usersInSf = await db.orm.users.where(MongoFieldFilter.eq('address.city', 'San Francisco')).all();
```
-The object-shorthand form works at runtime but requires a cast, because the embedded path is not in the model's declared field keys:
+`MongoFieldFilter.eq` is the recommended form. The object form needs a cast, and the cast drops type checking for every key in that object:
```typescript
const usersInSf = await db.orm.users
.where({ 'address.city': 'San Francisco' } as unknown as Record)
.all();
```
-
## Field update operations
-MongoDB field operations are accessed through a field accessor inside `update()`, `updateAndCount()`, and `upsert()` callbacks (for example `t.duration.inc(5)`). Each operation targets a field and produces a Mongo update operator. PostgreSQL's `update()` has no equivalent callback form (see [`update()`](#update)).
+On MongoDB, `update()`, `updateAll()`, `updateAndCount()`, and `upsert()` take a callback in place of a data object. The callback gets one argument, the update builder, written `u` in these examples, and returns an array of the operations you want. `updateAndCount()` takes the callback in the same place as `update()`. `upsert()` takes it as its `update` key, beside `create`. PostgreSQL has no callback form: pass an object to [`update()`](#update) instead.
+
+- For a top-level field, read it off the update builder as a property: `u.bio.set('Hello')`.
+- For a field inside an embedded object, call the update builder as a function with the dot path: `u('address.city').set('San Francisco')`. The `upsert()` callback cannot use a dot path. Given one, `upsert()` throws an error whose `code` is `ORM.OPERATION_UNSUPPORTED`. Set the whole embedded object on its top-level field instead, as in `u.address.set({ street: '1 Market St', city: 'San Francisco', zip: '94105', country: 'US' })`. Give every field of the embedded object, including the optional ones.
+
+All four methods require `.where()`. Without one they throw an error whose `code` is `ORM.WHERE_MISSING`. `.where({})` does not satisfy that rule, because an empty object adds no condition. To change every document, pass a filter that matches them all, such as `MongoFieldFilter.isNotNull('_id')`. A `_id` filter takes the hex string that a read gives you back, as the examples below show. On `upsert()`, `.where()` picks the document to update, exactly as on `update()`, and the full example is under [`upsert()`](#upsert). `update()` returns the updated document, or `null` when nothing matched. `updateAll()` returns an [`AsyncIterableResult`](#asynciterableresult) of the changed documents. [`updateAndCount()`](#updateandcount) exists on both databases, and it returns the number of changed documents.
### Available operations (MongoDB)
#### Remarks
-- MongoDB only.
-- `set()`, `unset()`, `inc()`, and `mul()` are available and verified.
+- MongoDB only. These are the four operations the update builder gives you for single-value fields: `set()`, `unset()`, `inc()`, and `mul()`. The four array operations follow under [Array operations](#array-operations-mongodb-unverified).
- `set(value)` assigns a field.
- `unset()` removes a field.
- - `inc(n)` increments a numeric field. Applied to a missing field, `$inc` initializes it to the increment.
- - `mul(n)` multiplies a numeric field. Applied to a missing field, MongoDB sets it to `0` regardless of the multiplier.
-- The field accessor is typed against the collection's **base** model, even after `variant(...)`. Accessing a variant-only field (such as `Tutorial.duration`) inside a callback is a compile error, though it works correctly at runtime because the accessor is a `Proxy` that resolves any field name.
+ - `inc(n)` increments a numeric field. TypeScript offers `inc` and `mul` on numeric fields only. Applied to a missing field, `inc` sets the field to `n`.
+ - `mul(n)` multiplies a numeric field. Applied to a missing field, it sets the field to `0` whatever `n` is.
+- After `variant(...)`, `u.duration.inc(10)` is a type error even though it runs correctly. Put `// @ts-expect-error` on the line directly above the line TypeScript flags. When the type error goes away, TypeScript reports the comment as unused, so delete the comment then.
#### Examples
+These examples use the MongoDB accessors from [Setting up the client](#setting-up-the-client).
+
##### `set()` (MongoDB)
+Return as many operations as you want. They are applied together, in one write.
+
```typescript
const updated = await db.orm.users
- .where({ _id: bobId })
- .update((u) => [u.bio.set('Set via field op')]);
+ .where({ _id: '6650f1c2a1b2c3d4e5f60002' })
+ .update((u) => [u.bio.set('Set via field op'), u.name.set('Bob R.')]);
+// A field inside the embedded `address` object, by dot path:
+const moved = await db.orm.users.where({ _id: '6650f1c2a1b2c3d4e5f60002' }).update((u) => [u('address.city').set('San Francisco')]);
```
##### `inc()` and `mul()` (MongoDB)
+`duration` is declared on `Tutorial`, not on the base `Post` model, so each `.update(...)` line that touches it needs its own `// @ts-expect-error` directly above it.
+
```typescript
const incremented = await db.orm.posts
.variant('Tutorial')
- .where({ _id: tutorialId })
- .update((t) => [t.duration.inc(10)]);
+ .where({ _id: '6650f1c2a1b2c3d4e5f60003' })
+ // @ts-expect-error duration is a Tutorial field, not a Post field
+ .update((u) => [u.duration.inc(10)]);
const multiplied = await db.orm.posts
.variant('Tutorial')
- .where({ _id: tutorialId })
- .update((t) => [t.duration.mul(3)]);
+ .where({ _id: '6650f1c2a1b2c3d4e5f60003' })
+ // @ts-expect-error duration is a Tutorial field, not a Post field
+ .update((u) => [u.duration.mul(3)]);
```
##### `unset()` (MongoDB)
```typescript
-const updated = await db.orm.users.where({ _id: aliceId }).update((u) => [u.bio.unset()]);
+const updated = await db.orm.users.where({ _id: '6650f1c2a1b2c3d4e5f60001' }).update((u) => [u.bio.unset()]);
+const changed = await db.orm.users.where({ _id: '6650f1c2a1b2c3d4e5f60002' }).updateAndCount((u) => [u.bio.unset()]);
```
### Operations that do not exist (MongoDB)
-#### Remarks
-
-- MongoDB. These are library gaps, not contract limitations. `min()`, `max()`, `rename()`, and `currentDate()` have **no accessor method at all**. The library never generates `$min`, `$max`, `$rename`, or `$currentDate`.
-
:::warning[No `min`, `max`, `rename`, or `currentDate` field operations]
-The field accessor implements `set`, `unset`, `inc`, `mul`, `push`, `pull`, `addToSet`, and `pop` only. `min`, `max`, `rename`, and `currentDate` are absent from the API. Because a field accessor is a `Proxy`, `u.bio.rename` resolves to `undefined` and only throws `TypeError` when you attempt to call it. Do not use these operations; they are not supported.
+The update builder implements eight operations, and only these eight: `set`, `unset`, `inc`, `mul`, `push`, `pull`, `addToSet`, and `pop`. `min`, `max`, `rename`, and `currentDate` are absent from the API, and TypeScript rejects them: `rename` is not a property of `u.bio`.
:::
-### Array operations (MongoDB, unverified)
+### Array operations (MongoDB) [#array-operations-mongodb-unverified]
#### Remarks
-- MongoDB. `push()`, `pull()`, `addToSet()`, and `pop()` exist on the field accessor and compile, but they are **unverified** on the current test contract, which has no array (`many: true`) field to target. Applied to a non-array field, MongoDB rejects the write with a server-level error (`The field '...' must be an array but is of type ...`), not a library error.
-- Treat these as available but exercise them against a real array field in your own schema before relying on them.
+- MongoDB. `push()`, `pull()`, `addToSet()`, and `pop()` are available on the update builder. `push(value)` appends one element. `addToSet(value)` appends it only if it is not already there. `pop(1)` removes the last element, and `pop(-1)` removes the first.
+- `pull(match)` removes every element equal to the argument. If the array holds objects, pass part of an object to remove every element that matches it, as in `u.tags.pull({ kind: 'draft' })`.
+- These operations need an array field, declared with `[]` in your contract, as in `tags String[]`. Applied to any other field, the write fails. There is no `error.code` to match, because the error is MongoDB's own.
+- The four lines below are separate calls because one update cannot apply two operations to the same field. One update can still change two different fields, such as pushing to `tags` and setting `bio`. The example schema has no array field, so these four lines assume `tags String[]` on `User` and will not run against the schema on this page.
+
+```typescript
+await db.orm.users.where({ _id: '6650f1c2a1b2c3d4e5f60001' }).update((u) => [u.tags.push('admin')]);
+await db.orm.users.where({ _id: '6650f1c2a1b2c3d4e5f60001' }).update((u) => [u.tags.addToSet('admin')]);
+await db.orm.users.where({ _id: '6650f1c2a1b2c3d4e5f60001' }).update((u) => [u.tags.pull('draft')]);
+await db.orm.users.where({ _id: '6650f1c2a1b2c3d4e5f60001' }).update((u) => [u.tags.pop(1)]);
+```
## Result types
### Model and result types
-`contract.d.ts` exports a `Models` namespace with one type per model. On PostgreSQL the schema is part of the name, `Models.public_User`; on SQLite, which has no schemas, it is `Models.User`. The same file exports a `models` constant for dotted access, `typeof models.public.User`. A model type carries every scalar field and every relation, so no query returns it directly. Three types derive what queries do return:
+`contract.d.ts` exports a type for every model, under `Models`. On PostgreSQL the schema is part of the name: `Models.public_User`. The same file exports a `models` constant, so `typeof models.public.User` is the same type. A model type lists every scalar field and every relation, and no query returns exactly that, so three types derive what a query does return:
-- `Scalars` is the model without its relations: what `all()` and `first()` return when you do not `select` or `include`.
-- `Shape` is a row with chosen fields and relations. In `Spec`, `"+"` names scalars and relations to keep, `"-"` names scalars to drop, and any other key is a relation with its own nested spec. Naming only relations in `"+"` keeps every scalar.
-- `ResultType` is one row of a query you already wrote.
+- `Scalars` is the model without its relations. It is what `all()` and `first()` return when you do not call `select()` or `include()`.
+- `Shape` is a row with the fields and relations you choose. In `Spec`, `"+"` names the scalar fields and relations to keep, `"-"` names scalar fields to drop, and any other key is a relation with its own nested spec. If `"+"` names only relations, every scalar field stays. A relation named in `"+"` comes with all of its scalar fields and none of its own relations.
+- `ResultType` is one row of a query you have already written.
-`Scalars` and `Shape` come from `@prisma/orm-postgres/family-contract/types` (`@prisma/orm-mongo/family-contract/types` on MongoDB); `ResultType` comes from `@prisma/orm-postgres/components/runtime`.
+Import `Scalars` and `Shape` from `@prisma/orm-postgres/family-contract/types`, or `@prisma/orm-mongo/family-contract/types` on MongoDB. Import `ResultType` from `@prisma/orm-postgres/components/runtime`. A contract emitted before `8.0.0-rc.10` has no `Models`; run `npx prisma contract emit` again after upgrading.
```typescript
-import type { Models } from "./prisma/contract";
-import type { Scalars, Shape } from "@prisma/orm-postgres/family-contract/types";
-import type { ResultType } from "@prisma/orm-postgres/components/runtime";
+import type { Models } from './contract.d';
+import type { Scalars, Shape } from '@prisma/orm-postgres/family-contract/types';
+import type { ResultType } from '@prisma/orm-postgres/components/runtime';
type UserRow = Scalars;
-// { id: number; email: string; name: string | null }
+// every scalar field of User, no posts and no tasks
-type UserResponse = Shape;
-// { id: number; name: string | null; posts: { id: number; title: string }[] }
+type UserResponse = Shape;
+// every scalar field of User except email, plus posts as an array of { id, title }
-const usersWithPosts = db.orm.public.User.include("posts");
+const usersWithPosts = db.orm.public.User.include('posts');
type UserWithPosts = ResultType;
-// the same type as Shape
+// the same type as Shape
```
-Use `Shape` to declare a function's return type once and let the compiler check the body at the `return`. Wrong field names, a relation under `"-"`, or a scalar in `"+"` next to a `"-"` are compile errors on that key. These types replace Prisma ORM 7's `Prisma.User` and `Prisma.UserGetPayload<...>`.
+Use `Shape` to declare a function's return type once, and TypeScript checks the body at the `return`. A wrong field name, a relation under `"-"`, or a scalar field in `"+"` next to a `"-"` is a compile error on that key. For Prisma ORM 7 users, these replace `Prisma.User` and `Prisma.UserGetPayload<...>`.
### `AsyncIterableResult`
-The read terminals `all()` / `createAll()` / `updateAll()` / `deleteAll()` return an `AsyncIterableResult`, imported (if you need the type) from `@prisma/orm-postgres/components/runtime`. It has a dual interface:
+The methods `all()` / `createAll()` / `updateAll()` / `deleteAll()` return an `AsyncIterableResult`. You can use it two ways:
+
+- `await` the result (or call `.toArray()`) to get every row in one array.
+- Loop over it with `for await ... of` to take rows one at a time.
-- **Buffered**: `await` the result (or call `.toArray()`) to collect every row into an array.
-- **Streaming**: use `for await ... of` to iterate rows one at a time.
+`createAll()`, `updateAll()`, and `deleteAll()` exist on PostgreSQL as well as MongoDB. [Write methods](#mutation-terminals) documents what each one takes, with examples. If you need the type by name, import it from `@prisma/orm-postgres/components/runtime` on PostgreSQL, or from `@prisma/orm-mongo/components/runtime` on MongoDB.
#### Single consumption and mode switching
-A result is consumed once per mode:
+Each result is read once, one way:
+
+- Re-`await`ing (or calling `.toArray()` on) a result you already awaited is **safe**. It returns the same array.
+- Looping a result a second time with `for await` throws an error whose `code` is `RUNTIME.ITERATOR_CONSUMED`. So does switching from one way to the other, such as awaiting a result and then looping it. The message contains `already been consumed`. To recognise the error, import `isRuntimeError` from `@prisma/orm-postgres/components/runtime` on PostgreSQL, or from `@prisma/orm-mongo/components/runtime` on MongoDB, and check `error.code`.
-- Re-`await`ing (or calling `.toArray()` on) an already-buffered result is **safe**: it returns the cached array, with no re-query and no throw.
-- **Switching modes** on a consumed result throws `RUNTIME.ITERATOR_CONSUMED` (message: `already been consumed`). Awaiting a result and then iterating it with `for await` (or the reverse) is the failure mode.
+PostgreSQL and MongoDB behave the same here. The example below uses the PostgreSQL accessor. On MongoDB the same methods are on `db.orm.users`, see [Setting up the client](#setting-up-the-client). `all()` returns the result object straight away, and the `await` is what runs the query, so the example holds the result in a variable.
```typescript
+import { isRuntimeError } from '@prisma/orm-postgres/components/runtime';
const result = db.orm.public.User.all();
const first = await result;
-const again = await result.toArray(); // safe: same cached array
+const again = await result.toArray(); // safe: same array
-// but switching mode throws:
-for await (const user of result) {
- // throws RUNTIME.ITERATOR_CONSUMED
+try {
+ for await (const user of result) {
+ }
+} catch (error) {
+ if (isRuntimeError(error) && error.code === 'RUNTIME.ITERATOR_CONSUMED') {
+ // the result was already read as an array
+ }
}
```
-This behavior is shared by the PostgreSQL and MongoDB implementations; the error message and rule are identical.
-
-### Named model types
-
-`contract.d.ts` exports a `Models` namespace and a `models` constant with one member per model, named `_` (`Models.public_User`, or `typeof models.public.User`; bare model names on SQLite). Two helpers from `@prisma/orm-postgres/family-contract/types` turn a model into a row type without a client in scope: `Scalars` is the row a default fetch returns, and `Shape` derives a data structure with chosen scalars and nested relations (`'-'` drops fields, `'+'` on a relation picks its fields). `ResultType` from `@prisma/orm-postgres/components/runtime` names what a query returns.
-
-```typescript
-import type { Models } from './prisma/contract';
-import type { Scalars, Shape } from '@prisma/orm-postgres/family-contract/types';
-import type { ResultType } from '@prisma/orm-postgres/components/runtime';
-
-type UserRow = Scalars;
-type UserResponse = Shape;
-const usersWithPosts = db.orm.public.User.include('posts');
-type UserWithPosts = ResultType;
-```
-
-These need `8.0.0-rc.10` or newer. A `contract.d.ts` emitted by an earlier release lacks the `Models` namespace until you run `prisma contract emit` again. On MongoDB, import `Scalars` and `Shape` from the `@prisma/orm-mongo` equivalent of the types entrypoint.
-
### Aggregate result shapes
-- `aggregate((agg) => ({ ... }))` resolves to a **single object** keyed by your aliases: `{ total: 10 }`.
-- `groupBy(...).aggregate((agg) => ({ ... }))` resolves to an **array**, one object per group, carrying both the group key field(s) and the aggregate aliases: `[{ customerId, orderCount, totalAmount }, ...]`.
-- `count()` is always a `number` (`0` over an empty set). `sum()` and `avg()` are `number | null`: they resolve to `null`, not `0`, over an empty result set.
+- `aggregate((agg) => ({ ... }))` resolves to a **single object** keyed by your aliases. `agg` is the callback's argument, and it carries the aggregate functions. `db.orm.public.Order.aggregate((agg) => ({ total: agg.count() }))` resolves to one object, such as `{ total: 10 }`.
+- [Grouped aggregates](#grouped-aggregates) documents `aggregate()`, `groupBy()` with more than one field, and every function `agg` gives you. The bullets here cover only the shape of what you get back.
+- `groupBy(...).aggregate((agg) => ({ ... }))` resolves to an **array**, one object per group. Each object carries the fields you grouped by plus your aliases. `db.orm.public.Order.groupBy('customerId').aggregate((agg) => ({ orderCount: agg.count(), totalAmount: agg.sum('amount') }))` resolves to `[{ customerId, orderCount, totalAmount }, ...]`.
+- `count()` is always a `number` (`0` over an empty set). `sum()`, `avg()`, `min()`, and `max()` resolve to `null`, not `0`, over an empty result set.
diff --git a/apps/docs/content/docs/orm/reference/sql-query-builder.mdx b/apps/docs/content/docs/orm/reference/sql-query-builder.mdx
index d4db84881fd..f2a15bddde7 100644
--- a/apps/docs/content/docs/orm/reference/sql-query-builder.mdx
+++ b/apps/docs/content/docs/orm/reference/sql-query-builder.mdx
@@ -997,7 +997,7 @@ Recover a plan's row type at the type level.
- Import `ResultType` from `@prisma/orm-postgres/components/runtime`. It's a family-agnostic utility, not specific to the SQL builder.
- `ResultType` is the **per-row** shape, not an array. `await runtime.query(plan)` resolves to `Row[]`, but `ResultType` is `Row`.
-- Since `8.0.0-rc.10` it also works on ORM queries: `ResultType`. See [Named model types](/orm/reference/orm-client#named-model-types).
+- Since `8.0.0-rc.10` it also works on ORM queries: `ResultType`. See [Model and result types](/orm/reference/orm-client#model-and-result-types).
#### Examples