From 9717012f2004188d2ddcb18380b9801adcbfe801 Mon Sep 17 00:00:00 2001 From: willbot Date: Wed, 21 Oct 2026 17:37:31 +0200 Subject: [PATCH 1/3] docs(docs): update the Prisma ORM 8 pages for 8.0.0-rc.19 Signed-off-by: willbot Signed-off-by: Will Madden --- .../add-to-existing-project/mongodb.mdx | 11 +- .../add-to-existing-project/postgresql.mdx | 11 +- .../docs/(index)/prisma-orm/from-scratch.mdx | 11 +- .../quickstart/existing-app/mongodb.mdx | 15 +- .../quickstart/existing-app/postgresql.mdx | 7 +- apps/docs/content/docs/cli/configuration.mdx | 6 +- apps/docs/content/docs/cli/contract-emit.mdx | 2 + apps/docs/content/docs/cli/contract-infer.mdx | 6 +- apps/docs/content/docs/cli/contract-print.mdx | 4 + apps/docs/content/docs/cli/db-schema.mdx | 16 ++ apps/docs/content/docs/cli/db-sign.mdx | 3 + apps/docs/content/docs/cli/db-verify.mdx | 12 +- apps/docs/content/docs/cli/migration-plan.mdx | 6 + apps/docs/content/docs/cli/orm-init.mdx | 2 +- .../guides/database/multiple-databases.mdx | 14 +- .../docs/guides/deployment/bun-workspaces.mdx | 7 +- .../guides/deployment/cloudflare-workers.mdx | 13 +- .../guides/deployment/pnpm-workspaces.mdx | 7 +- .../docs/guides/deployment/turborepo.mdx | 7 +- .../docs/guides/frameworks/solid-start.mdx | 9 +- .../guides/integrations/github-actions.mdx | 7 +- .../content/docs/guides/making-guides.mdx | 7 +- .../switch-to-prisma-orm/from-drizzle.mdx | 7 +- .../switch-to-prisma-orm/from-mongoose.mdx | 37 +++-- .../switch-to-prisma-orm/from-sql-orms.mdx | 7 +- .../guides/upgrade-prisma-orm/mongodb.mdx | 19 ++- .../guides/upgrade-prisma-orm/postgresql.mdx | 9 +- .../docs/orm/coming-from-prisma-orm-7.mdx | 25 +-- .../orm/contract-authoring/psl-syntax.mdx | 106 ++++++++---- .../the-contract-artifact.mdx | 11 +- .../typescript-schema-builder.mdx | 37 +++-- apps/docs/content/docs/orm/core-concepts.mdx | 4 +- .../content/docs/orm/data-modeling/index.mdx | 8 +- .../docs/orm/data-modeling/mongodb.mdx | 18 +- .../data-modeling/relational-databases.mdx | 2 +- .../docs/orm/extensions/using-extensions.mdx | 28 ++-- .../orm/fundamentals/advanced-queries.mdx | 42 ++--- .../docs/orm/fundamentals/reading-data.mdx | 12 +- .../orm/fundamentals/relations-and-joins.mdx | 18 +- .../docs/orm/fundamentals/transactions.mdx | 10 +- .../docs/orm/fundamentals/writing-data.mdx | 18 +- .../authoring-custom-middleware.mdx | 14 +- .../docs/orm/middleware/built-in-budgets.mdx | 10 +- .../docs/orm/middleware/built-in-cache.mdx | 13 +- .../docs/orm/middleware/built-in-lints.mdx | 18 +- .../orm/middleware/how-middleware-works.mdx | 11 +- .../orm/migrations/applying-a-migration.mdx | 4 +- .../orm/migrations/generating-a-migration.mdx | 2 +- .../orm/migrations/how-migrations-work.mdx | 8 +- .../orm/migrations/rollbacks-and-recovery.mdx | 6 +- .../docs/orm/reference/migration-api.mdx | 40 ++--- .../content/docs/orm/reference/orm-client.mdx | 154 +++++++++++------- .../docs/orm/reference/pipeline-builder.mdx | 9 +- .../docs/orm/reference/raw-queries.mdx | 33 ++-- .../docs/orm/reference/sql-query-builder.mdx | 112 ++++++------- .../reference/transactions-and-runtime.mdx | 134 ++++++++++++--- apps/docs/content/docs/orm/release-status.mdx | 4 +- 57 files changed, 741 insertions(+), 432 deletions(-) diff --git a/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/mongodb.mdx b/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/mongodb.mdx index eeb59a4ea17..9527a2912a9 100644 --- a/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/mongodb.mdx +++ b/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/mongodb.mdx @@ -67,11 +67,14 @@ The part after the last `/`, and before any `?`, is the name of the database. ```typescript title="src/prisma/db.ts" import 'dotenv/config'; import mongo from '@prisma/orm-mongo/runtime'; +import { contractFromJson } from '@prisma/orm-mongo/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const db = mongo({ - contractJson, +export const contract = contractFromJson(contractJson); + +export const db = mongo({ + contract, url: process.env['DATABASE_URL']!, }); ``` @@ -106,7 +109,7 @@ model Post { } ``` -Keep the first line, because `contract emit` reads only `.prisma` files that start with it. `@@map("users")` names the collection that stores the model. MongoDB stores the identifier of every document in `_id`. On MongoDB, `contract emit` names each field in `contract.json` by the name in its `@map`, so in queries and in results you use `_id`, not `id`. The same goes for any other field with `@map`. +Keep the first line, because `contract emit` reads only `.prisma` files that start with it. `@@map("users")` names the collection that stores the model. MongoDB stores the identifier of every document in `_id`, and `@map("_id")` stores the `id` field there. In `db.orm` queries and in their results you use the field name, `id`, and Prisma ORM reads and writes `_id` for you. The same goes for any other field with `@map`. A pipeline built with `db.query` reads documents as MongoDB stores them, so it uses `_id`. Change the models to match your documents: one model for each collection, with one field for each field of its documents. [Data modeling](/orm/data-modeling) lists the field types. @@ -151,7 +154,7 @@ npx tsx script.ts ```text no-copy { username: null, - _id: '6abca6a1e7a9bb8d8c716f65', + id: '6abca6a1e7a9bb8d8c716f65', email: 'existing@example.com', name: 'Existing User' } diff --git a/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/postgresql.mdx b/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/postgresql.mdx index 6ab77b14a82..083891c7bbf 100644 --- a/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/postgresql.mdx +++ b/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/postgresql.mdx @@ -65,11 +65,14 @@ DATABASE_URL="postgres://username:password@host:5432/database?sslmode=require" ```typescript title="src/prisma/db.ts" import 'dotenv/config'; import postgres from '@prisma/orm-postgres/runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const db = postgres({ - contractJson, +export const contract = contractFromJson(contractJson); + +export const db = postgres({ + contract, url: process.env['DATABASE_URL']!, }); ``` @@ -290,7 +293,7 @@ npx tsx script.ts ## 8. Query a table with `db.sql` -`db.sql` builds SQL queries: where `db.orm` names models and fields, `db.sql` names tables and columns as they are in the database. This example reads the table `user`, which the `User` model maps with `@@map("user")`. `.build()` returns the query without running it, and `db.runtime().query()` runs it. +`db.sql` builds SQL queries: where `db.orm` names models and fields, `db.sql` names tables and columns as they are in the database. This example reads the table `user`, which the `User` model maps with `@@map("user")`. `.build()` returns the query without running it, and `db.query()` runs it. Replace `script.ts` with this version: @@ -303,7 +306,7 @@ async function main() { .limit(2) .build(); - const rows = await db.runtime().query(query); + const rows = await db.query(query); console.log(rows); await db.close(); diff --git a/apps/docs/content/docs/(index)/prisma-orm/from-scratch.mdx b/apps/docs/content/docs/(index)/prisma-orm/from-scratch.mdx index 51bd4d8dd6d..2a22c270e26 100644 --- a/apps/docs/content/docs/(index)/prisma-orm/from-scratch.mdx +++ b/apps/docs/content/docs/(index)/prisma-orm/from-scratch.mdx @@ -43,7 +43,7 @@ npm install @prisma/orm-postgres dotenv npm install --save-dev prisma tsx typescript ``` -`@prisma/orm-postgres` is the library your code imports, and `prisma` is the command-line tool. `dotenv` loads `.env`, `tsx` runs `index.ts` without a build step, and `typescript` 5.9 or newer is a peer dependency of `@prisma/orm-postgres`. So is `temporal-polyfill`, which npm installs for you; with Yarn, add it yourself. The two Prisma packages have different version numbers (for example, `prisma` 8.0.0-rc.24 and `@prisma/orm-postgres` 8.0.0-rc.18); that is normal, and any two `latest` versions work together. +`@prisma/orm-postgres` is the library your code imports, and `prisma` is the command-line tool. `dotenv` loads `.env`, `tsx` runs `index.ts` without a build step, and `typescript` 5.9 or newer is a peer dependency of `@prisma/orm-postgres`. So is `temporal-polyfill`, which npm installs for you; with Yarn, add it yourself. The two Prisma packages have different version numbers (for example, `prisma` 8.0.0-rc.24 and `@prisma/orm-postgres` 8.0.0-rc.19); that is normal, and any two `latest` versions work together. ## 2. Create the config file and `.env` @@ -134,16 +134,19 @@ The `marker` line in the output is the record that signing stores in the databas ## 5. Write and read data -Create `index.ts`, which builds the client from the two emitted files and then deletes, creates, updates, and reads rows on the `User` model: +Create `index.ts`, which turns the two emitted files into a typed contract value with `contractFromJson`, builds the client from it, and then deletes, creates, updates, and reads rows on the `User` model: ```typescript title="index.ts" import "dotenv/config"; import postgres from "@prisma/orm-postgres/runtime"; +import { contractFromJson } from "@prisma/orm-postgres/static"; import type { Contract } from "./prisma/contract.d"; import contractJson from "./prisma/contract.json" with { type: "json" }; -const db = postgres({ - contractJson, +const contract = contractFromJson(contractJson); + +const db = postgres({ + contract, url: process.env["DATABASE_URL"]!, }); diff --git a/apps/docs/content/docs/(index)/prisma-orm/quickstart/existing-app/mongodb.mdx b/apps/docs/content/docs/(index)/prisma-orm/quickstart/existing-app/mongodb.mdx index eb3dc61e7f6..3484aba596c 100644 --- a/apps/docs/content/docs/(index)/prisma-orm/quickstart/existing-app/mongodb.mdx +++ b/apps/docs/content/docs/(index)/prisma-orm/quickstart/existing-app/mongodb.mdx @@ -77,11 +77,14 @@ You do not need `prisma-8.md` for this page. It is a short reference for writing ```typescript title="src/prisma/db.ts" import 'dotenv/config'; import mongo from '@prisma/orm-mongo/runtime'; +import { contractFromJson } from '@prisma/orm-mongo/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const db = mongo({ - contractJson, +export const contract = contractFromJson(contractJson); + +export const db = mongo({ + contract, url: process.env['DATABASE_URL']!, }); ``` @@ -117,7 +120,7 @@ model User { } ``` -Keep the first line, because `contract emit` reads only `.prisma` files that start with it. `@@map("users")` names the collection that stores the model. MongoDB stores the identifier of every document in `_id`. On MongoDB, `contract emit` names each field in `contract.json` by the name in its `@map`, so in queries and in results you use `_id`, not `id`. The same goes for any other field with `@map`. The file has no `datasource` or `generator` block. The connection string comes from `.env`, and `contract emit` in the next step takes the place of the generator. [Data modeling](/orm/data-modeling) covers field types and relations. +Keep the first line, because `contract emit` reads only `.prisma` files that start with it. `@@map("users")` names the collection that stores the model. MongoDB stores the identifier of every document in `_id`, and `@map("_id")` stores the `id` field there. In queries and in their results you use the field name, `id`, and Prisma ORM reads and writes `_id` for you. The same goes for any other field with `@map`. The file has no `datasource` or `generator` block. The connection string comes from `.env`, and `contract emit` in the next step takes the place of the generator. [Data modeling](/orm/data-modeling) covers field types and relations. ## 4. Generate the files your code imports @@ -206,20 +209,20 @@ npx tsx script.ts ```text no-copy Created: { - _id: '6abc2ce7b04f66be2c85564d', + id: '6abc2ce7b04f66be2c85564d', email: 'alice@example.com', name: 'Alice' } All users: [ { - _id: '6abc2ce7b04f66be2c85564d', + id: '6abc2ce7b04f66be2c85564d', email: 'alice@example.com', name: 'Alice' } ] ``` -The script passes no identifier, and the new document gets one in `_id`, returned as a string. To find a document by it, filter on `_id`, as in `db.orm.users.where({ _id: created._id }).first()`. +The script passes no identifier, and the new document gets one, returned as a string in `id`. To find a document by it, filter on `id`, as in `db.orm.users.where({ id: created.id }).first()`. In your app, import `db` from `src/prisma/db.ts` the same way. [Reading data](/orm/fundamentals/reading-data) and [Writing data](/orm/fundamentals/writing-data) show the other queries. diff --git a/apps/docs/content/docs/(index)/prisma-orm/quickstart/existing-app/postgresql.mdx b/apps/docs/content/docs/(index)/prisma-orm/quickstart/existing-app/postgresql.mdx index 6a6f30ec49c..b7d14587e28 100644 --- a/apps/docs/content/docs/(index)/prisma-orm/quickstart/existing-app/postgresql.mdx +++ b/apps/docs/content/docs/(index)/prisma-orm/quickstart/existing-app/postgresql.mdx @@ -77,11 +77,14 @@ You do not need `prisma-8.md` for this page. It is a short reference for writing ```typescript title="src/prisma/db.ts" import 'dotenv/config'; import postgres from '@prisma/orm-postgres/runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const db = postgres({ - contractJson, +export const contract = contractFromJson(contractJson); + +export const db = postgres({ + contract, url: process.env['DATABASE_URL']!, }); ``` diff --git a/apps/docs/content/docs/cli/configuration.mdx b/apps/docs/content/docs/cli/configuration.mdx index c089ded0881..80e8e331363 100644 --- a/apps/docs/content/docs/cli/configuration.mdx +++ b/apps/docs/content/docs/cli/configuration.mdx @@ -123,11 +123,13 @@ export default definePrismaConfig({ }); ``` -`prisma7Schema` takes one schema file, or a directory of `.prisma` files as Prisma ORM 7 reads it. A `DateTime` field, and a field with `@db.Timestamp`, `@db.Timestamptz`, `@db.Date`, or `@db.Time`, reads and writes strings such as `"2026-09-14 10:00:00.123"`, the text PostgreSQL prints, not `Temporal` values, so your application needs no `Temporal`. A `DateTime` value is in UTC, as Prisma ORM 7 writes it. To get `Temporal` values, write a Prisma ORM 8 contract with `npx prisma contract print --output prisma/contract.prisma` and change the types there. It reads every `.prisma` file it is given, so these files do not need the `// use prisma-8` first line. `contract emit` writes `contract.json` and `contract.d.ts` into the directory that holds the schema, here `prisma/`, unless you set `output`. Prisma ORM 7 keeps owning the database and its migrations, and you run its commands as `prisma7`, the Prisma ORM 7 CLI that [`orm init --from-prisma7-schema`](/cli/orm-init#on-a-prisma-orm-7-project) installs next to Prisma ORM 8. +`prisma7Schema` takes one schema file, or a directory of `.prisma` files as Prisma ORM 7 reads it. A `DateTime` field, and a field with `@db.Timestamp`, `@db.Timestamptz`, `@db.Date`, or `@db.Time`, reads and writes strings such as `"2026-09-14 10:00:00.123"`, the text PostgreSQL prints, not `Temporal` values, so your application needs no `Temporal`. A `DateTime` value is in UTC, as Prisma ORM 7 writes it. An `@updatedAt` field is filled with such text from your application's clock, also on a `@db.Date`, `@db.Time`, or `@db.Timetz` field, and it may sit beside `@default(now())`, which stays the database default. A list such as `String[]` reads as a list whose elements may be `null`, because the database Prisma ORM 7 built has no check against `null` elements. To get `Temporal` values, write a Prisma ORM 8 contract with `npx prisma contract print --output prisma/contract.prisma` and change the types there. It reads every `.prisma` file it is given, so these files do not need the `// use prisma-8` first line. `contract emit` writes `contract.json` and `contract.d.ts` into the directory that holds the schema, here `prisma/`, unless you set `output`. Prisma ORM 7 keeps owning the database and its migrations, and you run its commands as `prisma7`, the Prisma ORM 7 CLI that [`orm init --from-prisma7-schema`](/cli/orm-init#on-a-prisma-orm-7-project) installs next to Prisma ORM 8. Once the config is in place, run `npx prisma contract emit` and then [`npx prisma db sign`](/cli/db-sign). `db sign` checks that the database matches the contract and then records that in the database, without changing your tables. Run both commands again after each Prisma ORM 7 migration, whether you ran `npx prisma7 migrate dev` or `npx prisma7 migrate deploy`, so that the record names the new contract. -`contract emit` fails on any part of the schema that Prisma ORM 8 cannot represent exactly, such as a `view` block, `Unsupported(...)`, or `relationMode = "prisma"`. The error names the file and the line and suggests an edit to the Prisma ORM 7 schema. When that edit would also change the database on the next Prisma ORM 7 migration, the error says so. For example, removing an `Unsupported(...)` field drops its column, so for that field the error suggests adding `@@ignore` to the model instead, which leaves the next Prisma ORM 7 migration empty but removes the model from the Prisma ORM 7 client. Prisma ORM 8 has no views, so for a `view` block the error suggests removing the view or replacing it with a model over the table the view reads. `orm init --from-prisma7-schema` writes this config for you. +An `Unsupported("ltree")` field becomes a column of that database type with no field on the model, as in the Prisma ORM 7 client: migrations and `db verify` keep the column, and your code cannot read or write it. `contract print` cannot write such a column yet, so a schema with `Unsupported(...)` fields stays your contract source. + +`contract emit` fails on any part of the schema that Prisma ORM 8 cannot represent exactly, such as a `view` block or `relationMode = "prisma"`. The error names the file and the line and suggests an edit to the Prisma ORM 7 schema. When that edit would also change the database on the next Prisma ORM 7 migration, the error says so. A default function argument Prisma ORM 7 read as something else, such as `uuid(5)` or `cuid(1)`, is refused with `PSL.PRISMA7_UNKNOWN_DEFAULT`, and the message names the value to write. Prisma ORM 8 has no views, so for a `view` block the error suggests removing the view or replacing it with a model over the table the view reads. `orm init --from-prisma7-schema` writes this config for you. ## Use a Prisma ORM 6 MongoDB schema diff --git a/apps/docs/content/docs/cli/contract-emit.mdx b/apps/docs/content/docs/cli/contract-emit.mdx index 305e66fadc3..b6f0c5d4513 100644 --- a/apps/docs/content/docs/cli/contract-emit.mdx +++ b/apps/docs/content/docs/cli/contract-emit.mdx @@ -52,6 +52,8 @@ npx prisma db sign --db "$DATABASE_URL" The script runs on any Node.js version Prisma ORM supports: 22.18 or newer on the 22 line, 24.11 or newer on the 24 line, or 26 or newer. It rewrites every contract, snapshot, migration, ref, `migration.ts`, and `contract.d.ts` in the project, and every storage hash changes once. Until a database is signed, `db migrate` refuses with `MIGRATION.MARKER_MISMATCH`. Commit your work before you run it, so you can review its changes. +Since `8.0.0-rc.19`, `contract.d.ts` writes field and column types as generics applied to the contract, so a SQL `contract.d.ts` emitted by an earlier release no longer compiles. Run `contract emit` after you upgrade. The same emit stores each PostgreSQL default in the one form its column's type gives, such as `"1.50"` on a `numeric(10,2)` column, and a MongoDB model's stored field names under `storage.fields`; the CLI refuses a contract without them with `CONTRACT.VALIDATION_FAILED`. Then run [`db sign`](/cli/db-sign) against every database whose storage hash changed. The [upgrade instructions](https://github.com/prisma/orm/blob/v8.0.0-rc.19/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.18-to-8.0.0-rc.19/) include scripts for the contracts stored under `migrations/`. + When the contract source cannot be read, the command fails with `CONTRACT.SOURCE_LOAD_FAILED` and prints each finding, meaning each problem it found in the source, with the file and line where it knows them. In `--json` output, the findings are the entries of the `envelope.diagnostics` array in the final `result` event, next to `envelope.error`. Each entry has a `code`, a `summary`, and, where known, `where.path` and `where.line`. If an entry's `code` is `CONTRACT.SOURCE_DIAGNOSTIC`, the code of the specific problem, such as `PSL_NO_OPTED_IN_SCHEMA_FILES`, is in its `meta.code`. A warning in the contract source, such as `PSL_DEPRECATED_SCALAR_NAME` for a MongoDB scalar type name that is deprecated, does not stop the command. It is reported as a diagnostic whose `severity` is `warn`, and the command still writes both files. In `--json` output, the warning is an entry of `envelope.diagnostics` in the final `result` event, with `code` set to `CONTRACT.SOURCE_DIAGNOSTIC` and the warning's own code in `meta.code`. diff --git a/apps/docs/content/docs/cli/contract-infer.mdx b/apps/docs/content/docs/cli/contract-infer.mdx index c78437fc98e..64376be0f11 100644 --- a/apps/docs/content/docs/cli/contract-infer.mdx +++ b/apps/docs/content/docs/cli/contract-infer.mdx @@ -45,9 +45,11 @@ Inference gives you a starting point, not a finished design. Review: The file starts with `// use prisma-8`, because `contract emit` reads only `.prisma` files that start with that line. Each model is named after its table, and gets `@@map` only when the table name differs from the model name: a table `User` becomes `model User`, and a table `user_profile` becomes `model UserProfile` with `@@map("user_profile")`. -Every column default is written in a form `contract emit` accepts, so you can emit the file without editing it. [Default values](/orm/contract-authoring/psl-syntax#default-values) shows those forms. A list column that allows `NULL` is written `Type[]?`, and a list column that is `NOT NULL` is written `Type[]`. +Every column default is written in a form `contract emit` accepts, so you can emit the file without editing it. [Default values](/orm/contract-authoring/psl-syntax#default-values) shows those forms. A list column that allows `NULL` is written `Type[]?`, and a list column that is `NOT NULL` is written `Type[]`. A list column without the check Prisma ORM adds against `NULL` elements is written with `?` on the element type, as `Type?[]`. -Date and time columns are written as text types, so your application reads and writes them as strings and needs no `Temporal`: a `timestamp(3)` column is written `TimestampString(3)`, `timestamptz(3)` is `TimestamptzString(3)`, `date` is `DateString`, and `time(3)` is `TimeString(3)`. A `timestamp` value reads as `"2026-09-14 10:00:00.123"`. To get `Temporal` values instead, change the types to `Timestamp(3)`, `Timestamptz(3)`, `Date`, and `Time(3)` in the inferred file. A `bytea` default is written as the base64 text the `Bytes` type stores, such as `@default("aGVsbG8=")`. +Each column's type is read from the data types of the configured target and the extension packs in `prisma.config.ts`, and written as the shortest type constructor that matches it, such as `VarChar(20)`, `Bit(8)`, `VarBit(16)`, or `Interval(3)`. When columns have types that no pack declares, the command fails once and lists every one of them, instead of writing a contract that `contract emit` cannot read. Add the pack that declares the type to `extensions`. A contract you write by hand can declare such a column as ``Raw(sql`...`)``, as [Columns of any database type](/orm/contract-authoring/psl-syntax#columns-of-any-database-type) shows. + +Date and time columns are written as text types, so your application reads and writes them as strings and needs no `Temporal`: a `timestamp(3)` column is written `TimestampString(3)`, `timestamptz(3)` is `TimestamptzString(3)`, `date` is `DateString`, and `time(3)` is `TimeString(3)`. A `timestamp` value reads as `"2026-09-14 10:00:00.123"`. To get `Temporal` values instead, change the types to `Timestamp(3)`, `Timestamptz(3)`, `Date`, and `Time(3)` in the inferred file. A `bytea` default is written as a `bytea` tagged literal in the hexadecimal PostgreSQL prints, such as ``@default(bytea`\x68656c6c6f`)``. A contract inferred before `8.0.0-rc.15` keeps its types until you run `contract infer` again. When you do, the date and time fields and any `bytea` default change, and so does the storage hash: emit the contract, update the code that used `Temporal` values for these fields, then run `prisma db sign` for a database you keep with `db update`, or create an empty migration with [`migration new`](/cli/migration-new) and apply it when you use migrations. diff --git a/apps/docs/content/docs/cli/contract-print.mdx b/apps/docs/content/docs/cli/contract-print.mdx index 61982cb83fc..1557d65c7a9 100644 --- a/apps/docs/content/docs/cli/contract-print.mdx +++ b/apps/docs/content/docs/cli/contract-print.mdx @@ -44,6 +44,10 @@ npx prisma contract print > contract.v8.prisma The file starts with `// use prisma-8`. When you point `contract` at the file and run `contract emit`, you get the same contract as before, with the same hashes, so the database needs no migration and no new `db sign`. +A value the client generates is written as `@onCreate(...)` and `@onUpdate(...)`, after the field's type. A Prisma ORM 7 `@default(uuid())` prints as `@onCreate(uuid())`, and a field set from your application's clock on create and update prints as, for example, `Timestamptz(3) @onCreate(now()) @onUpdate(now())`. A date, time, or bytes default prints as a tagged literal, and a column of a database type no field type covers prints as ``Raw(sql`...`)``. + +A Prisma ORM 7 schema with an `Unsupported(...)` field cannot be printed yet: its column has no field, and Prisma ORM 8 PSL cannot declare one. Keep the Prisma ORM 7 schema as the contract source until then. + When a part of the contract has no PSL form, the command names that part, fails, and writes nothing. It also refuses an `--output` path that is one of the files it reads, `prisma.config.ts`, or a file `contract emit` writes. A warning in the contract source, such as `PSL_DEPRECATED_SCALAR_NAME`, does not stop the command. It is reported as a diagnostic whose `severity` is `warn`. In `--json` output, the warning is an entry of the result's `diagnostics`, with `code` set to `CONTRACT.SOURCE_DIAGNOSTIC` and the warning's own code in `meta.code`. diff --git a/apps/docs/content/docs/cli/db-schema.mdx b/apps/docs/content/docs/cli/db-schema.mdx index 02b7ea0e7fb..a4a4b97057d 100644 --- a/apps/docs/content/docs/cli/db-schema.mdx +++ b/apps/docs/content/docs/cli/db-schema.mdx @@ -31,6 +31,22 @@ npx prisma db schema --db "$DATABASE_URL" npx prisma db schema --db "$DATABASE_URL" --json > schema.json ``` +## Column types + +The tree and the JSON output name each column's type as the database reports it. In the JSON output it is the column's `typeText`: + +```bash +npx prisma db schema --db "$DATABASE_URL" --json | jq '.schema.tables[].columns[].typeText' +``` + +On PostgreSQL, `typeText` is the text `format_type` prints, such as `integer` or `character varying(20)`, not a short name such as `int4`. It differs from what the database prints in three cases: + +- On SQLite, the declared type is lower-cased: a column declared `VARCHAR(255)` reads `varchar(255)`. +- On PostgreSQL, an enum is named as a contract names it: `My Mood` where `format_type` prints `"My Mood"`, and `app.status` for an enum outside `public`. +- On PostgreSQL, a type in a schema that is not on the search path keeps its schema: `extensions.vector(3)` for a pgvector column when `extensions` is not on the search path. + +Before `8.0.0-rc.19`, the JSON output had `nativeType` and `resolvedNativeType` instead of `typeText`. + ## Related commands Use [`contract infer`](/cli/contract-infer) when you want to turn a live schema into a starter PSL contract. Use [`db verify`](/cli/db-verify) when you want to compare the live schema with the emitted contract. diff --git a/apps/docs/content/docs/cli/db-sign.mdx b/apps/docs/content/docs/cli/db-sign.mdx index 1c352d9cb2c..d340854ad7d 100644 --- a/apps/docs/content/docs/cli/db-sign.mdx +++ b/apps/docs/content/docs/cli/db-sign.mdx @@ -104,5 +104,8 @@ Use `db sign` only after you believe the live database already matches the emitt - `contract infer` for an existing database - a manually reviewed migration flow - a database restore that you need to mark as matching the current contract +- a Prisma ORM upgrade that changes your contract's storage hash but not the database + +Upgrading to `8.0.0-rc.19` changes the storage hash of a SQLite contract with a datetime or JSON column, of a PostgreSQL contract whose defaults `contract emit` rewrites, and of a contract that used `@noCheck`. The database still holds the old hash, so `db migrate` fails with `MIGRATION.MARKER_MISMATCH` and `db verify` with `CONTRACT.MARKER_MISMATCH` until you sign. After you emit the new contract, run `db sign` against every database: development, staging, and production. Do not use `db sign` to hide drift. If verification fails, `db sign` writes no signature and offers two ways out. One changes the database: run `prisma db update` to make the database match the contract, then sign again. The other leaves the database as it is: change the contract source to describe the database, run `contract emit` again, then sign again. diff --git a/apps/docs/content/docs/cli/db-verify.mdx b/apps/docs/content/docs/cli/db-verify.mdx index 2e0d41c953d..a14984f10ea 100644 --- a/apps/docs/content/docs/cli/db-verify.mdx +++ b/apps/docs/content/docs/cli/db-verify.mdx @@ -61,11 +61,21 @@ npx prisma db verify --db "$DATABASE_URL" --json Fix the database or contract, emit again if needed, then verify again. +## How column types are compared + +`db verify` compares a column's type as a data type with its parameters, not as text. It reads each database column's type, finds the data type of your target or extensions that declares it, and compares that with your contract. So `Numeric(10)` in your contract matches a `numeric(10,0)` column, and `VarChar(20)` matches `character varying(20)`. The migration planner compares types the same way. + +A database column whose type no data type of your target or extensions declares gets a message that says so, such as `The database column is "point", which no data type in the configured stack declares.` Add the extension that provides the type to your config, or declare the column with ``Raw(sql`point`)``. + ## How PostgreSQL defaults are compared When a column default is a literal value, `db verify` reads it as a value before it compares it with your contract, so the casts PostgreSQL adds when it prints the default do not cause a difference. This covers numbers such as `'-1'::integer` and `(5)::smallint`, text and enum values, including an enum value cast to a type in another PostgreSQL schema, `timestamp` values, `true`, `false`, `NULL`, JSON, and lists written as `'{...}'` or `ARRAY[...]`. `db verify` also recognizes `now()`, `clock_timestamp()`, `gen_random_uuid()`, and a sequence default, which it treats as `autoincrement()`. -Any other default is a SQL expression, which you write in your contract as a `sql` default, such as ``@default(sql`(now() + '00:03:00'::interval)`)``. [Default values](/orm/contract-authoring/psl-syntax#default-values) shows how to write one. `db verify` cannot work out whether two SQL expressions give the same value, so it compares their text, ignoring letter case and spaces. PostgreSQL often rewrites an expression when it stores it, for example by adding casts, so write a `sql` default in your contract the way PostgreSQL stores it. To see that form, run `npx prisma contract infer --output ./inferred.prisma` and copy the column's `@default` from that file. +Any other default is a SQL expression, which you write in your contract as a `sql` default, such as ``@default(sql`(now() + '00:03:00'::interval)`)``. [Default values](/orm/contract-authoring/psl-syntax#default-values) shows how to write one. `db verify` cannot work out whether two SQL expressions give the same value, so it compares their text, ignoring letter case and spaces. PostgreSQL often rewrites an expression when it stores it, for example by adding casts, so write a `sql` default in your contract the way PostgreSQL stores it. To see that form, run `npx prisma contract infer --output ./inferred.prisma` and copy the column's `@default` from that file. When a `sql` default fails the check because PostgreSQL stores it in another form, the message shows both texts and the `sql` literal to write: + +```text +The database reads this default back as sql`(now() - '1 day'::interval)`; the contract declares sql`now() - interval '1 day'`. The database may store a default expression in its own form, so the two can mean the same. If they do, write the default as sql`(now() - '1 day'::interval)`. +``` Check constraints and the `WHERE` clause of a partial index are compared more strictly than `sql` defaults: their text must match exactly, including letter case and spaces. diff --git a/apps/docs/content/docs/cli/migration-plan.mdx b/apps/docs/content/docs/cli/migration-plan.mdx index f512dbd4e63..642ca043975 100644 --- a/apps/docs/content/docs/cli/migration-plan.mdx +++ b/apps/docs/content/docs/cli/migration-plan.mdx @@ -42,9 +42,15 @@ npx prisma migration plan --name add_users_table | `--name ` | Sets the migration directory name suffix. | | `--from ` | Uses a specific starting contract state instead of the `db` ref. Accepts a [contract reference](/orm/migrations/the-migration-graph#contract-reference-forms): a hash, a hash prefix, a [ref name](#contract-references), a migration directory name, [`^`](#contract-references), or [`@empty`](#contract-references). `@db` is not accepted here, because `migration plan` is offline and cannot read a database. `@contract` is not accepted either. | | `--to ` | Sets the destination contract state. Defaults to the emitted contract. Accepts the same forms as `--from`, except `@empty`, which is refused as a destination. | +| `--rename :` | Plans a model or field rename that keeps its rows, such as `--rename User.name:User.fullName`, instead of a drop and a create. Repeatable. | +| `--delete ` | Lets a dropped model or field lose its data, such as `--delete User.legacy`. Repeatable. | +| `--convert ` | For a type change that can lose values: writes a slot in `migration.ts` for the conversion you write yourself. | +| `--backfill ` | For a new required field: writes a slot in `migration.ts` to fill existing rows yourself. | | `--config ` | Read this config file instead of `./prisma.config.ts`. | | `--json` | Prints a machine-readable result. | +On MongoDB, `--rename` and `--delete` name fields by their names in the schema, not by the names they are stored under. For a field `id ObjectId @id @map("_id")`, write `--rename User.id:User.userId`, not `User._id`. The first migration you plan after upgrading to `8.0.0-rc.19` starts from a contract snapshot that the upgrade script rewrote, which names each field by its stored name, so in that one migration name the old field by its stored name. + ### Contract references [#contract-references] A ref name is a name you gave a contract state with [`migration ref set`](/cli/migration-ref), such as `prod`. `^` is a migration directory name followed by `^`, and it names the contract state before that migration, while the directory name alone names the state after it. `@empty` names the empty database, before any migration. [Contract references each command accepts](/orm/migrations/the-migration-graph#contract-reference-forms) lists the forms for every command. diff --git a/apps/docs/content/docs/cli/orm-init.mdx b/apps/docs/content/docs/cli/orm-init.mdx index 89f019325f5..4d6c906cf2d 100644 --- a/apps/docs/content/docs/cli/orm-init.mdx +++ b/apps/docs/content/docs/cli/orm-init.mdx @@ -45,7 +45,7 @@ The exact files depend on the target and authoring style, but a Postgres PSL set - `prisma.config.ts` - a starter contract file such as `src/prisma/contract.prisma`, whose first line is `// use prisma-8`. Keep that line, because `contract emit` reads only `.prisma` files that start with it. - emitted contract artifacts after installation runs -- a runtime client file (`src/prisma/db.ts`) that imports `contract.json` +- a runtime client file (`src/prisma/db.ts`) that imports `contract.json`, turns it into a typed contract value with `contractFromJson`, and creates the client from that value - `.env.example`, `tsconfig.json`, and updated `package.json` - `prisma-8.md`, a short quick reference for writing your first typed query diff --git a/apps/docs/content/docs/guides/database/multiple-databases.mdx b/apps/docs/content/docs/guides/database/multiple-databases.mdx index caaaf6a37a1..c094d7970ed 100644 --- a/apps/docs/content/docs/guides/database/multiple-databases.mdx +++ b/apps/docs/content/docs/guides/database/multiple-databases.mdx @@ -126,11 +126,14 @@ Change `prisma/users/db.ts` to read `USERS_DATABASE_URL` and export a name you c ```typescript title="prisma/users/db.ts" import 'dotenv/config'; import postgres from '@prisma/orm-postgres/runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const usersDb = postgres({ // [!code ++] - contractJson, +export const contract = contractFromJson(contractJson); + +export const usersDb = postgres({ // [!code ++] + contract, url: process.env.USERS_DATABASE_URL!, // [!code ++] }); ``` @@ -180,11 +183,14 @@ model Post { ```typescript title="prisma/posts/db.ts" import 'dotenv/config'; import postgres from '@prisma/orm-postgres/runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const postsDb = postgres({ - contractJson, +export const contract = contractFromJson(contractJson); + +export const postsDb = postgres({ + contract, url: process.env.POSTS_DATABASE_URL!, }); ``` diff --git a/apps/docs/content/docs/guides/deployment/bun-workspaces.mdx b/apps/docs/content/docs/guides/deployment/bun-workspaces.mdx index 70cd3f63c9b..cafb97abda4 100644 --- a/apps/docs/content/docs/guides/deployment/bun-workspaces.mdx +++ b/apps/docs/content/docs/guides/deployment/bun-workspaces.mdx @@ -114,11 +114,14 @@ The generated client reads `DATABASE_URL` from the environment and loads `.env` ```ts title="packages/database/src/prisma/db.ts" import 'dotenv/config'; import postgres from '@prisma/orm-postgres/runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const db = postgres({ - contractJson, +export const contract = contractFromJson(contractJson); + +export const db = postgres({ + contract, url: process.env['DATABASE_URL']!, }); ``` diff --git a/apps/docs/content/docs/guides/deployment/cloudflare-workers.mdx b/apps/docs/content/docs/guides/deployment/cloudflare-workers.mdx index 54b61a16429..2e51b1762f2 100644 --- a/apps/docs/content/docs/guides/deployment/cloudflare-workers.mdx +++ b/apps/docs/content/docs/guides/deployment/cloudflare-workers.mdx @@ -39,7 +39,7 @@ Add Prisma ORM to a new Cloudflare Worker, query PostgreSQL from it, and prepare 1. Scaffold: `npm create cloudflare@latest prisma-cloudflare-worker -- --type=hello-world --lang=ts --git --no-deploy --no-agents`. In `prisma-cloudflare-worker`, run `npx prisma@latest orm init --yes --target postgres --authoring psl`, then `npx prisma@latest init` so the Prisma agent skills are installed and stay current, and use them. 2. Add `"compatibility_flags": ["nodejs_compat"]` to `wrangler.jsonc`. Get a database connection string: use the one I give you, or create a Prisma Postgres database with `npx create-db@latest` and show me the claim URL it prints. Write it to `.env` as `DATABASE_URL`; both the Prisma CLI and `wrangler dev` read that file. 3. Run `npx prisma db init` to create the tables from `src/prisma/contract.prisma`. -4. Replace `src/index.ts` following https://www.prisma.io/docs/guides/deployment/cloudflare-workers.md: create one `postgres({ contractJson, url: env.DATABASE_URL })` client per request inside `fetch`, run the queries, and close it with `ctx.waitUntil(db.close())`. Do not import the module-level client from `src/prisma/db.ts` in the Worker; a connection shared across requests hangs the second request. Run `npx wrangler types` so `Env` includes `DATABASE_URL`. +4. Replace `src/index.ts` following https://www.prisma.io/docs/guides/deployment/cloudflare-workers.md: turn the emitted JSON into `const contract = contractFromJson(contractJson)` (from `@prisma/orm-postgres/static`) once at module level, without exporting it, and create one `postgres({ contract, url: env.DATABASE_URL })` client per request inside `fetch`, run the queries, and close it with `ctx.waitUntil(db.close())`. Do not import the module-level client from `src/prisma/db.ts` in the Worker; a connection shared across requests hangs the second request. Run `npx wrangler types` so `Env` includes `DATABASE_URL`. 5. Start `npm run dev` in the background, wait until it reports ready, verify `curl http://localhost:8787` twice returns a created user and a growing count, then stop the dev server. 6. Deploy: check `npx wrangler whoami`; if I am not logged in, stop and ask me to run `npx wrangler login`. Then run `npx wrangler secret put DATABASE_URL` with the production connection string and `npm run deploy`, and verify the live URL. ``` @@ -155,15 +155,18 @@ Replace `src/index.ts` with a handler that creates a user and counts all users: ```typescript title="src/index.ts" import postgres from "@prisma/orm-postgres/runtime"; +import { contractFromJson } from "@prisma/orm-postgres/static"; import type { Contract } from "./prisma/contract.d"; import contractJson from "./prisma/contract.json" with { type: "json" }; +const contract = contractFromJson(contractJson); + export default { async fetch(request, env, ctx): Promise { const path = new URL(request.url).pathname; if (path === "/favicon.ico") return new Response("Not found", { status: 404 }); - const db = postgres({ contractJson, url: env.DATABASE_URL }); + const db = postgres({ contract, url: env.DATABASE_URL }); try { const user = await db.orm.public.User.create({ email: `user-${Math.ceil(Math.random() * 1000)}@prisma.io`, @@ -185,7 +188,7 @@ Three things to notice: - The client is created inside `fetch`, with the connection string from `env`, and closed after the response with `ctx.waitUntil(db.close())`. This is the opposite of a Node.js server, where you keep one client for the life of the process. Workers do not let a socket opened during one request be used by another, so a module-level client works for the first request and hangs the next one. That is why the Worker does not import `src/prisma/db.ts`. - Models are namespace-qualified on PostgreSQL: `db.orm.public.User`. `.create(...)` returns the inserted row, database defaults included. -- `contract.json` is imported with `with { type: "json" }`. Wrangler bundles it into the Worker. +- `contract.json` is imported with `with { type: "json" }`. Wrangler bundles it into the Worker. `contractFromJson` turns it into a typed contract value once, at module level, and every request's client takes that value. The value is not exported, because a Worker treats every named export of its entry module as an entrypoint. If another module needs it, declare it in a separate module and import it from there. ### 3.1. Generate the Env types @@ -275,7 +278,7 @@ Add the binding it prints to `wrangler.jsonc`. The `localConnectionString` is wh Run `npx wrangler types` again, then pass the Hyperdrive connection string to the client instead of `env.DATABASE_URL`: ```typescript title="src/index.ts" -const db = postgres({ contractJson, url: env.HYPERDRIVE.connectionString }); +const db = postgres({ contract, url: env.HYPERDRIVE.connectionString }); ``` Everything else in the handler stays the same. The `localConnectionString` must include a password, even for a local server that does not check one; otherwise `wrangler dev` refuses to start with `You must provide a password`. @@ -296,7 +299,7 @@ Create the client inside `fetch` and close it with `ctx.waitUntil(db.close())`, :::info -`@prisma/orm-postgres/serverless` exports `postgresServerless`, a client built for serverless runtimes. It runs in Workers with `nodejs_compat`. Create it once at module level, as `const postgres = postgresServerless({ contractJson })`. It holds no database connection. Inside `fetch`, open a connection for the request with `await using db = await postgres.connect({ url: env.DATABASE_URL })`. The connection has `db.orm`, `db.sql`, and `db.transaction(...)`, like the client this guide creates, and `await using` closes it when the request scope ends, so await every query before then. `connect()` opens the connection before it returns, and rejects with `DRIVER.CONNECTION_FAILED` when the database cannot be reached. Reads load the whole result unless you create the serverless client with a `cursor` option, and reads through a cursor hang behind Hyperdrive. See [Serverless client on PostgreSQL](/orm/reference/transactions-and-runtime#serverless-client-on-postgresql). +`@prisma/orm-postgres/serverless` exports `postgresServerless`, a client built for serverless runtimes. It runs in Workers with `nodejs_compat`. Create it once at module level, as `const postgres = postgresServerless({ contract })`, from the same `contract` value. Several serverless clients, for example one with a `cursor` option and one without, share that one value. It holds no database connection. Inside `fetch`, open a connection for the request with `await using db = await postgres.connect({ url: env.DATABASE_URL })`. The connection has `db.orm`, `db.sql`, and `db.transaction(...)`, like the client this guide creates, and `await using` closes it when the request scope ends, so await every query before then. `connect()` opens the connection before it returns, and rejects with `DRIVER.CONNECTION_FAILED` when the database cannot be reached. Reads load the whole result unless you create the serverless client with a `cursor` option, and reads through a cursor hang behind Hyperdrive. See [Serverless client on PostgreSQL](/orm/reference/transactions-and-runtime#serverless-client-on-postgresql). ::: diff --git a/apps/docs/content/docs/guides/deployment/pnpm-workspaces.mdx b/apps/docs/content/docs/guides/deployment/pnpm-workspaces.mdx index 9df090be55d..470e8d8d616 100644 --- a/apps/docs/content/docs/guides/deployment/pnpm-workspaces.mdx +++ b/apps/docs/content/docs/guides/deployment/pnpm-workspaces.mdx @@ -127,11 +127,14 @@ pnpm prisma contract emit ```typescript title="packages/database/src/prisma/db.ts" import 'dotenv/config'; import postgres from '@prisma/orm-postgres/runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const db = postgres({ - contractJson, +export const contract = contractFromJson(contractJson); + +export const db = postgres({ + contract, url: process.env['DATABASE_URL']!, }); ``` diff --git a/apps/docs/content/docs/guides/deployment/turborepo.mdx b/apps/docs/content/docs/guides/deployment/turborepo.mdx index d2eab5dbdde..2d62b5896f7 100644 --- a/apps/docs/content/docs/guides/deployment/turborepo.mdx +++ b/apps/docs/content/docs/guides/deployment/turborepo.mdx @@ -159,11 +159,14 @@ The generated `db.ts` reads `DATABASE_URL` from the environment and is the modul ```typescript title="packages/database/src/prisma/db.ts" import 'dotenv/config'; import postgres from '@prisma/orm-postgres/runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const db = postgres({ - contractJson, +export const contract = contractFromJson(contractJson); + +export const db = postgres({ + contract, url: process.env['DATABASE_URL']!, }); ``` diff --git a/apps/docs/content/docs/guides/frameworks/solid-start.mdx b/apps/docs/content/docs/guides/frameworks/solid-start.mdx index 2b4640b60ec..ea8729f4003 100644 --- a/apps/docs/content/docs/guides/frameworks/solid-start.mdx +++ b/apps/docs/content/docs/guides/frameworks/solid-start.mdx @@ -126,11 +126,14 @@ And the scaffolded client is all the wiring the app needs. There is no `prisma g ```typescript title="src/prisma/db.ts" import 'dotenv/config'; import postgres from '@prisma/orm-postgres/runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const db = postgres({ - contractJson, +export const contract = contractFromJson(contractJson); + +export const db = postgres({ + contract, url: process.env['DATABASE_URL']!, }); ``` @@ -395,7 +398,7 @@ project: ~/my-solid-prisma-app check: enabled Skill Package Version Installed into -prisma-8 @prisma/orm-postgres 8.0.0-rc.18 .claude/skills, .cursor/skills, .agents/skills, .devin/skills +prisma-8 @prisma/orm-postgres 8.0.0-rc.19 .claude/skills, .cursor/skills, .agents/skills, .devin/skills prisma-composer-core-concepts @prisma/composer 0.29.1 .claude/skills, .cursor/skills, .agents/skills, .devin/skills prisma-platform-core-concepts prisma 8.0.0-rc.24 .claude/skills, .cursor/skills, .agents/skills, .devin/skills diff --git a/apps/docs/content/docs/guides/integrations/github-actions.mdx b/apps/docs/content/docs/guides/integrations/github-actions.mdx index b891c365094..4117a31694f 100644 --- a/apps/docs/content/docs/guides/integrations/github-actions.mdx +++ b/apps/docs/content/docs/guides/integrations/github-actions.mdx @@ -78,11 +78,14 @@ Those two emitted files are the whole client. There is no generated client packa ```typescript title="src/prisma/db.ts" no-copy import 'dotenv/config'; import postgres from '@prisma/orm-postgres/runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const db = postgres({ - contractJson, +export const contract = contractFromJson(contractJson); + +export const db = postgres({ + contract, url: process.env['DATABASE_URL']!, }); ``` diff --git a/apps/docs/content/docs/guides/making-guides.mdx b/apps/docs/content/docs/guides/making-guides.mdx index e39c0bebdea..d34db6cd63e 100644 --- a/apps/docs/content/docs/guides/making-guides.mdx +++ b/apps/docs/content/docs/guides/making-guides.mdx @@ -140,11 +140,14 @@ Show the scaffolded `src/prisma/db.ts` rather than writing a client by hand: ```ts title="src/prisma/db.ts" import "dotenv/config"; import postgres from "@prisma/orm-postgres/runtime"; +import { contractFromJson } from "@prisma/orm-postgres/static"; import type { Contract } from "./contract.d"; import contractJson from "./contract.json" with { type: "json" }; -export const db = postgres({ - contractJson, +export const contract = contractFromJson(contractJson); + +export const db = postgres({ + contract, url: process.env.DATABASE_URL!, }); ``` diff --git a/apps/docs/content/docs/guides/switch-to-prisma-orm/from-drizzle.mdx b/apps/docs/content/docs/guides/switch-to-prisma-orm/from-drizzle.mdx index 468ab1823b2..7daa201d6ef 100644 --- a/apps/docs/content/docs/guides/switch-to-prisma-orm/from-drizzle.mdx +++ b/apps/docs/content/docs/guides/switch-to-prisma-orm/from-drizzle.mdx @@ -218,11 +218,14 @@ export default definePrismaConfig({ ```typescript title="src/prisma/db.ts" import 'dotenv/config'; import postgres from '@prisma/orm-postgres/runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const db = postgres({ - contractJson, +export const contract = contractFromJson(contractJson); + +export const db = postgres({ + contract, url: process.env['DATABASE_URL']!, }); ``` diff --git a/apps/docs/content/docs/guides/switch-to-prisma-orm/from-mongoose.mdx b/apps/docs/content/docs/guides/switch-to-prisma-orm/from-mongoose.mdx index 39853a65afa..d6c21539c51 100644 --- a/apps/docs/content/docs/guides/switch-to-prisma-orm/from-mongoose.mdx +++ b/apps/docs/content/docs/guides/switch-to-prisma-orm/from-mongoose.mdx @@ -205,11 +205,14 @@ It also wrote `src/prisma/db.ts`, the client the rest of the app imports. There ```typescript title="src/prisma/db.ts" import 'dotenv/config'; import mongo from '@prisma/orm-mongo/runtime'; +import { contractFromJson } from '@prisma/orm-mongo/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const db = mongo({ - contractJson, +export const contract = contractFromJson(contractJson); + +export const db = mongo({ + contract, url: process.env['DATABASE_URL']!, }); ``` @@ -286,8 +289,8 @@ model Post { Before you go on, check these differences from Mongoose: -- Prisma ORM reads and writes MongoDB fields by their stored names. `authorId @map("author")` is exposed by the query API as `author`, the key Mongoose stores, and `.include("author")` fills it with the user document the way `populate("author")` does. `db.orm.posts.where({ authorId })` is a type error. -- Mongoose adds a `__v` version key to every document. It is not in the contract and Prisma ORM passes it through on reads. You remove it in step 4, before Prisma ORM adds validators. +- The query API names each field as the contract does. `id ObjectId @id @map("_id")` is `id` in `where`, in rows and in write data, and `authorId ObjectId @map("author")` is `authorId`, although MongoDB stores them as `_id` and `author`. `.include("author")` adds the user document as `author`, the way `populate("author")` does. +- Mongoose adds a `__v` version key to every document. It is not in the contract, so Prisma ORM leaves it out of the rows it returns. You remove it from the documents in step 4, before Prisma ORM adds validators. You do not have to model every collection on day one. Start with the ones the routes you migrate first touch. @@ -344,19 +347,19 @@ const posts = await Post.find({ published: true }).populate("author"); const users = await db.orm.users.orderBy({ name: 1 }).all(); // Find one by id -const user = await db.orm.users.where({ _id: id }).first(); +const user = await db.orm.users.where({ id }).first(); // Find one by field const alice = await db.orm.users.where({ email: "alice@prisma.io" }).first(); -// Create; returns the inserted document with its _id +// Create; returns the inserted document with its id const user = await db.orm.users.create({ email: "alice@prisma.io", name: "Alice", profile: null }); // Update one; returns the updated document -await db.orm.users.where({ _id: id }).update({ name: "New name" }); +await db.orm.users.where({ id }).update({ name: "New name" }); // Delete one -await db.orm.users.where({ _id: id }).delete(); +await db.orm.users.where({ id }).delete(); // Populate a single reference const posts = await db.orm.posts.where({ published: true }).include("author").all(); @@ -407,7 +410,7 @@ app.get("/users", async (_req, res) => { }); app.get("/users/:id", async (req, res) => { - const user = await db.orm.users.where({ _id: req.params.id }).first(); + const user = await db.orm.users.where({ id: req.params.id }).first(); if (!user) return res.status(404).json({ error: "Not found" }); res.json(user); }); @@ -455,10 +458,10 @@ curl http://localhost:3000/users ``` ```json no-copy -[{"profile":{"bio":"Writes about databases"},"__v":0,"_id":"6aa2cd2ed97f8d7357dc9bd1","email":"alice@prisma.io","name":"Alice"},{"profile":null,"__v":0,"_id":"6aa2cd2ed97f8d7357dc9bd2","email":"bob@prisma.io","name":"Bob"}] +[{"profile":{"bio":"Writes about databases"},"id":"6aa2cd2ed97f8d7357dc9bd1","email":"alice@prisma.io","name":"Alice"},{"profile":null,"id":"6aa2cd2ed97f8d7357dc9bd2","email":"bob@prisma.io","name":"Bob"}] ``` -The same documents Mongoose returned, `__v` included, because both read the same collection. Bob's document has no `profile`, and because `profile` is optional in the contract, Prisma ORM returns it as `null`. +The same documents Mongoose returned, because both read the same collection. The id is under `id`, the field name in the contract, and `__v` is left out, because the contract does not declare it. Bob's document has no `profile`, and because `profile` is optional in the contract, Prisma ORM returns it as `null`. ```bash curl http://localhost:3000/posts @@ -475,10 +478,10 @@ curl -X POST http://localhost:3000/users \ ``` ```json no-copy -{"_id":"6aa2d3fcc0a1a4429529ba2c","email":"carol@prisma.io","name":"Carol","profile":null} +{"id":"6aa2d3fcc0a1a4429529ba2c","email":"carol@prisma.io","name":"Carol","profile":null} ``` -`.create(...)` returns the inserted document with its server-assigned `_id`, so the handler needs no second query. +`.create(...)` returns the inserted document with its server-assigned `id`, so the handler needs no second query. ### 3.4. Run both side by side @@ -576,21 +579,21 @@ Document failed validation ## Common gotchas -:::warning[Field names are storage names] +:::warning[Field names are contract names] -The query API uses the field names stored in MongoDB, not the names on the left of `@map`. With `authorId ObjectId @map("author")`, filter with `.where({ author: id })` and read `post.author`. TypeScript rejects the contract-side name. +The ORM query API uses the field names in the contract, not the names in `@map`. With `authorId ObjectId @map("author")`, filter with `.where({ authorId: id })` and read `post.authorId`. TypeScript rejects the stored name. The pipeline builder reads documents as MongoDB stores them, so it uses stored names, such as `_id` and `author`. ::: :::warning[ObjectId filters in the pipeline builder] -In the pipeline builder, `.match((f) => f._id.eq(id))` sends the id as a plain string and matches nothing. Filter ObjectId fields through the ORM, `db.orm.posts.where({ _id: id })` or `db.orm.posts.where(MongoFieldFilter.eq("_id", id))`, which converts the id for you. In a pipeline, pass an `ObjectId` instead, as the [`match()` reference](/orm/reference/pipeline-builder#match) shows. +In the pipeline builder, `.match((f) => f._id.eq(id))` sends the id as a plain string and matches nothing. Filter ObjectId fields through the ORM, `db.orm.posts.where({ id })` or `db.orm.posts.where(MongoFieldFilter.eq("id", id))`, which converts the id for you. In a pipeline, pass an `ObjectId` instead, as the [`match()` reference](/orm/reference/pipeline-builder#match) shows. ::: :::warning[No transactions on MongoDB yet] -The MongoDB client has no `db.transaction(...)`. For multi-document atomicity, pass your own `MongoClient` to `mongo({ contractJson, mongoClient, dbName })` and use the driver's `session.withTransaction(...)`, as shown in the [transactions](/orm/fundamentals/transactions) docs. +The MongoDB client has no `db.transaction(...)`. For multi-document atomicity, pass your own `MongoClient` to `mongo({ contract, mongoClient, dbName })` and use the driver's `session.withTransaction(...)`, as shown in the [transactions](/orm/fundamentals/transactions) docs. ::: diff --git a/apps/docs/content/docs/guides/switch-to-prisma-orm/from-sql-orms.mdx b/apps/docs/content/docs/guides/switch-to-prisma-orm/from-sql-orms.mdx index 9cdaf9ac446..7ee824f1aca 100644 --- a/apps/docs/content/docs/guides/switch-to-prisma-orm/from-sql-orms.mdx +++ b/apps/docs/content/docs/guides/switch-to-prisma-orm/from-sql-orms.mdx @@ -127,11 +127,14 @@ You will use these files during the migration: ```typescript title="src/prisma/db.ts" import 'dotenv/config'; import postgres from '@prisma/orm-postgres/runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const db = postgres({ - contractJson, +export const contract = contractFromJson(contractJson); + +export const db = postgres({ + contract, url: process.env['DATABASE_URL']!, }); ``` diff --git a/apps/docs/content/docs/guides/upgrade-prisma-orm/mongodb.mdx b/apps/docs/content/docs/guides/upgrade-prisma-orm/mongodb.mdx index 44161f21929..1d8d4ba33ef 100644 --- a/apps/docs/content/docs/guides/upgrade-prisma-orm/mongodb.mdx +++ b/apps/docs/content/docs/guides/upgrade-prisma-orm/mongodb.mdx @@ -24,7 +24,7 @@ Install the companion [prisma-mongodb-upgrade](https://github.com/prisma/skills/ :::info -Install the `latest` version of `@prisma/orm-mongo`, which is what `orm init` in step 1 does. This guide was written as of `8.0.0-rc.16`, and MongoDB support in Prisma ORM 8 still changes between release candidates, so a newer version may behave differently from what this guide shows. To see which version you have, run `npm ls @prisma/orm-mongo`. +Install the `latest` version of `@prisma/orm-mongo`, which is what `orm init` in step 1 does. This guide was written as of `8.0.0-rc.19`, and MongoDB support in Prisma ORM 8 still changes between release candidates, so a newer version may behave differently from what this guide shows. To see which version you have, run `npm ls @prisma/orm-mongo`. ::: @@ -76,16 +76,19 @@ Run `npx prisma contract emit` to generate `contract.json` from your `.prisma` c ### Import the client -Instantiate the Prisma client from the emitted contract. Replace `my-app-db` with the database name your v6 connection string uses. If the connection URL already carries the database name in its path, omit `dbName` and the client reads it from the URL: +Instantiate the Prisma client from the emitted contract: `contractFromJson` turns `contract.json` into a typed contract value, and the client takes it as `contract`. Replace `my-app-db` with the database name your v6 connection string uses. If the connection URL already carries the database name in its path, omit `dbName` and the client reads it from the URL: ```typescript tab="After" // src/prisma/db.ts import mongo from '@prisma/orm-mongo/runtime'; +import { contractFromJson } from '@prisma/orm-mongo/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -const db = mongo({ - contractJson, +export const contract = contractFromJson(contractJson); + +const db = mongo({ + contract, url: process.env['DATABASE_URL']!, dbName: 'my-app-db', }); @@ -199,13 +202,13 @@ await db.orm.users.where({ email: 'alice@example.com' }).first(); const user = await db.orm.users.create({ email: 'a@e.com', role: 'author', address: null }); // Update one -await db.orm.users.where({ _id: user._id }).update({ role: 'author' }); +await db.orm.users.where({ id: user.id }).update({ role: 'author' }); // Update many await db.orm.users.where({ role: 'reader' }).updateAll({ role: 'author' }); // Delete one -await db.orm.users.where({ _id: user._id }).delete(); +await db.orm.users.where({ id: user.id }).delete(); ``` ```typescript tab="Before" @@ -368,12 +371,14 @@ const rows = await prisma.post.aggregateRaw({ ```typescript tab="After" // src/prisma/db.ts import mongo from '@prisma/orm-mongo/runtime'; +import { contractFromJson } from '@prisma/orm-mongo/static'; import { MongoClient } from 'mongodb'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; +export const contract = contractFromJson(contractJson); export const mongoClient = new MongoClient(process.env['DATABASE_URL']!); -export const db = mongo({ contractJson, mongoClient, dbName: 'my-app-db' }); +export const db = mongo({ contract, mongoClient, dbName: 'my-app-db' }); // Elsewhere: typed ORM and raw commands from the same client. await db.orm.users.where({ email: 'alice@example.com' }).first(); diff --git a/apps/docs/content/docs/guides/upgrade-prisma-orm/postgresql.mdx b/apps/docs/content/docs/guides/upgrade-prisma-orm/postgresql.mdx index 9f9bcd0b83c..a4ef8068241 100644 --- a/apps/docs/content/docs/guides/upgrade-prisma-orm/postgresql.mdx +++ b/apps/docs/content/docs/guides/upgrade-prisma-orm/postgresql.mdx @@ -14,7 +14,7 @@ The guide covers **PostgreSQL only**. Guidance for other databases will follow. :::info -Step 2.1 installs the `latest` version of both Prisma ORM 8 packages: `prisma`, the Prisma ORM 8 CLI, and `@prisma/orm-postgres`. This guide was written with `prisma` as of `8.0.0-rc.21` and `@prisma/orm-postgres` as of `8.0.0-rc.16`: the CLI is released separately, so the two numbers differ. Prisma ORM 7 stays at `7.10.1`: the example app in step 1.1 starts on it, and step 1.2 installs `@prisma/prisma7@7.10.1`. +Step 2.1 installs the `latest` version of both Prisma ORM 8 packages: `prisma`, the Prisma ORM 8 CLI, and `@prisma/orm-postgres`. This guide was written with `prisma` as of `8.0.0-rc.21` and `@prisma/orm-postgres` as of `8.0.0-rc.19`: the CLI is released separately, so the two numbers differ. Prisma ORM 7 stays at `7.10.1`: the example app in step 1.1 starts on it, and step 1.2 installs `@prisma/prisma7@7.10.1`. ::: @@ -387,6 +387,7 @@ Pick one small route and move only that code. The rest of the application stays import "dotenv/config"; import { PrismaPg } from "@prisma/adapter-pg"; import postgres from "@prisma/orm-postgres/runtime"; // [!code ++] +import { contractFromJson } from "@prisma/orm-postgres/static"; // [!code ++] import type { Contract } from "../generated/prisma8/contract.d"; // [!code ++] import contractJson from "../generated/prisma8/contract.json" with { type: "json" }; // [!code ++] import { PrismaClient } from "../generated/prisma/client"; @@ -397,10 +398,12 @@ const adapter = new PrismaPg({ connectionString }); export const prisma = new PrismaClient({ adapter }); -export const db = postgres({ url: connectionString, contractJson }); // [!code ++] +export const contract = contractFromJson(contractJson); // [!code ++] + +export const db = postgres({ url: connectionString, contract }); // [!code ++] ``` -`prisma` is the Prisma ORM 7 client and `db` is the Prisma ORM 8 client, both connected to the same database. +`prisma` is the Prisma ORM 7 client and `db` is the Prisma ORM 8 client, both connected to the same database. `contractFromJson` turns the emitted `contract.json` into a typed contract value, which the Prisma ORM 8 client takes as `contract`. ### 3.2. Rewrite the route diff --git a/apps/docs/content/docs/orm/coming-from-prisma-orm-7.mdx b/apps/docs/content/docs/orm/coming-from-prisma-orm-7.mdx index 30f6df1940c..c0169bc5f96 100644 --- a/apps/docs/content/docs/orm/coming-from-prisma-orm-7.mdx +++ b/apps/docs/content/docs/orm/coming-from-prisma-orm-7.mdx @@ -54,15 +54,17 @@ The biggest visible change is in field types. Where Prisma ORM 7 wrote a Prisma | `Int`, `Boolean`, `Float`, `BigInt`, `Bytes` | unchanged | | | `String @db.Uuid` | `Uuid` | | | `DateTime`, `DateTime @db.Timestamptz` | `DateTime` | same name, and it is already a `timestamptz` column. Values come back as `Temporal.Instant`, not JavaScript `Date`; `new Date(value.epochMilliseconds)` converts one. See the note below the table | -| `updatedAt DateTime @updatedAt` | `updatedAt temporal.updatedAt()` | write `temporal.updatedAt()` where the type would go. It declares a `DateTime` field and sets it on every create and update. Any field name works. `temporal.` is part of the contract syntax; there is nothing to import | +| `updatedAt DateTime @updatedAt` | `updatedAt DateTime @onCreate(now()) @onUpdate(now())`, or `updatedAt temporal.updatedAt()` | `@onCreate(now())` and `@onUpdate(now())` set the field from your application's clock on create and on every update. `temporal.updatedAt()`, written where the type would go, declares a `DateTime` field that does the same. `temporal.` is part of the contract syntax; there is nothing to import | | `createdAt DateTime @default(now())` | the same, or `createdAt temporal.createdAt()` | both work. `@default(now())` is a database default. `temporal.createdAt()` is set by Prisma ORM from your application's clock and gives the column no database default | -| `String @default(cuid())` | `String @default(cuid(2))` | `2` is the cuid version. Plain `cuid()` is rejected | +| `String @id @default(uuid())` | `String @id @onCreate(uuid())` | `@default` is now only a database default, and a value Prisma ORM generates is written `@onCreate(...)`. The same goes for `uuid(7)`, `ulid()`, and `nanoid()`. `@default(uuid())` is rejected with `PSL_GENERATOR_IN_DEFAULT`. [Generated values](/orm/contract-authoring/psl-syntax#generated-values) lists the functions | +| `String @default(cuid())` | `String @onCreate(cuid(2))` | `2` is the cuid version. Plain `cuid()` is rejected | | `@default(dbgenerated("gen_random_uuid()"))` | ``@default(sql`gen_random_uuid()`)`` | `dbgenerated` is rejected. Write any SQL expression as `` sql`...` `` instead, but keep writing `now()` and `autoincrement()` as plain functions. [Default values](/orm/contract-authoring/psl-syntax#default-values) covers every kind of default | | `Json` | `Jsonb` | Prisma ORM 7's `Json` stored a PostgreSQL `jsonb` column, so write `Jsonb`. In Prisma ORM 8, `Json` means PostgreSQL's older `json` type, which you almost certainly do not want | | `Json @default("{}")` | ``Jsonb @default(json`{}`)`` | write a JSON default as `` json`...` ``. A quoted string is text, and a JSON column does not accept it | +| `DateTime @default("2024-01-01T00:00:00Z")`, `Bytes @default("aGVsbG8=")` | ``DateTime @default(timestamptz`2024-01-01T00:00:00Z`)``, ``Bytes @default(bytea`\x68656c6c6f`)`` | a fixed date, time, or bytes default is a tagged literal named after the column's type. A `bytea` default holds hexadecimal after `\x`, not base64 | | `enum Role { ADMIN USER }`, in a new database | `enum Role { ADMIN USER }` | same syntax. You can give members explicit values (`ADMIN = "admin"`). The column is stored as text, not as a PostgreSQL enum type | | `enum Role { ADMIN USER }` with a field `role Role`, in an existing Prisma ORM 7 database | `native_enum Role { ADMIN = "ADMIN" USER = "USER" }` with the field `role pg.enum(Role)` | your database already has a PostgreSQL enum type for it, and `native_enum` keeps it. See the note below the table | -| `String[]` | `String[]` | unchanged on PostgreSQL. The list filters (`has`, `hasEvery`, `hasSome`, `isEmpty`) are not available; filter on a list with raw SQL for now (see below) | +| `String[]` | `String[]`, or `String?[]` | Prisma ORM 8 adds a check that rejects `NULL` elements in a `String[]` column. Write `String?[]` to allow them, for example when a column Prisma ORM 7 created already holds them. The list filters (`has`, `hasEvery`, `hasSome`, `isEmpty`) are not available; filter on a list with raw SQL for now (see below) | | `@@schema("billing")` | `namespace billing { model ... }` | a `namespace` block is a PostgreSQL schema; wrap the models in it. They become `db.orm.billing.Invoice` and so on | | implicit many-to-many (`Post[]` on `Tag` and `Tag[]` on `Post`, with no model for the join table) | the same two list fields, plus a model for the join table | you write the join table as a model yourself. Example below | | `@id`, `@unique`, `@default`, `@relation`, `@map`, `@@map`, `@@index`, `@@id`, `@@unique` | unchanged | a model without `@@map` names its table after the model, exactly as written, as in Prisma ORM 7 | @@ -204,11 +206,14 @@ There is no generated `PrismaClient`: you create the client once, in `src/prisma ```ts title="src/prisma/db.ts" import "dotenv/config"; import postgres from "@prisma/orm-postgres/runtime"; +import { contractFromJson } from "@prisma/orm-postgres/static"; import type { Contract } from "./contract.d"; import contractJson from "./contract.json" with { type: "json" }; -export const db = postgres({ - contractJson, +export const contract = contractFromJson(contractJson); + +export const db = postgres({ + contract, url: process.env.DATABASE_URL!, }); ``` @@ -220,7 +225,7 @@ export const db = postgres({ - `db.orm`: your models, by model name (`db.orm.public.User`). - `db.sql`: the [SQL query builder](/orm/reference/sql-query-builder). It holds your tables by table name, as in `db.sql.public.User`, and a table's name is its model's name unless you set `@@map`. On this page, the raw SQL examples use it only to give each column's type. - `db.raw.sql`: lets you write a SQL query as text. -- `db.runtime()`: the connection. It runs raw queries. +- `db.query(plan)` and `db.execute(plan)`: run a built query, such as a raw query. `db.toSql(plan)` shows the SQL it would send. - `db.transaction`: runs several queries in one transaction. To add middleware, add a `middleware: [...]` key to the `postgres({ ... })` call above. A middleware is a plain object with a name and one or more hook functions, for example `{ name: "log", async beforeQuery(plan) { console.log(plan.sql); } }`. [How middleware works](/orm/middleware/how-middleware-works) lists the hooks. @@ -313,18 +318,18 @@ const user = db.sql.public.User; const select = db.raw.sql`SELECT id, email FROM "User" WHERE active = true` .returnsRow({ id: user.columns.id, email: user.columns.email }) .build(); -const rows = await db.runtime().query(select); +const rows = await db.query(select); // A statement that changes rows: ask for the count. const update = db.raw.sql`UPDATE "User" SET active = false WHERE id = ${id}` .affectedCount() .build(); -await db.runtime().execute(update); +await db.execute(update); ``` The table is `User`, because a model without `@@map` names its table after the model. As in Prisma ORM 7, a value you put in the SQL with `${...}`, such as `${id}`, is sent to the database as a query parameter. -Every raw query that returns rows needs `.returnsRow(...)` with a type per column, and you take the type from the table, as above. A computed column has no table column to take it from, so write the type name as a string instead: for `SELECT count(*) AS total FROM "User"`, write `.returnsRow({ total: "pg/int8@1" })`. [Raw queries](/orm/reference/raw-queries) lists the type names. Finish the query with `.build()` and pass it to `db.runtime().query()` for rows, or to `db.runtime().execute()` for the number of rows a write changed. On PostgreSQL, `db.runtime()` returns the connection directly, so it needs no `await`. +Every raw query that returns rows needs `.returnsRow(...)` with a type per column, and you take the type from the table, as above. A computed column has no table column to take it from, so write the type name as a string instead: for `SELECT count(*) AS total FROM "User"`, write `.returnsRow({ total: "pg/int8@1" })`. [Raw queries](/orm/reference/raw-queries) lists the type names. Finish the query with `.build()` and pass it to `db.query()` for rows, or to `db.execute()` for the number of rows a write changed. On PostgreSQL, `db.runtime()` returns the connection directly, so it needs no `await`. ### Transaction @@ -403,7 +408,7 @@ Also not available today: const query = db.raw.sql`SELECT id FROM "Post" WHERE ${tag} = ANY(tags)` .returnsRow({ id: post.columns.id }) .build(); - const rows = await db.runtime().query(query); + const rows = await db.query(query); ``` - Transaction options: `isolationLevel`, `timeout`, `maxWait`, and transactions inside transactions. - `omit`, `relationLoadStrategy`, `Prisma.skip`, and the automatic batching of `findUnique` calls. diff --git a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx index bb2ea0349a1..cbcd823fd42 100644 --- a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx @@ -42,7 +42,7 @@ enum Priority { } model User { - id Uuid @id @default(uuid()) + id Uuid @id @onCreate(uuid()) email String createdAt DateTime @default(now()) address Address? @@ -52,7 +52,7 @@ model User { } model Post { - id Uuid @id @default(uuid()) + id Uuid @id @onCreate(uuid()) title ShortName userId Uuid priority Priority @default(Low) @@ -132,46 +132,84 @@ Set `contract` to a glob, such as `"./src/prisma/**/*.prisma"`, to write the con ## Models and fields -Models declare fields with a type, an optional `?` marker, and attributes. [Scalar fields](/orm/data-modeling#scalar-fields) lists the types a field can hold, among them `String`, `Int`, `Boolean`, `Decimal`, `DateTime`, `Json`, and `Bytes`. On PostgreSQL, plain `String` is a `text` column, `Int` is `int4`, and `DateTime` is `timestamptz`. You can also use one of these PostgreSQL types as the field type: `VarChar`, `Char`, `Numeric`, `Timestamp`, `Timestamptz`, `Time`, `Timetz`, `Date`, `Uuid`, `Inet`, `SmallInt`, or `Real`. Their arguments are optional: `VarChar(35)` and `Char(2)` set a length, `Numeric(10, 2)` sets a precision and scale, and `Timestamp(3)`, `Timestamptz(3)`, `Time(3)`, and `Timetz(3)` set how many digits of a second are kept. `Timestamptz` without an argument is the same column as `DateTime`, so use it only when you want to set that precision. +Models declare fields with a type, an optional `?` marker, and attributes. [Scalar fields](/orm/data-modeling#scalar-fields) lists the types a field can hold, among them `String`, `Int`, `Boolean`, `Decimal`, `DateTime`, `Json`, and `Bytes`. On PostgreSQL, plain `String` is a `text` column, `Int` is `int4`, and `DateTime` is `timestamptz`. You can also use one of these PostgreSQL types as the field type: `VarChar`, `Char`, `Numeric`, `Timestamp`, `Timestamptz`, `Time`, `Timetz`, `Date`, `Uuid`, `Inet`, `SmallInt`, `Real`, `Bit`, `VarBit`, or `Interval`. Their arguments are optional: `VarChar(35)`, `Char(2)`, `Bit(8)`, and `VarBit(64)` set a length, `Numeric(10, 2)` sets a precision and scale, and `Timestamp(3)`, `Timestamptz(3)`, `Time(3)`, `Timetz(3)`, and `Interval(3)` set how many digits of a second are kept. Write each argument as a whole number without quotes: `VarChar("35")` and `VarChar(35.0)` are refused with `PSL_VALUE_TYPE_INCOMPATIBLE`. `Timestamptz` without an argument is the same column as `DateTime`, so use it only when you want to set that precision. On PostgreSQL, the date and time types reach your code as `Temporal` values: `DateTime` and `Timestamptz` as a `Temporal.Instant`, `Timestamp` as a `Temporal.PlainDateTime`, `Date` as a `Temporal.PlainDate`, and `Time` as a `Temporal.PlainTime`. Your app needs a global `Temporal` to read and write them, and [Scalar fields](/orm/data-modeling#scalar-fields) says how to check for it. When you want a string instead, use `DateString`, a `date` column your code gets as a string, or `TimestampString`, `TimestamptzString`, and `TimeString`, which do the same for `timestamp`, `timestamptz`, and `time`. `TimestamptzJsDate` is a `timestamptz` column your code gets as a JavaScript `Date`, so it needs no `Temporal`, but a `Date` drops the microseconds PostgreSQL stores. - `@id` marks the primary key. `@@id([a, b])` declares a composite key. - `@unique` adds a unique constraint on one field. `@@unique([userId, title])` adds one across several fields. - `@@index([...])` declares a secondary index. -- `@default(...)` sets a default the database fills in when it is a literal, `now()`, `autoincrement()`, or any other SQL expression. Write the SQL as a `sql` tagged literal, which is `sql` followed by the SQL in backticks, such as ``@default(sql`gen_random_uuid()`)``. See [Default values](#default-values) for how to write each kind of literal. -- `@default(...)` sets a default Prisma ORM generates when it is `uuid()` (a version 4 UUID, the same as `uuid(4)`), `uuid(7)`, `cuid(2)`, `ulid()`, or `nanoid()`, where `nanoid(n)` sets the length, from 2 to 255. Prisma ORM computes the value when it writes the row and the database will not fill it in for you, so a row written by raw SQL or another application gets no value. Prisma ORM 7's `cuid()` is rejected with a hint to use `cuid(2)`. +- `@default(...)` sets a database default: the database fills in the column when an insert leaves it out. The default is a literal, `now()`, `autoincrement()`, or any other SQL expression. Write the SQL as a `sql` tagged literal, which is `sql` followed by the SQL in backticks, such as ``@default(sql`gen_random_uuid()`)``. See [Default values](#default-values) for how to write each kind of literal. +- `@onCreate(...)` sets a value Prisma ORM generates when it creates the row, and `@onUpdate(...)` one it generates on every update. See [Generated values](#generated-values). - `@map("column_name")` sets a field's column name in the database. `@@map("table_name")` sets the table or collection name when it differs from the model name. Without `@@map`, the table or collection has the model's name exactly as written, so `model UserProfile` is the table `"UserProfile"`. - `@@check(expression: "total >= 0", name: "order_total_positive")` adds a check constraint of your own to the table. See [Check constraints](#check-constraints). - `@@control(observed)` says how far Prisma ORM manages the table. See [Control policy](#control-policy). -A field can be a list on PostgreSQL: `tags String[]` is a `text[]` column, and the same works for `Int[]` and the other scalar types. Prisma ORM adds a check constraint to a list column so it cannot hold a `NULL` element. To allow `NULL` elements, put the `?` on the element type: `String?[]` allows `null` elements, `String[]?` allows a `null` list, and `String?[]?` allows both. In your code, `String?[]` is a `ReadonlyArray`, `String[]?` is a `ReadonlyArray | null`, and `String?[]?` is a `ReadonlyArray | null`. To leave the check constraint out of a list that does not allow `NULL` elements, for example when the column already exists without it, add `@noCheck(elementNotNull)` to the field. The field's type does not change. A list of an enum or of a named type works the same way, and so does a list of a value object, as [Value objects](#value-objects) shows. +A field can be a list on PostgreSQL: `tags String[]` is a `text[]` column, and the same works for `Int[]` and the other scalar types. Prisma ORM adds a check constraint to a list column so it cannot hold a `NULL` element. To allow `NULL` elements, put the `?` on the element type: `String?[]` allows `null` elements, `String[]?` allows a `null` list, and `String?[]?` allows both. In your code, `String?[]` is a `ReadonlyArray`, `String[]?` is a `ReadonlyArray | null`, and `String?[]?` is a `ReadonlyArray | null`. A `String?[]` column has no check constraint, so use it for a list column that already exists without one. A list of an enum or of a named type works the same way, and so does a list of a value object, as [Value objects](#value-objects) shows. -`@updatedAt` is gone, so write `temporal.updatedAt()` where the field's type would go: +How IDs map differs by database: + +```prisma tab="PostgreSQL" +model User { + id Uuid @id @onCreate(uuid()) +} +``` + +```prisma tab="MongoDB" +model User { + id ObjectId @id @map("_id") +} +``` + +On PostgreSQL the primary key is an ordinary column, so pick its type and default yourself. On MongoDB the primary key is the document's `_id`, so type it `ObjectId` and map it to `_id`. Your code still names the field `id`, in rows, `where`, `select`, `orderBy`, and write data: `@map` changes only the name in the stored document. A field of a [value object](#value-objects) cannot take `@map`, because it is always stored under its own name. + +### Generated values + +`@onCreate(fn())` and `@onUpdate(fn())` name a function that Prisma ORM runs in your application to fill the field: `@onCreate` when it creates the row and you leave the field out, `@onUpdate` on every update. The functions are: + +- `uuid()`, a version 4 UUID, the same as `uuid(4)`, and `uuid(7)`, a version 7 UUID. +- `cuid(2)`, `ulid()`, and `ksuid()`. +- `nanoid()`, 21 characters long, and `nanoid(n)`, `n` characters long, from 2 to 255. +- `now()`, the current time. ```prisma model Post { - updatedAt temporal.updatedAt() + id String @id @onCreate(uuid(7)) + slug String @onCreate(nanoid(12)) + createdAt DateTime @default(now()) + updatedAt DateTime @onCreate(now()) @onUpdate(now()) } ``` -Prisma ORM sets the field to the current time on every create and update. `temporal` is a built-in group of timestamp field types, such as `temporal.createdAt()` and `temporal.updatedAt()`, so you do not import it or declare it. On PostgreSQL the column is `timestamptz`, and you can still set the field yourself on a write. `temporal.createdAt()` sets the field once, when the row is created. Both take the time from your application's clock, not from the database, so the column has no database default, and a row you insert with raw SQL must supply the value. For a value the database generates, use `DateTime @default(now())` instead. `temporal.createdAtJsDate()` and `temporal.updatedAtJsDate()` do the same with a JavaScript `Date` in place of a `Temporal` value. `temporal.createdAt()` and `temporal.updatedAt()` need a global `Temporal` for writes as well as reads, so when your runtime has none, install `temporal-polyfill` and add `import "temporal-polyfill/full/global"` to your app's entry file. +Each function makes a value for the field's type: `uuid()` works on `String`, `VarChar(n)`, `Char(n)`, and `Uuid`, and `now()` works on every PostgreSQL date and time type, `Date`, `Time`, and `Timetz` included. The database does not fill these columns, so a row written by raw SQL or another application gets no value. A field may have both a database `@default` and a generator: `createdAt DateTime @default(now()) @onCreate(now())` takes its time from your application when Prisma ORM creates the row, and from the database for other inserts. On an optional field, the generator fills the field when you leave it out, and an explicit `null` is stored as `null`. -How IDs map differs by database: +`@default(uuid())` and the other generator functions in `@default` are refused with `PSL_GENERATOR_IN_DEFAULT`, which names the `@onCreate(...)` to write. `@default(now())` and `@default(autoincrement())` stay: they are database defaults. -```prisma tab="PostgreSQL" -model User { - id Uuid @id @default(uuid()) +An argument a function does not take, such as `uuid(5)`, `cuid(1)`, or `nanoid(300)`, is refused with `PSL_INVALID_GENERATOR_ARGUMENT`, and `cuid()` needs its version, `cuid(2)`. An argument written with a fraction, such as `nanoid(8.0)`, or in quotes, is refused with `PSL_VALUE_TYPE_INCOMPATIBLE`. A generator must also fit its column: `uuid()` writes 36 characters, `ksuid()` 27, `ulid()` 26, `cuid(2)` 24, `nanoid()` 21, and `nanoid(n)` `n`, so `VarChar(5) @onCreate(uuid())` is refused when you run `contract emit`. A bare `Char` holds one character, and a bare `VarChar` has no limit. + +`@updatedAt` is gone. Write `@onCreate(now()) @onUpdate(now())`, as `updatedAt` above does, or `temporal.updatedAt()` where the field's type would go: + +```prisma +model Post { + updatedAt temporal.updatedAt() } ``` -```prisma tab="MongoDB" -model User { - id ObjectId @id @map("_id") +Prisma ORM sets the field to the current time on every create and update. `temporal` is a built-in group of timestamp field types, such as `temporal.createdAt()` and `temporal.updatedAt()`, so you do not import it or declare it. On PostgreSQL the column is `timestamptz`, and you can still set the field yourself on a write. `temporal.createdAt()` sets the field once, when the row is created. Both take the time from your application's clock, not from the database, so the column has no database default, and a row you insert with raw SQL must supply the value. For a value the database generates, use `DateTime @default(now())` instead. `temporal.createdAtJsDate()` and `temporal.updatedAtJsDate()` do the same with a JavaScript `Date` in place of a `Temporal` value. `temporal.createdAt()` and `temporal.updatedAt()` need a global `Temporal` for writes as well as reads, so when your runtime has none, install `temporal-polyfill` and add `import "temporal-polyfill/full/global"` to your app's entry file. + +### Columns of any database type + +For a column of a database type no field type covers, write ``Raw(sql`...`)`` with the type exactly as the database prints it: + +```prisma +model Category { + id Int @id + path Raw(sql`ltree`) + area Raw(sql`circle`)? @default(sql`'<(0,0),1>'::circle`) } ``` -On PostgreSQL the primary key is an ordinary column, so pick its type and default yourself. On MongoDB the primary key is the document's `_id`, so type it `ObjectId` and map it to `_id`. +Migrations create the column with that text, and [`db verify`](/cli/db-verify) compares it with the type the database reports, so write `integer`, not `int4`, and quote a name that needs it, as in ``Raw(sql`"MyType"`)``. Your code reads and writes the value as `unknown`, and a default is a `sql` tagged literal. `Raw("ltree")`, without the `sql` tag, is refused. `Raw` works on PostgreSQL and SQLite. ### Default values @@ -186,19 +224,23 @@ model Account { ratio Float @default(NaN) role String @default("member") active Boolean @default(true) - startsOn Date @default("2024-01-01") + startsOn Date @default(date`2024-01-01`) + validFrom DateTime @default(timestamptz`2024-01-01T00:00:00Z`) + payload Bytes @default(bytea`\x010203`) createdAt DateTime @default(now()) expiresAt DateTime @default(sql`(now() + '00:03:00'::interval)`) } ``` - A JSON default is a `json` tagged literal: `json` followed by the JSON document in backticks. Write a JSON array the same way, as `tags` shows. -- A decimal default is a bare number, and it keeps every digit you write, trailing zeros included. +- A decimal default is a bare number. `contract emit` stores it with the scale of its column, so `@default(1.5)` on a `Numeric(10, 2)` field is stored as `"1.50"`. - `NaN`, `Infinity`, and `-Infinity` are written without quotes, and so are `true` and `false`. -- A text default is a quoted string, and so is a fixed date or time, as `startsOn` shows. A `DateTime` default is a point in time with a UTC offset, such as `"2024-01-01T00:00:00Z"`, and a `Timestamp` default has no offset, such as `"2024-01-01 00:00:00"`. -- `contract emit` stores a fixed date or time default in one canonical text for each value, however you write it. `@default("2024-01-01T01:00:00+01:00")` on a `DateTime` field is stored as `"2024-01-01T00:00:00Z"`, the same instant in UTC, and `@default("2024-01-01 12:34:56.500")` on a `Timestamp` field is stored as `"2024-01-01T12:34:56.5"`. `contract infer` and `contract print` write date and time defaults in the same form. -- A `Bytes` default is a quoted string of base64 text, such as `@default("aGVsbG8=")`, or of PostgreSQL hex text, which starts with `\x`. -- Each default, and each member of an `enum`, is checked by the column's type, so a value the column cannot hold unchanged is refused: more characters than a `VarChar(n)` or `Char(n)` takes, a number a `Numeric(p, s)` would round, an integer outside the range of `Int` or `SmallInt`, or text that is not a UUID on a `Uuid` field. +- A text default is a quoted string. +- A fixed date, time, or interval is a tagged literal named after the column's PostgreSQL type: `timestamptz` for `DateTime` and `Timestamptz`, and `timestamp`, `date`, `time`, `timetz`, and `interval` for the types of those names, as in ``@default(interval`P1D`)``. A `timestamptz` default is a point in time with a UTC offset, such as ``timestamptz`2024-01-01T00:00:00Z` ``, and a `timestamp` default has no offset, such as ``timestamp`2024-01-01 00:00:00` ``. A quoted string on one of these fields is refused, and the message shows the tagged literal to write. +- A `Bytes` default is a `bytea` tagged literal holding the bytes as hexadecimal after `\x`, as `payload` shows. Base64 text is not accepted. +- On SQLite, a `DateTime` default uses the `datetime` tag, as in ``@default(datetime`2024-01-01T00:00:00Z`)``, and a `Bytes` default the `blob` tag, which holds hexadecimal digits, as in ``@default(blob`68656c6c6f`)``. +- `contract emit` stores a fixed date or time default in one canonical text for each value, however you write it. ``@default(timestamptz`2024-01-01T01:00:00+01:00`)`` on a `DateTime` field is stored as `"2024-01-01T00:00:00Z"`, the same instant in UTC, and ``@default(timestamp`2024-01-01 12:34:56.500`)`` on a `Timestamp` field is stored as `"2024-01-01T12:34:56.5"`. `contract infer` and `contract print` write date and time defaults in the same form. +- Each default, and each member of an `enum`, is checked by the column's type, so a value the column cannot hold unchanged is refused: more characters than a `VarChar(n)` or `Char(n)` takes, a number a `Numeric(p, s)` would round, more digits of a second than a `Timestamptz(3)` keeps, an integer outside the range of `Int` or `SmallInt`, or text that is not a UUID on a `Uuid` field. - A date or time default that the field's type does not hold makes `contract emit` fail with `PSL_INVALID_DEFAULT_LITERAL`, and the message shows a text the field takes. That includes an offset on a `Timestamp`, `Date`, or `Time` default, a missing offset on a `DateTime`, `Timestamptz`, or `Timetz` default, more than six digits after the decimal point (three on SQLite), and a date or time that does not exist, such as `2024-02-30`. The CLI checks these defaults on Node.js 24 too, which has no built-in `Temporal`. - On a field typed with an `enum` block, the default is the member's name without quotes, as in `@default(Low)`. On a `pg.enum(...)` field, it is the value the member stores, in quotes, as in `@default("admin")`. [Enums](#enums) covers both. - Any other SQL expression is a `sql` tagged literal, and it goes into the column default exactly as written. Write the current time as `now()` and a sequence as `autoincrement()`, not as `` sql`now()` `` or `` sql`autoincrement()` ``, which are refused. @@ -255,7 +297,7 @@ model Order { `expression:` is the condition as SQL, using column names, as on `@@index`. `name:` and `map:` work as they do on `@@index`: `name:` for a constraint you are adding, and `map:` for one that already exists in the database under that exact name. A model can carry any number of `@@check` attributes. PostgreSQL only. -Prisma ORM also adds two check constraints of its own: one on every enum column, so it accepts only the enum's values, and one on every list column, so it rejects `NULL` elements. When one of them gets in your way, `@noCheck` on the field leaves both out, `@noCheck(membership)` leaves out only the enum check, and `@noCheck(elementNotNull)` only the list check. The field's TypeScript type does not change, so the database can then hold values the type does not describe. +Prisma ORM also adds two check constraints of its own: one on every enum column, so it accepts only the enum's values, and one on every list column, so it rejects `NULL` elements. A list whose elements may be `NULL` is written `T?[]`, and it gets no check, as [Models and fields](#models-and-fields) describes. ### Control policy @@ -316,6 +358,8 @@ enum Priority { In `pg/text@1`, `pg` is PostgreSQL, `text` is the column type, and `@1` is the version of how the value is stored and read. Write `@@type("pg/int4@1")` to store the values as integers instead. When a member has no explicit value, the member name itself is stored. +Each member's value is a value of the enum's type, written as a `@default` of that type is: a quoted string for text, a bare number for a number type, and a tagged literal for a date or time. A quoted member on a number type, such as `Half = "0.5"` under `@@type("pg/numeric@1")`, is refused, and the message shows what to write, here `Half = 0.5`. Members are compared as values of the type, so on `pg/numeric@1` the members `0.5` and `0.50` are the same value and are refused together. An enum cannot use `pg/jsonb@1`, `pg/timetz@1`, `pg/enum@1`, `pg/vector@1`, or `pg/geometry@1`: use a text enum, or a `native_enum` block in place of `pg/enum@1`. + `@@type` is optional, and when you leave it out Prisma ORM picks the type from the member values: bare member names and string values give the database's text type, and integer values give its integer type. Give every member the same kind of value, because a mix of string and integer values throws an error whose `code` is `PSL_ENUM_CANNOT_INFER_TYPE`. A `@default` names the member, as in `priority Priority @default(Low)`, even where the member stores a different value. An `enum` block is not a PostgreSQL `enum` type: the column is text or an integer. For a PostgreSQL `enum` type, declare it in a `native_enum` block and type the field `pg.enum(Role)`: @@ -331,7 +375,7 @@ model User { } ``` -Each member needs a value. `pg` comes with `@prisma/orm-postgres`, so there is nothing to import. You do not create the PostgreSQL type yourself: `npx prisma migration plan` includes the `CREATE TYPE`. +Each member needs a value. `pg` comes with `@prisma/orm-postgres`, so there is nothing to import. You do not create the PostgreSQL type yourself: `npx prisma migration plan` includes the `CREATE TYPE`. A field can name a `native_enum` declared in another [namespace](#namespaces) by putting the namespace first, as in `pg.enum(auth.OrderStatus)`. ## Value objects @@ -346,7 +390,7 @@ type Address { } model User { - id Uuid @id @default(uuid()) + id Uuid @id @onCreate(uuid()) address Address? addresses Address[] } @@ -440,7 +484,7 @@ Models outside any block are in `public`. In queries, the block name is the segm ```prisma title="src/prisma/contract.prisma (excerpt)" model Profile { - id Uuid @id @default(uuid()) + id Uuid @id @onCreate(uuid()) userId Uuid @unique user supabase:auth.AuthUser @relation(fields: [userId], references: [id], onDelete: Cascade) } @@ -454,7 +498,7 @@ Two block kinds and one attribute declare PostgreSQL row-level security, and [`m ```prisma model User { - id Uuid @id @default(uuid()) + id Uuid @id @onCreate(uuid()) @@map("user") @@rls @@ -511,7 +555,7 @@ A base model declares a discriminator field, the field whose value says which va ```prisma model Task { - id Uuid @id @default(uuid()) + id Uuid @id @onCreate(uuid()) title String type String @@ -528,7 +572,7 @@ model Bug { } ``` -Field and model names are written bare, as `type` and `Task` are, while table names and discriminator values are quoted, as `"task"` and `"bug"` are. Rows whose `type` column holds `"bug"` are `Bug` records. A variant reuses its base model's fields, so a `Bug` has `id`, `title`, and `type` as well as `severity` and `stepsToRepro`. The base model can be declared before or after its variants. A variant does not declare an `@id` of its own: it takes the base model's primary key. +Field and model names are written bare, as `type` and `Task` are, while table names and discriminator values are quoted, as `"task"` and `"bug"` are. Rows whose `type` column holds `"bug"` are `Bug` records. A variant reuses its base model's fields, so a `Bug` has `id`, `title`, and `type` as well as `severity` and `stepsToRepro`. The base model can be declared before or after its variants. A variant does not declare an `@id` of its own: it takes the base model's primary key. On PostgreSQL and SQLite the discriminator field must hold text: `String`, or on PostgreSQL `Char(n)` or `VarChar(n)`, and on SQLite `sql.String(n)`. You query a variant through the base model: `db.orm.public.Task.variant('bug')` returns the `Bug` rows only, and `public` here is the PostgreSQL schema. `variant()` takes the discriminator value from `@@base`, not the model name. Writing a row of a variant starts with the same `variant('bug')` call. See [`variant()`](/orm/reference/orm-client#variant). @@ -559,7 +603,7 @@ types { } model Post { - id Uuid @id @default(uuid()) + id Uuid @id @onCreate(uuid()) embedding Embedding1536? } ``` diff --git a/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx b/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx index 217e72e2fcd..ac464a8bed6 100644 --- a/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx @@ -116,21 +116,24 @@ You never write these types yourself. Both files come from the one `npx prisma c ## How the application consumes the artifacts -`db` is built from both files: `contract.json` as the value, `contract.d.ts` as the type. `prisma orm init` writes `src/prisma/db.ts` for you. +`db` is built from both files: `contractFromJson(contractJson)` checks `contract.json` once and gives it the type from `contract.d.ts`, and the client takes that value as `contract`. `prisma orm init` writes `src/prisma/db.ts` for you. ```typescript title="src/prisma/db.ts" import "dotenv/config"; import postgres from "@prisma/orm-postgres/runtime"; +import { contractFromJson } from "@prisma/orm-postgres/static"; import type { Contract } from "./contract.d"; import contractJson from "./contract.json" with { type: "json" }; -export const db = postgres({ - contractJson, +export const contract = contractFromJson(contractJson); + +export const db = postgres({ + contract, url: process.env['DATABASE_URL']!, }); ``` -The connection string comes from the `DATABASE_URL` environment variable: `prisma orm init` writes `.env.example`, and you put your `DATABASE_URL` in `.env`. Keep the `.d` in the import path: it names `contract.d.ts`, so the import still finds the generated type when you author the contract in TypeScript and a `contract.ts` sits beside it. The `tsconfig.json` settings the JSON import needs are on the [transactions and runtime page](/orm/reference/transactions-and-runtime). Before its first query, `db` reads the record in the database of which contract it matches and compares it with the contract's hashes. If they do not match, it logs a warning whose `code` is `CONTRACT.MARKER_MISMATCH` and runs the query anyway. If there is no record at all, because nothing has signed the database yet, it logs a warning whose `code` is `CONTRACT.MARKER_MISSING` and again runs the query. On MongoDB the client is `mongo(...)` from `@prisma/orm-mongo/runtime`, described under [`mongo(options)`](/orm/reference/transactions-and-runtime#mongooptions). +The connection string comes from the `DATABASE_URL` environment variable: `prisma orm init` writes `.env.example`, and you put your `DATABASE_URL` in `.env`. Keep the `.d` in the import path: it names `contract.d.ts`, so the import still finds the generated type when you author the contract in TypeScript and a `contract.ts` sits beside it. The `tsconfig.json` settings the JSON import needs are on the [transactions and runtime page](/orm/reference/transactions-and-runtime). Before its first query, `db` reads the record in the database of which contract it matches and compares it with the contract's hashes. If they do not match, it logs a warning whose `code` is `CONTRACT.MARKER_MISMATCH` and runs the query anyway. If there is no record at all, because nothing has signed the database yet, it logs a warning whose `code` is `CONTRACT.MARKER_MISSING` and again runs the query. On MongoDB the client is `mongo({ contract, url })` from `@prisma/orm-mongo/runtime`, with `contractFromJson` from `@prisma/orm-mongo/static`, described under [`mongo(options)`](/orm/reference/transactions-and-runtime#mongooptions). When `CONTRACT.MARKER_MISMATCH` shows up in production, apply the pending migration, or run `npx prisma db sign` when the database already matches the contract. The client warns by default, and you can set [`verifyMarker`](/orm/reference/transactions-and-runtime#postgresoptions) to `false` to skip the check. It cannot be made to fail, so run `prisma db verify` when you need a check that fails. diff --git a/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx b/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx index ef9d7916913..0ab0ae78494 100644 --- a/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx @@ -99,24 +99,24 @@ import { defineContract, field, model, rel } from "@prisma/orm-mongo/contract-bu const User = model("User", { collection: "users", fields: { - _id: field.objectId(), + id: field.objectId().mongo({ field: "_id" }), email: field.string(), }, relations: { - posts: rel.hasMany("Post", { from: "_id", to: "authorId" }), + posts: rel.hasMany("Post", { from: "id", to: "authorId" }), }, }); const Post = model("Post", { collection: "posts", fields: { - _id: field.objectId(), + id: field.objectId().mongo({ field: "_id" }), authorId: field.objectId(), title: field.string(), publishedAt: field.date().optional(), }, relations: { - author: rel.belongsTo(User, { from: "authorId", to: User.ref("_id") }), + author: rel.belongsTo(User, { from: "authorId", to: User.ref("id") }), }, }); @@ -136,15 +136,16 @@ The options object takes four more keys, all optional: | `defaultControlPolicy: "managed"` | The [control policy](#control-policy) for every model that does not set its own. | | `namespaces: ["audit"]` | The PostgreSQL schemas other than `public` that models may use; see [Namespaces](#namespaces). | -Take `field`, `model`, `rel`, and `type` from the function's one argument, and import `defineContract`, `enumType`, and `member` from the package. The package also exports `model` and `rel` for use outside the function, plus a `field` that has only `column`, `generated`, and `namedType`. +Take `field`, `model`, `rel`, `type`, and `gen` from the function's one argument, and import `defineContract`, `enumType`, and `member` from the package. The package also exports `model`, `rel`, `field`, and `gen` for use outside the function, without the helpers of extension packs. -On MongoDB, `defineContract` also accepts a single object holding `models`, as above, and you import `field`, `model`, and `rel` from the package instead. The MongoDB builder differs from the PostgreSQL one in five ways: +On MongoDB, `defineContract` also accepts a single object holding `models`, as above, and you import `field`, `model`, and `rel` from the package instead. The MongoDB builder differs from the PostgreSQL one in six ways: -- Every model declares an `_id` field built with `field.objectId()`. That field is always the primary key, so you never mark it with `.id()` and there is no `.attributes(...)` on MongoDB. +- The field stored as `_id` is always the primary key, so you never mark it with `.id()` and there is no `.attributes(...)` on MongoDB. Declare it as `id: field.objectId().mongo({ field: "_id" })` to name it `id` in your code, as `id ObjectId @id @map("_id")` does in PSL, or as `_id: field.objectId()` to keep the name `_id`. `.mongo({ field })` sets the name a field is stored under in its documents, for any field of a model. A model without an `_id` field gets one from MongoDB, an ObjectId, on every insert. - The scalar helpers are `field.objectId()`, `field.string()`, `field.int32()`, `field.double()`, `field.bool()`, and `field.date()`. - The collection name is an inline option on the model rather than a chained call, and relations are inline as well. -- Relations name the field on each side. `rel.hasMany` takes `{ from, to }` on MongoDB and `{ by }` on PostgreSQL. `to` accepts either the field name as a string or a typed reference such as `User.ref("_id")`, and both forms mean the same thing. PostgreSQL writes that reference as `User.refs.id`. +- Relations name the field on each side. `rel.hasMany` takes `{ from, to }` on MongoDB and `{ by }` on PostgreSQL. `to` accepts either the field name as a string or a typed reference such as `User.ref("id")`, and both forms mean the same thing. PostgreSQL writes that reference as `User.refs.id`. - Indexes are an `indexes` option on the model, built with the `index` helper the same package exports, as in `indexes: [index({ email: 1 }, { unique: true })]`. The `1` is ascending, MongoDB's own index syntax. +- Each collection gets a `$jsonSchema` validator built from its model, as with a PSL contract, so MongoDB refuses a document with a field the model does not declare, a missing required field, or a value of the wrong type. `defineContract` refuses what a validator cannot describe, such as an `_id` field that is optional or a list. To keep a collection without a validator, give its model `controlPolicy: "observed"`. Enums and extension packs work on MongoDB too: `enumType` and `member` come from `@prisma/orm-mongo/contract-builder`. Pass the enums in the same object as the models, as `defineContract({ models, enums })`, and extension packs go in the same `extensions` option. @@ -166,7 +167,7 @@ const Address = valueObject("Address", { const User = model("User", { collection: "users", fields: { - _id: field.objectId(), + id: field.objectId().mongo({ field: "_id" }), email: field.string(), address: field.valueObject(Address).optional(), embedding: field.vector().optional(), @@ -199,8 +200,24 @@ The examples below use the PostgreSQL builder. There is no helper for a date without a time, or a time without a date. For a date-only column, name the type yourself: `field.column({ codecId: "pg/date-temporal@1" } as const)`. `field.column(...)` and the chained `.column(...)` below are different calls: `field.column(descriptor)` builds a field from a type description, and `.column(name)` on an existing field sets its column name. Extension packs add more helpers of their own. +For a column of a database type no helper covers, use ``type.Raw(sql`...`)`` from the function's argument, with the type exactly as the database prints it, as in ``path: field.column(type.Raw(sql`ltree`))``, with `sql` imported from the package. It works as [``Raw(sql`...`)`` in PSL](/orm/contract-authoring/psl-syntax#columns-of-any-database-type) does: your code reads and writes the value as `unknown`, and a default is a `sql` expression. + For a primary key your app generates, use a `field.id.*` helper. Each one marks the field as the primary key and generates the ID in your app when you create a row. `field.id.uuidv4String()` is the common choice, and the others are `field.id.uuidv7String()`, `field.id.ulid()`, `field.id.nanoid()`, `field.id.cuid2()`, and `field.id.ksuid()`. To store a UUID in a `uuid` column instead of a `character(36)` column, use `field.id.uuidv4Native()` or `field.id.uuidv7Native()`. +To generate a value in your app on any field, chain `.onCreate(...)` with a call from `gen`, and `.onUpdate(...)` to generate one on every update. `gen` has `gen.uuid()`, `gen.uuid(7)`, `gen.cuid(2)`, `gen.ulid()`, `gen.nanoid()`, `gen.nanoid(16)`, `gen.ksuid()`, and `gen.now()`, the same functions as [`@onCreate` and `@onUpdate` in PSL](/orm/contract-authoring/psl-syntax#generated-values), and each makes a value for the field's column type: + +```typescript title="src/prisma/contract.ts (excerpt)" +const Post = model("Post", { + fields: { + id: field.uuidNative().id().onCreate(gen.uuid(7)), + slug: field.text().onCreate(gen.nanoid(12)), + updatedAt: field.dateTime().onCreate(gen.now()).onUpdate(gen.now()), + }, +}); +``` + +A field may have both: `field.dateTime().default(now()).onCreate(gen.now())` takes its time from your app when Prisma ORM creates the row, and from the database for other inserts. `field.generated(...)` still works, but `.onCreate(...)` replaces it. + For a key the database generates, chain `.default(...)` and `.id()` on a field. An integer that counts up is `field.int().default(autoincrement()).id()`, and a UUID is ``field.uuidNative().default(sql`gen_random_uuid()`).id()``. Import `autoincrement` and `sql` from `@prisma/orm-postgres/contract-builder`. `field.temporal.createdAt()` sets the column to the current time when the row is created, and `field.temporal.updatedAt()` sets it on every create and every update. Prisma ORM sets both in the client, from your application's clock, so the column has no database default. If you insert a row with raw SQL, you must supply the value yourself. For a time the database sets instead, use `field.dateTime().default(now())`, with `now` imported from `@prisma/orm-postgres/contract-builder`. @@ -217,7 +234,7 @@ Every field builder supports chained modifiers: - `.unique()` adds a unique constraint. `.id()` marks the primary key, for a key that is not generated, such as an integer you set yourself. - `.column("column_name")` sets the column name in the database when it differs from the field name. - `.many()` makes the field a list, stored as a PostgreSQL array column such as `text[]`. A list column gets a check constraint that rejects `NULL` elements. To allow `NULL` elements, write `.many({ elementsNullable: true })`: the field's TypeScript type becomes `ReadonlyArray`. Add `.optional()` to allow a `NULL` list as well. -- `.noCheck()` leaves out the check constraints Prisma ORM generates for the column: the one that keeps an enum column to the enum's values, and the one that keeps `NULL` out of a list. `.noCheck("membership")` and `.noCheck("elementNotNull")` leave out one or the other. The field's TypeScript type does not change. +- `.onCreate(gen.uuid())` and `.onUpdate(gen.now())` generate a value in your app, as described above. ## Enums diff --git a/apps/docs/content/docs/orm/core-concepts.mdx b/apps/docs/content/docs/orm/core-concepts.mdx index 7d6e5e042ab..bb761a0e3f3 100644 --- a/apps/docs/content/docs/orm/core-concepts.mdx +++ b/apps/docs/content/docs/orm/core-concepts.mdx @@ -107,12 +107,12 @@ const plan = db.sql.public.Post .limit(10) .build(); -const publishedPosts = await db.runtime().query(plan); +const publishedPosts = await db.query(plan); ``` `db` is the client, which `src/prisma/db.ts` creates from `contract.json`. [`prisma orm init`](/cli/orm-init) writes that file for you. -Every query reaches the database as a plan, however you wrote it. Because a plan is data, you can inspect it before anything touches the database, and a failed query can report exactly what it ran. +Every query reaches the database as a plan, however you wrote it. Because a plan is data, you can inspect it before anything touches the database: `db.toSql(plan)` returns the SQL and parameters it would send, as `{ sql, params }`, and works with no database at all. A failed query can report exactly what it ran. On PostgreSQL you write a query in one of the following ways, starting with the one that does the most for you: diff --git a/apps/docs/content/docs/orm/data-modeling/index.mdx b/apps/docs/content/docs/orm/data-modeling/index.mdx index 5faacbab99e..2b59cb9157d 100644 --- a/apps/docs/content/docs/orm/data-modeling/index.mdx +++ b/apps/docs/content/docs/orm/data-modeling/index.mdx @@ -56,7 +56,7 @@ model User { } ``` -On MongoDB, the primary key is the document's mandatory `_id` field, and it must be an `ObjectId`. `@map("_id")` gives the field that name in the database. The field takes no `@default`, because MongoDB assigns the id itself when you create a document without one. `@@map("users")` names the collection and is optional: without it, the collection is the model name exactly as written, `User`. +On MongoDB, the primary key is the document's mandatory `_id` field, usually an `ObjectId`. `@map("_id")` stores the field as `_id` in the database, and your code still calls it `id`. The field takes no `@default`, because MongoDB assigns the id itself when you create a document without one. `@@map("users")` names the collection and is optional: without it, the collection is the model name exactly as written, `User`. ### Natural keys @@ -104,11 +104,11 @@ The database assigns the value, so you only know it after the insert. It is also // UUID: globally unique, generated before the insert. // Good for ids that appear in URLs or are created across services. model ApiToken { - id String @id @default(uuid()) + id String @id @onCreate(uuid()) } ``` -A UUID takes more space than an integer, but independent services never produce the same one. `@default(uuid())` generates a random UUID, and `@default(uuid(7))` generates one that sorts by creation time. On MongoDB there is no choice to make, because the key is always the `ObjectId` shown above. +A UUID takes more space than an integer, but independent services never produce the same one. `@onCreate(uuid())` generates a random UUID, and `@onCreate(uuid(7))` generates one that sorts by creation time. On MongoDB there is no choice to make, because the key is always the `ObjectId` shown above. ### Composite keys @@ -149,7 +149,7 @@ A `Decimal` reaches your code as a string, so nothing is rounded through a JavaS `Json` and `Jsonb` are two separate types, and they differ in how you can filter on them. On a `Jsonb` field, `.where()` compares the whole value, so you can ask whether the column equals a given document but not whether a key inside it matches. A `Json` field cannot be compared in `.where()` at all, so use `Jsonb` unless you specifically need PostgreSQL's plain `json` column. When you need a path query into the document, reach for [raw SQL](/orm/reference/raw-queries). [Extensions](/orm/extensions) add more types, such as vectors or geometry. -A trailing `?` makes a field optional: `name String?`. A trailing `[]` makes it a list: `tags String[]`. `String?[]` is a list whose elements may be `null`, and `String[]?` is a list that may itself be `null`. A `@default(...)` must be written in a form the field's type accepts, such as the `json` literal ``@default(json`{}`)`` on a `Jsonb` field and `@default(1.50)` on a `Decimal` field; [Default values](/orm/contract-authoring/psl-syntax#default-values) lists them. +A trailing `?` makes a field optional: `name String?`. A trailing `[]` makes it a list: `tags String[]`. `String?[]` is a list whose elements may be `null`, and `String[]?` is a list that may itself be `null`. A `@default(...)` must be written in a form the field's type accepts, such as the `json` literal ``@default(json`{}`)`` on a `Jsonb` field, `@default(1.50)` on a `Decimal` field, and ``@default(timestamptz`2024-01-01T00:00:00Z`)`` on a `DateTime` field; [Default values](/orm/contract-authoring/psl-syntax#default-values) lists them. A field can also hold one of a fixed set of values. Declare them in an `enum` block: diff --git a/apps/docs/content/docs/orm/data-modeling/mongodb.mdx b/apps/docs/content/docs/orm/data-modeling/mongodb.mdx index 8f70676943f..77e8ecd2cca 100644 --- a/apps/docs/content/docs/orm/data-modeling/mongodb.mdx +++ b/apps/docs/content/docs/orm/data-modeling/mongodb.mdx @@ -21,16 +21,16 @@ model User { } ``` -In the contract you call the field `id`, but in TypeScript it is always `_id`, never `id`, because `@map("_id")` sets the name in the database and in your code alike. So you write `user._id` and `where({ _id })`. `@@map("users")` names the collection and is optional: without it the collection takes the model name exactly as written, so `User`. `@unique` on `email` becomes a unique index when you migrate. +`@map("_id")` stores the field as the document's `_id`, and your code calls it `id`, its name in the contract. So you write `user.id` and `where({ id })`, and the same goes for `select`, `orderBy`, and the values you write. Any other field with `@map` works the same way: `firstName String @map("first_name")` is `firstName` in your code and `first_name` in the document. Only the [pipeline builder](/orm/fundamentals/advanced-queries#mongodb-pipeline-builder) reads documents as they are stored, so it uses `_id`. `@@map("users")` names the collection and is optional: without it the collection takes the model name exactly as written, so `User`. `@unique` on `email` becomes a unique index when you migrate. The migration also gives each collection a `$jsonSchema` validator built from its model, from a PSL contract and a [TypeScript contract](/orm/contract-authoring/typescript-schema-builder) alike, so MongoDB refuses a document with a field the model does not declare, a missing required field, or a value of the wrong type. MongoDB does not check documents already stored when the validator is added. -Write `ObjectId` for the key type. That is the contract type only: in TypeScript a document's own `_id` is a plain string, so there is no `ObjectId` to construct. The old `String @db.ObjectId` and `@default(auto())` are no longer allowed. +Write `ObjectId` for the key type. That is the contract type only: in TypeScript the id is a plain string, so there is no `ObjectId` to construct. The old `String @db.ObjectId` and `@default(auto())` are no longer allowed. Leave the id out of the values you pass to `create`, and MongoDB assigns it. In the examples below, `db` is the client created in `src/prisma/db.ts`, and `db.orm` is the ORM client, one of the query APIs on `db` that [the ORM reference](/orm/reference) covers. ```ts import { db } from "./prisma/db"; const user = await db.orm.users.create({ name: "Ada", email: "ada@example.com" }); -const found = await db.orm.users.where({ _id: user._id }).first(); +const found = await db.orm.users.where({ id: user.id }).first(); ``` The name after `db.orm.` is the collection name, so with `@@map("users")` you write `db.orm.users`, not `db.orm.user`. The values go straight into `create`, without the `data:` wrapper Prisma ORM 7 used. You `await` the whole chain, and the last call says what you want back: `.first()` for one document or `null`, `.all()` for every matching document. @@ -92,7 +92,7 @@ A blog post's comments make it concrete. If you always render them with the post ## Embedded documents -Describe the shape of embedded data with a `type` block, then use it as a field. An embedded value has no `_id` and no collection of its own, so it is created, loaded, updated, and deleted with the document that holds it. +Describe the shape of embedded data with a `type` block, then use it as a field. An embedded value has no `_id` and no collection of its own, so it is created, loaded, updated, and deleted with the document that holds it. Its fields cannot take `@map`, because they are always stored under their own names. ```prisma type Address { @@ -119,8 +119,8 @@ await db.orm.users.create({ name: "Ada", tags: [], address }); In an `update`, the callback argument `u` builds the changes you want, and because the callback returns an array, several changes go in one call. Use `u.fieldName` to change a top-level field, or `u("path.to.field")` for a field inside an embedded document, written as a string because a dotted path is not a property name. On a list of scalars, `.push(...)` adds values and `.pull(...)` removes them: ```ts -await db.orm.users.where({ _id: user._id }).update((u) => [u("address.city").set("Hamburg")]); -await db.orm.users.where({ _id: user._id }).update((u) => [u.tags.push("admin"), u.tags.pull("new")]); +await db.orm.users.where({ id: user.id }).update((u) => [u("address.city").set("Hamburg")]); +await db.orm.users.where({ id: user.id }).update((u) => [u.tags.push("admin"), u.tags.pull("new")]); ``` Replace a whole embedded value with `u.address.set({ ... })`, remove it with `u.address.unset()`, and replace a list of embedded values as a whole with `u.addresses.set([ ... ])`. Filtering is more limited: the object form of `.where()` filters on top-level model fields only, so to match on a field inside an embedded document you use the pipeline builder, a chained builder for MongoDB aggregation pipelines: @@ -135,7 +135,7 @@ Embedded arrays are only a good idea while they stay bounded. An array that grow ## References across collections -When related records need their own collection, store one document's `_id` on the other and declare the relation with the same `@relation(fields:, references:)` used everywhere in Prisma ORM: +When related records need their own collection, store one document's `id` on the other and declare the relation with the same `@relation(fields:, references:)` used everywhere in Prisma ORM: ```prisma model User { @@ -153,10 +153,10 @@ model Post { } ``` -This is a one-to-many by reference: many posts point at one user, and each post is queryable on its own. Note that in `references: [id]` you write the field's contract-side name, `id`, not `_id`. There are no nested writes on MongoDB, so create the user first and then the post with its `authorId`. To load a post together with its author, chain `.include("author")`, which adds the related record to every row you get back: +This is a one-to-many by reference: many posts point at one user, and each post is queryable on its own. In `references: [id]` you write the field's name in the contract, `id`, not `_id`. There are no nested writes on MongoDB, so create the user first and then the post with its `authorId`. To load a post together with its author, chain `.include("author")`, which adds the related record to every row you get back: ```ts -await db.orm.posts.create({ title: "Hello", authorId: user._id }); +await db.orm.posts.create({ title: "Hello", authorId: user.id }); const posts = await db.orm.posts.include("author").all(); ``` diff --git a/apps/docs/content/docs/orm/data-modeling/relational-databases.mdx b/apps/docs/content/docs/orm/data-modeling/relational-databases.mdx index 362fbe4030e..ff3f2be6bd9 100644 --- a/apps/docs/content/docs/orm/data-modeling/relational-databases.mdx +++ b/apps/docs/content/docs/orm/data-modeling/relational-databases.mdx @@ -175,7 +175,7 @@ model Feature { } ``` -`@@discriminator(type)` marks the field that records the variant, and `@@base(Task, "bug")` declares that `Bug` is the `"bug"` variant of `Task`. Both take a field or model name without quotes, while the variant value is a string. A variant has every field of its base plus the ones it declares, and it needs no `@id` of its own because it uses the base model's primary key. +`@@discriminator(type)` marks the field that records the variant, and `@@base(Task, "bug")` declares that `Bug` is the `"bug"` variant of `Task`. Both take a field or model name without quotes, while the variant value is a string. The discriminator field holds text: `String`, `Char(n)`, or `VarChar(n)` on PostgreSQL, and `String` or `sql.String(n)` on SQLite. A variant has every field of its base plus the ones it declares, and it needs no `@id` of its own because it uses the base model's primary key. Read and write variants through the base model, where `.variant(...)` limits the query to one variant. It takes the discriminator value, `"bug"`, not the model name `"Bug"`, and a value the model does not declare throws. On a create, the discriminator value is filled in for you: diff --git a/apps/docs/content/docs/orm/extensions/using-extensions.mdx b/apps/docs/content/docs/orm/extensions/using-extensions.mdx index c674509d18d..f011ce4ff54 100644 --- a/apps/docs/content/docs/orm/extensions/using-extensions.mdx +++ b/apps/docs/content/docs/orm/extensions/using-extensions.mdx @@ -51,11 +51,12 @@ Add the extension to the `extensions` array on the client as well, which is what ```ts title="src/prisma/db.ts" import pgvector from '@prisma/orm-extension-pgvector/runtime'; import postgres from '@prisma/orm-postgres/runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const db = postgres({ - contractJson, +export const db = postgres({ + contract: contractFromJson(contractJson), url: process.env['DATABASE_URL']!, extensions: [pgvector], }); @@ -79,7 +80,7 @@ types { } model Post { - id String @id @default(uuid()) + id String @id @onCreate(uuid()) title String embedding Embedding1536? } @@ -106,11 +107,17 @@ await db.orm.public.Post.create({ title: 'about cats', embedding }); In Prisma ORM 8 `create` takes the fields directly, with no `data` wrapper. [Create a record](/orm/coming-from-prisma-orm-7#create-a-record) shows the two side by side. `public` is the PostgreSQL schema your tables are in. -Searching for the rows closest to a query vector uses the operations the extension adds, and you write that search with the SQL query builder rather than with `db.orm`. In that block: +To get the rows closest to a query vector on `db.orm`, use the method pgvector adds for each vector field under `using`. It orders the rows by cosine distance, nearest first, and an optional second argument keeps only the rows within that distance: + +```ts +const nearest = await db.orm.public.Post.using.embedding_nearest(queryVector).limit(10).all(); +``` + +See [Package methods with `using`](/orm/reference/orm-client#package-methods-with-using). To get the distance itself in each row, write the search with the SQL query builder. In that block: - `db.orm` is the ORM API, and under it you address a model by the name in your contract, so the model `Post` is `db.orm.public.Post`. - `db.sql` is the [SQL query builder](/orm/fundamentals/advanced-queries), and under it you address a table by the name it has in the database, so the same data is `db.sql.public.Post`. The two names are the same because the model sets no `@@map`. -- `db.runtime()` is what runs a built query. +- `db.query(...)` runs a built query. - `f`, in the callback of each builder method, holds the row's columns. - `fns` holds the functions you can call on those columns, including the ones pgvector adds. - `queryVector` is a `number[]` of the same length as the stored embeddings. @@ -126,11 +133,11 @@ export async function similaritySearch(queryVector: number[]) { .limit(10) .build(); - return db.runtime().query(plan); + return db.query(plan); } ``` -`.select('distance', ...)` names the computed column `distance`, and `.build()` returns a plain object that `db.runtime().query(...)` runs. Each row that comes back has `id`, `title`, and `distance`, a `number` that is `null` when the row has no embedding. +`.select('distance', ...)` names the computed column `distance`, and `.build()` returns a plain object that `db.query(...)` runs. Each row that comes back has `id`, `title`, and `distance`, a `number` that is `null` when the row has no embedding. Alongside `fns.cosineDistance`, pgvector gives you `fns.cosineSimilarity`. Both take two vectors and return a PostgreSQL `float8`, which you get as a `number`. The difference is which way you sort: order ascending when you sort by distance, because the closest row has the smallest distance, and descending when you sort by similarity. @@ -192,11 +199,12 @@ The client is where the real difference is, because you build it with `supabase( ```ts title="src/prisma/db.ts" import { supabase } from '@prisma/orm-extension-supabase/runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const db = await supabase({ - contractJson, +export const db = await supabase({ + contract: contractFromJson(contractJson), url: process.env['DATABASE_URL']!, jwksUrl: process.env['SUPABASE_JWKS_URL']!, }); @@ -206,7 +214,7 @@ export const db = await supabase({ `supabase(...)` takes the same `extensions` array as `postgres(...)`, so pgvector or another extension goes there in the same way. -`await db.asUser(jwt)` takes the user's Supabase JWT and gives you a client that runs as that user. `db.asAnon()` gives you one that runs as the anonymous role, and `db.asServiceRole()` one that bypasses row-level security. `db` itself has no `orm` or `sql`: those live on the three role-bound clients. +`await db.asUser(jwt)` takes the user's Supabase JWT and gives you a client that runs as that user. `db.asAnon()` gives you one that runs as the anonymous role, and `db.asServiceRole()` one that bypasses row-level security. `db` itself has no `orm` or `sql`: those live on the three role-bound clients. Each role-bound client has `orm`, `sql`, `raw`, `prepare`, `query`, `execute`, `toSql`, and `transaction()`, the same members a `postgres(...)` client uses to run queries. Pass your own collection classes to `supabase(...)` in the `collections` option, as you would to `postgres(...)`, and the role-bound clients use them on their `orm`. See [Custom `Collection` subclass](/orm/reference/orm-client#custom-collection-subclass). `@prisma/orm-extension-supabase` has a contract of its own, which describes Supabase's `auth` and `storage` tables, check constraints included. Prisma read those tables from one Supabase version, supabase/postgres 17.6.1.106, and a Supabase project on another version can have a different set of check constraints. [`npx prisma db verify`](/cli/db-verify) compares your database with the extension's contract as well as with yours, and when a check constraint the extension describes is missing from your database, `db verify` lists it and fails. You cannot fix that with a migration, because Prisma ORM never changes Supabase's own tables. Instead, [open an issue on prisma/orm](https://github.com/prisma/orm/issues) with the output of `db verify` and your Supabase version, so the extension can be updated to match. diff --git a/apps/docs/content/docs/orm/fundamentals/advanced-queries.mdx b/apps/docs/content/docs/orm/fundamentals/advanced-queries.mdx index 81ccf52a17b..1be472682b7 100644 --- a/apps/docs/content/docs/orm/fundamentals/advanced-queries.mdx +++ b/apps/docs/content/docs/orm/fundamentals/advanced-queries.mdx @@ -18,7 +18,7 @@ Every example on this page queries these four models: ```prisma model User { - id String @id @default(cuid(2)) + id String @id @onCreate(cuid(2)) email String @unique name String? posts Post[] @@ -27,7 +27,7 @@ model User { } model Post { - id String @id @default(cuid(2)) + id String @id @onCreate(cuid(2)) title String published Boolean @default(false) createdAt DateTime @default(now()) @@ -39,7 +39,7 @@ model Post { } model Tag { - id String @id @default(cuid(2)) + id String @id @onCreate(cuid(2)) name String @unique posts Post[] @@ -57,7 +57,7 @@ model PostTag { } ``` -In Prisma ORM 8 a many-to-many relation is the two list fields plus a model for the join table, as `PostTag` here; see [Relations and joins](/orm/fundamentals/relations-and-joins). The MongoDB examples use the same models, except that each id is declared as `id ObjectId @id @map("_id")`, so the property is `_id`. +In Prisma ORM 8 a many-to-many relation is the two list fields plus a model for the join table, as `PostTag` here; see [Relations and joins](/orm/fundamentals/relations-and-joins). The MongoDB examples use the same models, except that each id is declared as `id ObjectId @id @map("_id")`, so the property is `id` and the stored field is `_id`. ## PostgreSQL: SQL query builder @@ -76,9 +76,9 @@ A SQL builder query takes two steps: first you build it, then you run it. You de Start from a table with `db.sql.public.`. `db.sql` is the SQL query builder, keyed by table name. A table's name is the model's `@@map` value, or the model name exactly as written when the model has no `@@map`, and column names are the field names unless a field sets `@map`. `public` is the PostgreSQL schema your tables are in. If you put a model inside a `namespace` block in your contract, the block's name is the PostgreSQL schema instead, so for a model inside `namespace billing { ... }` you write `billing` in place of `public`: `db.sql.billing.
`. -`db.runtime()` is the database connection; call it whenever you run a query. `query()` always resolves to an array of rows, even when the query matches one row. +`db.query(...)` runs a built query and always resolves to an array of rows, even when the query matches one row. -Chain the clauses you want, call `.build()`, and pass the result to `db.runtime().query(...)`: +Chain the clauses you want, call `.build()`, and pass the result to `db.query(...)`: ```typescript import { db } from "./prisma/db"; @@ -89,7 +89,7 @@ const publishedPosts = db.sql.public.post .limit(10).offset(20) .build(); -const rows = await db.runtime().query(publishedPosts); +const rows = await db.query(publishedPosts); ``` The `.where(...)` callback receives two arguments. The first, `f`, holds the column references, and the second, `fns`, holds the operators: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `notIn`, `and`, `or`, and `ilike` for text columns on PostgreSQL. There are more; the [SQL query builder reference](/orm/reference/sql-query-builder#built-in-functions) has the full list. @@ -116,7 +116,7 @@ function titles(posts: PublishedPost[]) { } ``` -`ResultType<...>` is one row, not the array, so `await db.runtime().query(publishedPosts)` resolves to `PublishedPost[]`. See [`ResultType`](/orm/reference/sql-query-builder#resulttype) in the reference. +`ResultType<...>` is one row, not the array, so `await db.query(publishedPosts)` resolves to `PublishedPost[]`. See [`ResultType`](/orm/reference/sql-query-builder#resulttype) in the reference. ### Join tables with precise control @@ -131,7 +131,7 @@ const postsWithAuthors = db.sql.public.post .limit(10) .build(); -const rows = await db.runtime().query(postsWithAuthors); +const rows = await db.query(postsWithAuthors); // Array<{ postId, title, authorEmail }> ``` @@ -147,7 +147,7 @@ const postTagPairs = db.sql.public.post_tag .select((f) => ({ postTitle: f.p.title, tagName: f.t.name })) .build(); -const rows = await db.runtime().query(postTagPairs); +const rows = await db.query(postTagPairs); ``` ```js no-copy @@ -171,7 +171,7 @@ const topAuthors = db.sql.public.post .limit(5) .build(); -const rows = await db.runtime().query(topAuthors); +const rows = await db.query(topAuthors); ``` ```js no-copy @@ -194,11 +194,11 @@ const insertUser = db.sql.public.user .insert([{ email: "sql@prisma.io" }]) .returning("id", "email").build(); -const [insertedUser] = await db.runtime().query(insertUser); -// insertedUser is { id, email }. The id comes from the @default(cuid(2)) in the contract. +const [insertedUser] = await db.query(insertUser); +// insertedUser is { id, email }. The id comes from the @onCreate(cuid(2)) in the contract. ``` -Drop the `.returning(...)` call and the insert returns no rows, so run it with `db.runtime().execute(...)`, which resolves to `{ affectedRows }`. +Drop the `.returning(...)` call and the insert returns no rows, so run it with `db.execute(...)`, which resolves to `{ affectedRows }`. The builder writes updates and deletes too. Choose the rows with `.where(...)`, and add `.returning(...)` when you want columns back. `postId` and `tagId` here are values from your own code: @@ -208,8 +208,8 @@ const publishPost = db.sql.public.post .where((f, fns) => fns.eq(f.id, postId)).build(); const deleteTag = db.sql.public.tag.delete().where((f, fns) => fns.eq(f.id, tagId)).build(); -const { affectedRows } = await db.runtime().execute(publishPost); -await db.runtime().execute(deleteTag); +const { affectedRows } = await db.execute(publishPost); +await db.execute(deleteTag); ``` Neither of those two returns rows, so both go to `execute()`. Add `.returning(...)` to either one and you run it with `query()` instead. @@ -226,7 +226,7 @@ const users = db.sql.public.user .select("upperEmail", (f, fns) => fns.raw`UPPER(${f.email})`.returns("pg/text@1")) .limit(10).build(); -const rows = await db.runtime().query(users); +const rows = await db.query(users); // [{ id: 'cuid20000000000000000001', email: 'alice@prisma.io', upperEmail: 'ALICE@PRISMA.IO' }, ...] ``` @@ -250,7 +250,7 @@ const authorPostCounts = db.raw.sql` .returnsRow({ id: user.columns.id, email: user.columns.email, postCount: "pg/int8@1" }) .build(); -const rows = await db.runtime().query(authorPostCounts); +const rows = await db.query(authorPostCounts); ``` Quote identifiers as PostgreSQL requires; `"authorId"` keeps the camel case. Each entry in the `.returnsRow(...)` object says how to read one result column. An entry is either a column from the contract, as in `user.columns.id`, or a type name as a string for a result column the contract has no counterpart for. `"pg/int8@1"` is a 64-bit integer, so `postCount` comes back as a `bigint`. @@ -262,7 +262,7 @@ const publishDrafts = db.raw.sql` UPDATE "post" SET "published" = true WHERE "published" = false `.affectedCount().build(); -const { affectedRows } = await db.runtime().execute(publishDrafts); +const { affectedRows } = await db.execute(publishDrafts); ``` To use one raw statement inside another, interpolate it with `${...}`. End the inner statement at `.returnsRow(...)` without `.build()`; call `.build()` only on the outer one. Every un-built raw statement exposes its declared columns as `.returns`, so the outer statement can reuse them: @@ -290,7 +290,7 @@ const activeAuthors = db.raw.sql` postCount: authorsWithPosts.returns.postCount, }).build(); -const rows = await db.runtime().query(activeAuthors); +const rows = await db.query(activeAuthors); ``` A column the inner statement already declared can be reused in the outer row object, as `authorsWithPosts.returns.postCount` is here. The [raw queries reference](/orm/reference/raw-queries#the-client-level-dbrawsql-tag) documents every raw query method. @@ -387,7 +387,7 @@ Each row is a post document with an extra `author` field, and that field is an a `db.orm` holds your models by model name, as in `db.orm.public.User` on PostgreSQL. On MongoDB there is no schema segment, and the name is the collection: `db.orm.post`. -A built query that returns rows runs through `db.runtime().query(builtQuery)` on PostgreSQL. On MongoDB, await the connection first: `(await db.runtime()).query(builtQuery)`. A write that returns no rows goes to `execute(builtQuery)` instead, which resolves to `{ affectedRows }`. +A built query that returns rows runs through `db.query(builtQuery)` on PostgreSQL, and a write that returns no rows through `db.execute(builtQuery)`, which resolves to `{ affectedRows }`. `db.toSql(builtQuery)` returns the SQL and parameters the query would send, as `{ sql, params }`, without a database. On MongoDB, await the connection first: `(await db.runtime()).query(builtQuery)`. Inside a transaction, `tx` is the argument your callback receives, and you run the built query on it: `await db.transaction(async (tx) => tx.query(builtQuery))`. `tx.query(...)` and `tx.execute(...)` run built queries inside a transaction; see [Transactions](/orm/fundamentals/transactions). diff --git a/apps/docs/content/docs/orm/fundamentals/reading-data.mdx b/apps/docs/content/docs/orm/fundamentals/reading-data.mdx index ae8e3d5d38f..ff91299f078 100644 --- a/apps/docs/content/docs/orm/fundamentals/reading-data.mdx +++ b/apps/docs/content/docs/orm/fundamentals/reading-data.mdx @@ -57,7 +57,7 @@ For Prisma ORM 7 users, `findMany` and `findFirst` / `findUnique` map directly o ## Example schema -All examples on this page are based on the contract below. The file is `contract.prisma`, and it is still written in the Prisma schema language, so it looks like the `schema.prisma` you already know. `@default(cuid(2))` generates a CUID version 2 id. +All examples on this page are based on the contract below. The file is `contract.prisma`, and it is still written in the Prisma schema language, so it looks like the `schema.prisma` you already know. `@onCreate(cuid(2))` generates a CUID version 2 id.
@@ -65,7 +65,7 @@ All examples on this page are based on the contract below. The file is `contract ```prisma tab="PostgreSQL" model User { - id String @id @default(cuid(2)) + id String @id @onCreate(cuid(2)) email String @unique name String? createdAt DateTime @default(now()) @@ -73,7 +73,7 @@ model User { } model Post { - id String @id @default(cuid(2)) + id String @id @onCreate(cuid(2)) title String content String? published Boolean @@ -108,7 +108,7 @@ model Post {
:::note -On MongoDB, `@map("_id")` renames the field to `_id` everywhere: you filter on `_id`, you select `_id`, and the returned document has an `_id` key. That is why the MongoDB examples below never say `id`. +On MongoDB, `@map("_id")` stores the field as the document's `_id`, but your code still calls it `id`: you filter on `id`, you select `id`, and the returned document has an `id` key. ::: ## Fetch many records or one @@ -145,7 +145,7 @@ const user = await db.orm.public.User.first({ id: userId }); ``` ```typescript tab="MongoDB" -const user = await db.orm.users.where({ _id: userId }).first(); +const user = await db.orm.users.where({ id: userId }).first(); ``` If you want an error instead of `null` when nothing matches, call `.firstOrThrow()`, the replacement for `findUniqueOrThrow` and `findFirstOrThrow`: @@ -260,7 +260,7 @@ const users = await db.orm.public.User.select("id", "email").all(); ``` ```typescript tab="MongoDB" -const users = await db.orm.users.select("_id", "email").all(); +const users = await db.orm.users.select("id", "email").all(); ``` ```js no-copy diff --git a/apps/docs/content/docs/orm/fundamentals/relations-and-joins.mdx b/apps/docs/content/docs/orm/fundamentals/relations-and-joins.mdx index dcc0ff08bb6..22400bbd06e 100644 --- a/apps/docs/content/docs/orm/fundamentals/relations-and-joins.mdx +++ b/apps/docs/content/docs/orm/fundamentals/relations-and-joins.mdx @@ -54,14 +54,14 @@ The model that holds the foreign key declares the relation, and the `@unique` on ```prisma model Profile { - id String @id @default(cuid(2)) + id String @id @onCreate(cuid(2)) bio String userId String @unique user User @relation(fields: [userId], references: [id]) } ``` -Write `@default(cuid(2))` and you never pass `id` yourself: `cuid(2)` generates the id (the `2` is the CUID version). To read a profile with its user, query from the profile and include the relation: +Write `@onCreate(cuid(2))` and you never pass `id` yourself: `cuid(2)` generates the id (the `2` is the CUID version). To read a profile with its user, query from the profile and include the relation: ```typescript const profileWithUser = await db.orm.public.Profile @@ -75,7 +75,7 @@ const profileWithUser = await db.orm.public.Profile ```prisma model User { - id String @id @default(cuid(2)) + id String @id @onCreate(cuid(2)) email String @unique profile Profile? } @@ -105,13 +105,13 @@ The child stores the parent's id, and the parent declares a list field: ```prisma model User { - id String @id @default(cuid(2)) + id String @id @onCreate(cuid(2)) email String @unique posts Post[] } model Post { - id String @id @default(cuid(2)) + id String @id @onCreate(cuid(2)) title String published Boolean @default(false) createdAt DateTime @default(now()) @@ -169,14 +169,14 @@ Write three models: on each side, declare a list field typed as the other model, ```prisma model Post { - id String @id @default(cuid(2)) + id String @id @onCreate(cuid(2)) title String published Boolean @default(false) tags Tag[] } model Tag { - id String @id @default(cuid(2)) + id String @id @onCreate(cuid(2)) name String @unique posts Post[] } @@ -245,7 +245,7 @@ See [what is not available yet](/orm/coming-from-prisma-orm-7#not-available-yet) ```typescript const user = await db.orm.users.create({ email: "jane@prisma.io" }); -const post = await db.orm.posts.create({ title: "Hello", published: false, authorId: user._id }); +const post = await db.orm.posts.create({ title: "Hello", published: false, authorId: user.id }); ``` If you need the link records themselves, for example because the join table has columns of its own, query its model directly: @@ -312,7 +312,7 @@ type Address { } model User { - id ObjectId @id @map("_id") // the property is _id, because of @map("_id") + id ObjectId @id @map("_id") // stored as _id; the property is id email String address Address? posts Post[] diff --git a/apps/docs/content/docs/orm/fundamentals/transactions.mdx b/apps/docs/content/docs/orm/fundamentals/transactions.mdx index 2c00fa26847..b3b3c68ea09 100644 --- a/apps/docs/content/docs/orm/fundamentals/transactions.mdx +++ b/apps/docs/content/docs/orm/fundamentals/transactions.mdx @@ -12,7 +12,7 @@ Prisma ORM 8 runs on PostgreSQL, SQLite, and MongoDB, but there is no MySQL supp ## Run writes in a transaction -Use a transaction whenever one business operation spans more than one write: creating a user with their first records, moving a value between two rows, or deleting a parent after its children. Pass a callback to `db.transaction(...)`, and inside it, query through `tx` instead of `db`. `tx.orm` is the model API, what `prisma.user` was in Prisma ORM 7; `tx.sql` is the SQL query builder, and both work the same as `db.orm` and `db.sql`. Every query you make on `tx` runs inside the transaction, and `db.transaction(...)` returns whatever your callback returns: +Use a transaction whenever one business operation spans more than one write: creating a user with their first records, moving a value between two rows, or deleting a parent after its children. Pass a callback to `db.transaction(...)`, and inside it, query through `tx` instead of `db`. `tx.orm` is the model API, what `prisma.user` was in Prisma ORM 7; `tx.sql` is the SQL query builder, and both work the same as `db.orm` and `db.sql`. `tx` also has `raw`, `prepare`, `query`, and `execute`, as `db` does. Every query you make on `tx` runs inside the transaction, and `db.transaction(...)` returns whatever your callback returns: ```typescript import { db } from "./prisma/db"; @@ -153,19 +153,21 @@ Run `npx prisma contract emit` once first; `db.ts` imports the two files it writ ```typescript title="src/prisma/db.ts" import "dotenv/config"; import mongo from "@prisma/orm-mongo/runtime"; +import { contractFromJson } from "@prisma/orm-mongo/static"; import { MongoClient } from "mongodb"; import type { Contract } from "./contract.d"; // the two files `prisma contract emit` writes import contractJson from "./contract.json" with { type: "json" }; +export const contract = contractFromJson(contractJson); export const client = new MongoClient(process.env["DATABASE_URL"]!); -export const db = mongo({ - contractJson, +export const db = mongo({ + contract, mongoClient: client, dbName: "app", }); ``` -Keep the `contractJson` import and the `Contract` type, and run `prisma contract emit` after every change to your contract, on PostgreSQL and on MongoDB alike. +Keep the `contract` value built from `contract.json` and the `Contract` type, and run `prisma contract emit` after every change to your contract, on PostgreSQL and on MongoDB alike. `dbName` is now the only thing that picks the database, so set it to the database your driver code reads, and `client.db("app")` and Prisma ORM stay on the same one. An empty `dbName` is an error. ```typescript diff --git a/apps/docs/content/docs/orm/fundamentals/writing-data.mdx b/apps/docs/content/docs/orm/fundamentals/writing-data.mdx index 171b758264c..6380354a804 100644 --- a/apps/docs/content/docs/orm/fundamentals/writing-data.mdx +++ b/apps/docs/content/docs/orm/fundamentals/writing-data.mdx @@ -16,7 +16,7 @@ On PostgreSQL the path to a model is `db.orm..`, so the `User ## Example schema -Examples use this contract. In Prisma ORM 8 the schema file is `contract.prisma` instead of `schema.prisma`; the docs call it your contract. `@default(cuid(2))` means Prisma ORM generates the `id` for you, so you never pass one: +Examples use this contract. In Prisma ORM 8 the schema file is `contract.prisma` instead of `schema.prisma`; the docs call it your contract. `@onCreate(cuid(2))` means Prisma ORM generates the `id` for you, so you never pass one:
@@ -24,7 +24,7 @@ Examples use this contract. In Prisma ORM 8 the schema file is `contract.prisma` ```prisma tab="PostgreSQL" model User { - id String @id @default(cuid(2)) + id String @id @onCreate(cuid(2)) email String @unique name String? createdAt DateTime @default(now()) @@ -32,7 +32,7 @@ model User { } model Post { - id String @id @default(cuid(2)) + id String @id @onCreate(cuid(2)) title String content String? published Boolean @@ -72,7 +72,7 @@ To put models in another PostgreSQL schema, wrap them in a `namespace` block: ```prisma namespace billing { model Invoice { - id String @id @default(cuid(2)) + id String @id @onCreate(cuid(2)) amount Int } } @@ -102,10 +102,10 @@ const user = await db.orm.users.create({ name: "Jane", createdAt: new Date(), }); -// user._id is filled in by the server +// user.id is filled in by the server ``` -The returned record is complete, so you can use the generated values right away (PostgreSQL shown; on MongoDB the key is `_id`): +The returned record is complete, so you can use the generated values right away (PostgreSQL shown): ```js no-copy { id: 'cuid20000000000000000003', email: 'jane@prisma.io', name: 'Jane', createdAt: 2026-07-06T09:09:56.119Z } @@ -143,7 +143,7 @@ On MongoDB the driver throws its own error, and a duplicate key gives you an err MongoDB contracts do not support `@default`. For a timestamp, type the field `temporal.createdAt()` or `temporal.updatedAt()` and a client created by `mongo()` fills it in, as [Field types](/orm/data-modeling/mongodb#field-types) shows. Pass any other value yourself. -`@map("_id")` renames the id field to `_id` everywhere, so the returned document has an `_id` key and not an `id` key. +`@map("_id")` stores the id field as the document's `_id`, and the returned document still has an `id` key, the field's name in the contract. ::: @@ -210,13 +210,13 @@ The callback returns an array, so you can apply several operations in one update `set` and `unset` work on any field, while `inc` and `mul` are on number fields only. `push`, `pull`, `addToSet`, and `pop` are for array fields, and you call them the same way: `p.tags.push("news")`. These field operations are MongoDB only: PostgreSQL has no callback form of `update`, so on PostgreSQL you pass an object. See [Field update operations](/orm/reference/orm-client#field-update-operations) in the reference. -There is no `increment` on PostgreSQL, so to add to a number in place, write the update as raw SQL. For a `views Int` column added to `Post`, `db.raw.sql` writes a raw statement, `.affectedCount()` says you want the row count back as `{ affectedRows }`, `.build()` finishes it, and `db.runtime().execute(...)` runs it; [Advanced queries](/orm/fundamentals/advanced-queries) explains raw SQL: +There is no `increment` on PostgreSQL, so to add to a number in place, write the update as raw SQL. For a `views Int` column added to `Post`, `db.raw.sql` writes a raw statement, `.affectedCount()` says you want the row count back as `{ affectedRows }`, `.build()` finishes it, and `db.execute(...)` runs it; [Advanced queries](/orm/fundamentals/advanced-queries) explains raw SQL: ```typescript const query = db.raw.sql`UPDATE post SET views = views + 1 WHERE id = ${postId}` .affectedCount() .build(); -const { affectedRows } = await db.runtime().execute(query); +const { affectedRows } = await db.execute(query); ``` ## Delete one record diff --git a/apps/docs/content/docs/orm/middleware/authoring-custom-middleware.mdx b/apps/docs/content/docs/orm/middleware/authoring-custom-middleware.mdx index 6145a7ebd2d..6a079a0cb16 100644 --- a/apps/docs/content/docs/orm/middleware/authoring-custom-middleware.mdx +++ b/apps/docs/content/docs/orm/middleware/authoring-custom-middleware.mdx @@ -51,7 +51,7 @@ The `name` is the label Prisma ORM uses when it talks about your middleware, so If a call gives you back a count instead of rows, `afterQuery` is not called for it and `afterExecute` is called instead. On the ORM API the calls that return a count are `createAndCount`, `updateAndCount`, and `deleteAndCount`. Every other call returns rows and reaches `afterQuery`, including `create`, `update`, `delete`, `updateAll`, and `deleteAll`. -The SQL query builder is the other way to write a query on the same client, `db.sql.public.User...` in place of `db.orm.public.User...`. With it you build a query and run it in two steps: `.build()` gives you the query as a plain object, the plan, and a method on `db.runtime()` runs it. `db.runtime().query(plan)` returns rows and calls `afterQuery`, while `db.runtime().execute(plan)` reports how many rows the statement changed and calls `afterExecute`. +The SQL query builder is the other way to write a query on the same client, `db.sql.public.User...` in place of `db.orm.public.User...`. With it you build a query and run it in two steps: `.build()` gives you the query as a plain object, the plan, and a method on `db` runs it. `db.query(plan)` returns rows and calls `afterQuery`, while `db.execute(plan)` reports how many rows the statement changed and calls `afterExecute`. To have the logger cover the counting calls as well, add a second hook to the object `queryLogger()` returns in `src/prisma/query-logger.ts`, next to `afterQuery`: @@ -73,17 +73,18 @@ You register a middleware by adding it to the `middleware` array where you creat ```ts title="src/prisma/db.ts" import postgres from '@prisma/orm-postgres/runtime'; import { queryLogger } from './query-logger'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const db = postgres({ - contractJson, +export const db = postgres({ + contract: contractFromJson(contractJson), url: process.env['DATABASE_URL']!, middleware: [queryLogger()], }); ``` -The only line added here is the `middleware` option. The `src/prisma/db.ts` that `npm create prisma@latest` generates has more in it than this, including a `connectDatabase` export that the generated `seed.ts` imports, so keep that file and add the `middleware` option to the `postgres({ ... })` calls in it rather than replacing it with the file above. Those calls only run when the app is started with `DATABASE_URL` exported, as `npm run dev` does. With `npm run dev:composer`, the generated file takes its client from Composer through `service.load()` instead, and the `middleware` option in `db.ts` is never used, so run the app with `DATABASE_URL` exported while you follow this guide. If you set your project up by hand, [The data contract](/orm/contract-authoring/the-data-contract) covers where `contract.json` and `contract.d.ts` come from. +The only line added here is the `middleware` option. The `src/prisma/db.ts` that `npm create prisma@latest` generates has more in it than this, including a `connectDatabase` export that the generated `seed.ts` imports, so keep that file and add the `middleware` option to the `postgres({ ... })` calls in it rather than replacing it with the file above. Those calls only run when the app is started with `DATABASE_URL` exported, as `npm run dev` does. With `npm run dev:composer`, the generated file takes its client from Composer through `service.load()` instead, and the `middleware` option in `db.ts` is never used, so run the app with `DATABASE_URL` exported while you follow this guide. If you set your project up by hand, [The data contract](/orm/contract-authoring/the-data-contract) covers where `contract.json` and `contract.d.ts` come from. That one line is all the setup there is, and every query on this client now passes through the logger, whether you wrote it with the ORM API (`db.orm.public.User...`) or with the SQL query builder. @@ -260,13 +261,14 @@ import { AsyncLocalStorage } from 'node:async_hooks'; import postgres from '@prisma/orm-postgres/runtime'; import { BinaryExpr, ColumnRef, ParamRef } from '@prisma/orm-postgres/relational-core/ast'; import { scopeUserSelects } from './scope-user-selects'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; export const currentName = new AsyncLocalStorage(); -export const db = postgres({ - contractJson, +export const db = postgres({ + contract: contractFromJson(contractJson), url: process.env['DATABASE_URL']!, middleware: [ scopeUserSelects(() => { diff --git a/apps/docs/content/docs/orm/middleware/built-in-budgets.mdx b/apps/docs/content/docs/orm/middleware/built-in-budgets.mdx index 1499f8fc62a..dc0890bda03 100644 --- a/apps/docs/content/docs/orm/middleware/built-in-budgets.mdx +++ b/apps/docs/content/docs/orm/middleware/built-in-budgets.mdx @@ -34,11 +34,12 @@ Register `budgets` in the `postgres(...)` call that creates your client, with th ```ts title="src/prisma/db.ts" import postgres from '@prisma/orm-postgres/runtime'; import { budgets } from '@prisma/orm-postgres/family-runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const db = postgres({ - contractJson, +export const db = postgres({ + contract: contractFromJson(contractJson), url: process.env['DATABASE_URL']!, middleware: [ budgets({ @@ -133,11 +134,12 @@ So for a job that genuinely has to read millions of rows, such as a nightly expo ```ts title="scripts/export/db.ts" import postgres from '@prisma/orm-postgres/runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from '../../src/prisma/contract.d'; import contractJson from '../../src/prisma/contract.json' with { type: 'json' }; -export const db = postgres({ - contractJson, +export const db = postgres({ + contract: contractFromJson(contractJson), url: process.env['DATABASE_URL']!, }); ``` diff --git a/apps/docs/content/docs/orm/middleware/built-in-cache.mdx b/apps/docs/content/docs/orm/middleware/built-in-cache.mdx index 351664195d5..7f6f9d192fa 100644 --- a/apps/docs/content/docs/orm/middleware/built-in-cache.mdx +++ b/apps/docs/content/docs/orm/middleware/built-in-cache.mdx @@ -21,13 +21,14 @@ Your contract is the Prisma ORM 8 name for your schema: `contract.prisma` in a g ```ts title="src/prisma/db.ts" import { createCacheMiddleware } from '@prisma/orm-extension-middleware-cache'; import postgres from '@prisma/orm-postgres/runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; export const cache = createCacheMiddleware(); -export const db = postgres({ - contractJson, +export const db = postgres({ + contract: contractFromJson(contractJson), url: process.env['DATABASE_URL']!, middleware: [cache], }); @@ -45,7 +46,7 @@ You tell Prisma ORM that a read may be cached by attaching a cache annotation to Prisma ORM gives you two ways to write the same read, the SQL query builder and the ORM API, and both of them can be cached. Use whichever one the rest of your code already uses, because the annotation attaches differently in each. -With the SQL query builder you build the query yourself, call `.build()`, and run the result with `db.runtime().query(...)` for rows or `db.runtime().execute(...)` for a count. Only rows can be cached, so nothing you run with `db.runtime().execute(...)` ever is. To cache a read, chain `.annotate(cacheAnnotation({}))` onto the query before you call `.build()`. `db.sql.public.User` names the `User` table in the PostgreSQL schema `public`. +With the SQL query builder you build the query yourself, call `.build()`, and run the result with `db.query(...)` for rows or `db.execute(...)` for a count. Only rows can be cached, so nothing you run with `db.execute(...)` ever is. To cache a read, chain `.annotate(cacheAnnotation({}))` onto the query before you call `.build()`. `db.sql.public.User` names the `User` table in the PostgreSQL schema `public`. ```ts title="src/prisma/get-users-cached.ts" import { cacheAnnotation } from '@prisma/orm-extension-middleware-cache'; @@ -58,7 +59,7 @@ export async function getUsersCached() { .limit(10) .build(); - return db.runtime().query(plan); + return db.query(plan); } ``` @@ -96,7 +97,7 @@ const plan = db.sql.public.User .limit(10) .build(); -return db.runtime().query(plan); +return db.query(plan); ``` A `key` is used exactly as you wrote it, and the cache adds nothing to it, so any two reads that pick the same `key` share one entry even when their parameter values or their models differ: the rows stored for `id: 1` come back for `id: 2` as well. Either use `key` only on a query whose parameter values never change, or put the parameter values into the string yourself, behind a prefix no other read in your app uses: @@ -127,7 +128,7 @@ export const cache = createCacheMiddleware({ Two different queries must never get the same key, or one query's rows are served for the other. If `deriveKey` throws, the read fails. -Your own calling code cannot see the difference, because `db.runtime().query(plan)` and `db.orm.public.User.first(...)` give you the same rows whether the cache answered or the database did. +Your own calling code cannot see the difference, because `db.query(plan)` and `db.orm.public.User.first(...)` give you the same rows whether the cache answered or the database did. A read inside `db.transaction(...)`, or on a connection you took out of the pool yourself with [`db.runtime().connection()`](/orm/reference/transactions-and-runtime#manual-connection-and-transaction-control), always goes to the database, and its rows are not stored. Code that holds a transaction or a connection expects to read back what it has just written, so the cache stays out of the way and you always see your own writes. diff --git a/apps/docs/content/docs/orm/middleware/built-in-lints.mdx b/apps/docs/content/docs/orm/middleware/built-in-lints.mdx index f21735b858d..2f93bbdaf7b 100644 --- a/apps/docs/content/docs/orm/middleware/built-in-lints.mdx +++ b/apps/docs/content/docs/orm/middleware/built-in-lints.mdx @@ -13,11 +13,12 @@ You turn it on by adding it to the `middleware` list when you create your `db` c ```ts title="src/prisma/db.ts" import postgres from '@prisma/orm-postgres/runtime'; import { lints } from '@prisma/orm-postgres/family-runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const db = postgres({ - contractJson, +export const db = postgres({ + contract: contractFromJson(contractJson), url: process.env['DATABASE_URL']!, middleware: [lints()], }); @@ -75,14 +76,14 @@ warn { A rule set to `error` throws before the database sees anything, so the call you were awaiting rejects. It throws an ordinary `Error` with four extra properties beside its `message`: `code`, `category`, `severity`, and `details`. Use the `isRuntimeError` type guard, exported from `@prisma/orm-postgres/components/runtime`, so that TypeScript knows those properties are there and you can read `code` off the error. -The blocked `DELETE` below comes from the SQL query builder, where you build the query yourself, call `.build()`, and run the result with `db.runtime().query(...)` for rows or `db.runtime().execute(...)` for a count: +The blocked `DELETE` below comes from the SQL query builder, where you build the query yourself, call `.build()`, and run the result with `db.query(...)` for rows or `db.execute(...)` for a count: ```ts import { isRuntimeError } from '@prisma/orm-postgres/components/runtime'; try { const plan = db.sql.public.User.delete().build(); - await db.runtime().execute(plan); + await db.execute(plan); } catch (error) { if (isRuntimeError(error) && error.code === 'LINT.DELETE_WITHOUT_WHERE') { // add a where() clause, or use a client with the rule set to warn @@ -149,7 +150,7 @@ So a `select *` you wrote by hand is blocked: const plan = db.raw.sql`select * from "User"` .returnsRow({ id: 'pg/int4@1' }) .build(); -await db.runtime().query(plan); +await db.query(plan); ``` It throws an error whose `code` is `LINT.SELECT_STAR` and whose `message` is `Raw SQL plan selects all columns via *`. `returnsRow` tells Prisma ORM the type of each column the statement gives back, written as `pg/` plus the PostgreSQL type name plus `@1`, and [`fns.raw` and `.returns()`](/orm/reference/sql-query-builder#fnsraw-and-returns) lists the ids you can use. The `severities` key for the last rule is `readOnlyMutation`, and there is no documented way to mark a query read-only, so you are unlikely to see that one. @@ -191,7 +192,7 @@ const plan = db.sql.public.User .delete() .where((f, fns) => fns.ne(f.id, null)) .build(); -await db.runtime().execute(plan); +await db.execute(plan); ``` Both deletes pass the rule and run, because each query has a `WHERE` clause and that is all `LINT.DELETE_WITHOUT_WHERE` looks for, so you can leave `lints` on its defaults in the script that runs them. An `UPDATE` takes the same shape, as in `db.orm.public.User.where((u) => u.id.isNotNull()).updateAndCount({ name: 'x' })` or `db.sql.public.User.update({ name: 'x' }).where((f, fns) => fns.ne(f.id, null)).build()`. @@ -201,11 +202,12 @@ The second way is to build that script its own `db` client with the rule set to ```ts title="scripts/backfill/db.ts" import postgres from '@prisma/orm-postgres/runtime'; import { lints } from '@prisma/orm-postgres/family-runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from '../../src/prisma/contract.d'; import contractJson from '../../src/prisma/contract.json' with { type: 'json' }; -export const db = postgres({ - contractJson, +export const db = postgres({ + contract: contractFromJson(contractJson), url: process.env['DATABASE_URL']!, middleware: [lints({ severities: { deleteWithoutWhere: 'warn' } })], }); diff --git a/apps/docs/content/docs/orm/middleware/how-middleware-works.mdx b/apps/docs/content/docs/orm/middleware/how-middleware-works.mdx index b4f8aab01e6..fc12a5cd0f4 100644 --- a/apps/docs/content/docs/orm/middleware/how-middleware-works.mdx +++ b/apps/docs/content/docs/orm/middleware/how-middleware-works.mdx @@ -26,11 +26,12 @@ The `src/prisma/db.ts` below imports two files from your own project, `contract. import { createCacheMiddleware } from '@prisma/orm-extension-middleware-cache'; import postgres from '@prisma/orm-postgres/runtime'; import { budgets, lints } from '@prisma/orm-postgres/family-runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const db = postgres({ - contractJson, +export const db = postgres({ + contract: contractFromJson(contractJson), url: process.env['DATABASE_URL']!, middleware: [ createCacheMiddleware(), @@ -67,7 +68,7 @@ Add `queryLogger()` to the `middleware` array in `src/prisma/db.ts`, alongside t Every query goes through that list, whichever of the two APIs you wrote it with. With the ORM API you write `db.orm.public.User...`, where `public` is the PostgreSQL schema and `User` is the model in your contract. -With the [SQL query builder](/orm/reference/sql-query-builder) you build a query and run it in two steps. `.build()` returns a plan, which is the query as a plain object, not yet run. `db.runtime().query(plan)` runs it and returns rows, and `db.runtime().execute(plan)` runs it and returns how many rows it changed. +With the [SQL query builder](/orm/reference/sql-query-builder) you build a query and run it in two steps. `.build()` returns a plan, which is the query as a plain object, not yet run. `db.query(plan)` runs it and returns rows, and `db.execute(plan)` runs it and returns how many rows it changed. Both APIs send their queries on the same `db` object, so both see the same middleware. If your database is MongoDB, pass the same `middleware` option to `mongo(...)` from `@prisma/orm-mongo/runtime` and type your own middleware as `MongoMiddleware` from `@prisma/orm-mongo/family-runtime`. @@ -77,11 +78,11 @@ Both APIs send their queries on the same `db` object, so both see the same middl You write only the hooks you care about, and Prisma ORM skips the ones you leave out. Which hooks it calls depends on what your query gives back: if the query returns rows, one set of hooks runs, and if it only reports how many rows it changed, a shorter set runs instead. -**A row query** is anything that returns rows. On the ORM API that is every call except `createAndCount`, `updateAndCount`, and `deleteAndCount`, so `create`, `update`, and `delete` are row queries too. It is also what you get from `db.runtime().query(plan)`. If those names are new, the [queries table](/orm/coming-from-prisma-orm-7#queries) puts each of them next to the Prisma ORM 7 call it replaces. A row query calls your hooks in this order: +**A row query** is anything that returns rows. On the ORM API that is every call except `createAndCount`, `updateAndCount`, and `deleteAndCount`, so `create`, `update`, and `delete` are row queries too. It is also what you get from `db.query(plan)`. If those names are new, the [queries table](/orm/coming-from-prisma-orm-7#queries) puts each of them next to the Prisma ORM 7 call it replaces. A row query calls your hooks in this order: `beforeCompile` → `beforeQuery` → `interceptQuery` → `onRow` (once per row) → `afterQuery` → `afterTransaction` -**A non-returning write** is a statement that reports how many rows it affected rather than returning them. On the ORM API those are `createAndCount`, `updateAndCount`, and `deleteAndCount`, and with the SQL query builder it is whatever you hand to `db.runtime().execute(plan)`. These run a shorter sequence: +**A non-returning write** is a statement that reports how many rows it affected rather than returning them. On the ORM API those are `createAndCount`, `updateAndCount`, and `deleteAndCount`, and with the SQL query builder it is whatever you hand to `db.execute(plan)`. These run a shorter sequence: `beforeCompile` → `beforeExecute` → `interceptExecute` → `afterExecute` → `afterTransaction` diff --git a/apps/docs/content/docs/orm/migrations/applying-a-migration.mdx b/apps/docs/content/docs/orm/migrations/applying-a-migration.mdx index 59fa49246ce..964ed713439 100644 --- a/apps/docs/content/docs/orm/migrations/applying-a-migration.mdx +++ b/apps/docs/content/docs/orm/migrations/applying-a-migration.mdx @@ -108,9 +108,9 @@ When the state you want is an earlier one than the database already matches, pla When an operation fails, `db migrate` stops and prints what failed and why: ```text -✘ [MIGRATION.RUNNER_FAILED] Operation pgvector.install-vector-extension failed during execution: create extension "vector" +✘ [MIGRATION.EXECUTION_FAILED] Operation pgvector.install-vector-extension failed during execution: create extension "vector" why: extension "vector" is not available - docs: https://docs.prisma.io/docs/orm/v8/reference/error-reference/MIGRATION.RUNNER_FAILED + docs: https://docs.prisma.io/docs/orm/v8/reference/error-reference#MIGRATION.EXECUTION_FAILED ``` Here, an operation from the pgvector [extension package](#extension-spaces) failed because the `vector` PostgreSQL extension is not available on the database server. Install it there, or enable it in your database host's settings, and then run `db migrate` again. diff --git a/apps/docs/content/docs/orm/migrations/generating-a-migration.mdx b/apps/docs/content/docs/orm/migrations/generating-a-migration.mdx index c73e5018ffb..7d3a168df29 100644 --- a/apps/docs/content/docs/orm/migrations/generating-a-migration.mdx +++ b/apps/docs/content/docs/orm/migrations/generating-a-migration.mdx @@ -224,7 +224,7 @@ npx prisma migration check :::note[What's early] -Planning covers tables, columns, indexes, constraints, and the backfill placeholder shown above. What it cannot do yet is notice that you renamed something: if you rename an optional field, it plans the change as **drop column + add column**, and `migration plan` prints a warning that the change can lose data. Take that warning seriously, because `db migrate` runs destructive operations without asking, so read `migration show ` before you apply anything. To rename a column and keep the data in it, edit `migration.ts`, replace that pair of operations with a [`rawSql` operation](/orm/migrations/editing-a-migration#escape-hatch-raw-sql) that runs `ALTER TABLE ... RENAME COLUMN`, and recompile. To rename a table, replace the dropped and created table with [`renameTable`](/orm/reference/migration-api#schemas-and-tables), which keeps the rows and renames the constraints and indexes named after the table. One more difference from Prisma ORM 7: `migration plan` never asks you questions the way `migrate dev` did. +Planning covers tables, columns, indexes, constraints, and the backfill placeholder shown above. It cannot tell a rename from a drop and a create on its own. When a plan would drop a model or field that holds data, `migration plan` asks about it before it writes anything: pass `--rename :` to keep the data, or `--delete ` to let it go, as the [`migration plan` reference](/cli/migration-plan#options) shows. Where nobody can answer, the command fails with `CLI.CONSENT_REQUIRED`. `db migrate` runs destructive operations without asking, so read `migration show ` before you apply anything. ::: diff --git a/apps/docs/content/docs/orm/migrations/how-migrations-work.mdx b/apps/docs/content/docs/orm/migrations/how-migrations-work.mdx index 539d3b84a26..23371551fb7 100644 --- a/apps/docs/content/docs/orm/migrations/how-migrations-work.mdx +++ b/apps/docs/content/docs/orm/migrations/how-migrations-work.mdx @@ -118,11 +118,13 @@ Inside `ops.json`, an operation is not only the change itself: it also carries t - **Precheck**: confirms the database is in the state the change expects, here that `"user"` has no `phone` column. - **Execute**: the statements that make the change, here `ALTER TABLE "public"."user" ADD COLUMN "phone" text`. -- **Postcheck**: confirms the change is in the database, here that the `phone` column exists. Some operations have no postcheck. `db migrate` skips an operation only when its postcheck already passes, so an operation with no postcheck always runs. +- **Postcheck**: confirms the full change is in the database, here that the `phone` column exists with the type `text`. A postcheck compares the whole definition, such as a column's type and nullability or an index's table and columns, not just a name. Some operations have no postcheck. `db migrate` skips an operation only when its postcheck already passes, so an operation with no postcheck always runs. When `db migrate` reaches an operation, it runs the postcheck first. If the postcheck passes, the change is already in the database, so `db migrate` skips the operation. If it does not pass, `db migrate` runs the precheck, then the statements, then the postcheck again, and it stops the run if either check fails. Once the operations are done, it checks the database against the contract before it updates the marker, which catches a database that satisfied every individual operation but still doesn't match what you declared. -Say `"user"` already has a `phone` column of another type. The postcheck only asks whether a `phone` column exists, so it passes and the operation is skipped. The check against the contract then finds the wrong type, and the run fails with an error whose `code` is `MIGRATION.RUNNER_FAILED` and whose `why:` line reads `The resulting database schema does not satisfy the destination contract.` To see which columns differ, run [`npx prisma db verify --schema-only`](/cli/db-verify), which compares the tables with your contract and skips the marker check. Then change the column by hand to the type your contract declares, here `text`, with `ALTER TABLE "public"."user" ALTER COLUMN "phone" TYPE text;`, and run `db migrate` again. [Drift](/orm/migrations/rollbacks-and-recovery#drift-when-the-database-isnt-where-migrations-left-it) covers other changes made outside a migration. +Say `"user"` already has a `phone` column of another type. The postcheck does not pass, because the type differs, so `db migrate` runs the precheck. The precheck finds a `phone` column, and the run fails with an error whose `code` is `MIGRATION.PRECHECK_FAILED`. To see which columns differ, run [`npx prisma db verify --schema-only`](/cli/db-verify), which compares the tables with your contract and skips the marker check. Then change the column by hand to the type your contract declares, here `text`, with `ALTER TABLE "public"."user" ALTER COLUMN "phone" TYPE text;`, and run `db migrate` again. [Drift](/orm/migrations/rollbacks-and-recovery#drift-when-the-database-isnt-where-migrations-left-it) covers other changes made outside a migration. + +The checks are written into `ops.json` when `migration.ts` runs, so an `ops.json` written before `8.0.0-rc.19` keeps its older checks, some of which only asked whether an object of that name exists. For each such migration that is not applied yet, run its `migration.ts` again (`node /migration.ts`) before you apply it. An applied migration needs nothing, because its operations do not run again. Each operation also has an `operationClass`, which says what kind of change the operation makes to the database and which you choose yourself when you write a raw SQL operation: @@ -205,6 +207,8 @@ Here is the whole loop, from idea to typed code: The commands, the file layout, the migration history, and the checks work the same way on PostgreSQL and MongoDB. On PostgreSQL, operations run SQL statements such as `ALTER TABLE`. On MongoDB, they create collections, indexes, and JSON Schema validators. +Each MongoDB collection that Prisma ORM manages gets a closed `$jsonSchema` validator, whether you write the contract in PSL or in TypeScript. It refuses a field the model does not declare, a missing required field, and a value of the wrong BSON type. A collection that stores several models, without one being a variant of another, gets a validator that accepts the documents of each model. MongoDB does not check documents already stored when a validator is added, so fix any document that does not match its model first. Then apply the validator with `npx prisma db update`, or with `npx prisma migration plan` followed by `npx prisma db migrate`. To leave a collection without a validator in a TypeScript contract, give its model `controlPolicy: 'observed'`, and Prisma ORM creates and changes nothing in that collection. + Each database keeps its own [marker and ledger](/orm/migrations/the-migration-graph#terms-used-on-this-page), where the ledger is that database's list of every migration applied to it, and `migration log` is the command that reads it. :::note[Migrations are early] diff --git a/apps/docs/content/docs/orm/migrations/rollbacks-and-recovery.mdx b/apps/docs/content/docs/orm/migrations/rollbacks-and-recovery.mdx index eba9a4c1cd4..a4562a4ffa6 100644 --- a/apps/docs/content/docs/orm/migrations/rollbacks-and-recovery.mdx +++ b/apps/docs/content/docs/orm/migrations/rollbacks-and-recovery.mdx @@ -72,9 +72,9 @@ Once you apply this migration with `--advance-ref db`, the `db` ref names a stat When a migration fails, `db migrate` stops at the operation that failed rather than carrying on, and it tells you which operation it was and why: ```text -✘ [MIGRATION.RUNNER_FAILED] Operation alterNullability.setNotNull.user.nickname failed during precheck: ensure no NULL values in "nickname" - why: Migration runner failed - docs: https://docs.prisma.io/docs/orm/v8/reference/error-reference/MIGRATION.RUNNER_FAILED +✘ [MIGRATION.PRECHECK_FAILED] 3 rows of "user" hold NULL in "nickname", so it cannot become required. + why: Operation alterNullability.setNotNull.user.nickname failed during precheck: ensure no NULL values in "nickname" + docs: https://docs.prisma.io/docs/orm/v8/reference/error-reference#MIGRATION.PRECHECK_FAILED ``` How much a failed run leaves behind depends on your database, and [when something goes wrong](/orm/migrations/applying-a-migration#when-something-goes-wrong) explains what you are left to clean up on PostgreSQL and on MongoDB before you try again. diff --git a/apps/docs/content/docs/orm/reference/migration-api.mdx b/apps/docs/content/docs/orm/reference/migration-api.mdx index e8b46430570..12e9c641155 100644 --- a/apps/docs/content/docs/orm/reference/migration-api.mdx +++ b/apps/docs/content/docs/orm/reference/migration-api.mdx @@ -124,47 +124,27 @@ Each operation is a method you call on the migration, `this.({ ... })`, wi | --- | --- | --- | --- | | `addColumn` | `schema`, `table`, `column` | `ALTER TABLE "public"."user" ADD COLUMN "bio" text` | additive | | `dropColumn` | `schema`, `table`, `column` | `ALTER TABLE "public"."user" DROP COLUMN "bio"` | destructive | -| `alterColumnType` | `schema`, `table`, `column`, `options` | `ALTER TABLE "public"."post" ALTER COLUMN "views" TYPE bigint USING "views"::bigint` | destructive | -| `setNotNull` | `schema`, `table`, `column` | `ALTER TABLE "public"."post" ALTER COLUMN "views" SET NOT NULL` | destructive | +| `alterColumnType` | `schema`, `table`, `column`, `type`, `using?`, `raw?`, `operationClass?` | `ALTER TABLE "public"."post" ALTER COLUMN "views" TYPE bigint` | destructive, or data with `using` | +| `setNotNull` | `schema`, `table`, `column` | `ALTER TABLE "public"."post" ALTER COLUMN "views" SET NOT NULL` | widening | | `dropNotNull` | `schema`, `table`, `column` | `ALTER TABLE "public"."post" ALTER COLUMN "views" DROP NOT NULL` | widening | -| `setDefault` | `schema`, `table`, `column`, `operationClass?` | `ALTER TABLE "public"."user" ALTER COLUMN "name" SET DEFAULT 'anonymous'` | additive | -| `dropDefault` | `schema`, `table`, `column` | `ALTER TABLE "public"."user" ALTER COLUMN "name" DROP DEFAULT` | destructive | +| `setDefault` | `schema`, `table`, `column` | `ALTER TABLE "public"."user" ALTER COLUMN "name" SET DEFAULT 'anonymous'` | additive, or widening when the column already has a default | +| `dropDefault` | `schema`, `table`, `column` | `ALTER TABLE "public"."user" ALTER COLUMN "name" DROP DEFAULT` | widening | `addColumn` and `setDefault` take a `col(...)` call as `column`. The other methods take the column's name as a string. -`alterColumnType` takes the new type in `options`, which has four fields: - -| Field | What to put in it | -| --- | --- | -| `qualifiedTargetType` | The new type, as it appears in the `ALTER COLUMN ... TYPE` statement: `'bigint'`, `'varchar(255)'`. | -| `formatTypeExpected` | The same type, spelled the way PostgreSQL itself names it. The postcheck compares the column's type after the change with this string, and the run stops if they differ. See the table below. | -| `rawTargetTypeForLabel` | The type as the CLI prints it in the operation's label. It affects nothing else, so use the same value as `qualifiedTargetType`. | -| `using` | Optional. The expression in the `USING` clause, for a conversion PostgreSQL cannot do on its own. Without it, the column is cast to the new type with `::`. | +`alterColumnType` takes the new type in `type`, written as a migration writes it, such as `'int8'`, `'varchar(255)'`, or `'"Mood"'` for an enum. Prisma ORM works out how PostgreSQL prints that type through the data types of your configured target and extensions, and the postcheck compares the column's type with it. A type it cannot read fails with `MIGRATION.COLUMN_TYPE_UNREADABLE`. For a column of a type no data type declares, pass the type as PostgreSQL prints it and set `raw: true`, and it is checked as written. ```ts -this.alterColumnType({ - schema: 'public', - table: 'post', - column: 'views', - options: { qualifiedTargetType: 'bigint', formatTypeExpected: 'bigint', rawTargetTypeForLabel: 'bigint' }, -}), +this.alterColumnType({ schema: 'public', table: 'post', column: 'views', type: 'int8' }), ``` -PostgreSQL spells most types the way you wrote them. These are the common ones it spells differently, as `\d post` in psql prints them: +`using` is the expression in the `USING` clause, for a conversion PostgreSQL cannot do on its own. Without it, the column is cast to the new type. The operation is `data` when it has a `using`, and `destructive` otherwise; pass `operationClass` (`'widening'`, `'destructive'`, or `'data'`) to choose another class. -| You write | `formatTypeExpected` | -| --- | --- | -| `varchar(255)`, `varchar` | `character varying(255)`, `character varying` | -| `char(3)` | `character(3)` | -| `int4`, `int8`, `int2` | `integer`, `bigint`, `smallint` | -| `float8`, `float4` | `double precision`, `real` | -| `bool` | `boolean` | -| `timestamptz`, `timestamp` | `timestamp with time zone`, `timestamp without time zone` | -| `int[]` | `integer[]` | +When the column is an `autoincrement()` column, `alterColumnType` also changes the sequence the column owns to the new width, with `ALTER SEQUENCE ... AS bigint` for an `int8` column. It first refuses, before any statement runs, when the sequence has already handed out a value too large for the new width. A migration applied before `8.0.0-rc.19` left the sequence at its old width. Change it yourself, for example `ALTER SEQUENCE "public"."post_id_seq" AS bigint`; `pg_get_serial_sequence('"public"."post"', 'id')` names the sequence. -`text`, `integer`, `bigint`, `numeric(10,2)`, `jsonb`, `uuid`, `date`, `bytea`, and `citext` are spelled the same in both places. +`migration.ts` refuses an `alterColumnType` with `using` to the type the migration's start contract already gives the column, with `MIGRATION.ALTER_COLUMN_TYPE_USING_WITHOUT_TYPE_CHANGE`. Its postcheck would already pass, so the conversion would never run. To change the values of a column whose type stays the same, write a [`dataTransform`](#data), which checks the rows themselves. -`setDefault` takes the column with its new default, and Prisma ORM writes the `DEFAULT` clause: `column: col('name', 'text', { default: lit('anonymous'), codecRef: { codecId: 'pg/text@1' } })`, or `column: col('createdAt', 'timestamptz', { default: fn('now()') })`. With a `codecRef`, the column's codec checks a literal default first, and a value it refuses fails with [`CONTRACT.DEFAULT_INVALID`](/orm/reference/error-reference#CONTRACT.DEFAULT_INVALID). A `migration.ts` that still passes `defaultSql`, written before `8.0.0-rc.15`, stops with [`MIGRATION.OPERATION_OPTION_REMOVED`](/orm/reference/error-reference#MIGRATION.OPERATION_OPTION_REMOVED); rewrite the call this way. Its `ops.json` still applies. When the column already has a default and you are replacing it, set `operationClass: 'widening'`, which only changes how the CLI reports the operation. +`setDefault` takes the column with its new default, and Prisma ORM writes the `DEFAULT` clause: `column: col('name', 'text', { default: lit('anonymous'), codecRef: { codecId: 'pg/text@1' } })`, or `column: col('createdAt', 'timestamptz', { default: fn('now()') })`. With a `codecRef`, the column's codec checks a literal default first, and a value it refuses fails with [`CONTRACT.DEFAULT_INVALID`](/orm/reference/error-reference#CONTRACT.DEFAULT_INVALID). A `migration.ts` that still passes `defaultSql`, written before `8.0.0-rc.15`, stops with [`MIGRATION.OPERATION_OPTION_REMOVED`](/orm/reference/error-reference#MIGRATION.OPERATION_OPTION_REMOVED); rewrite the call this way. Its `ops.json` still applies. The operation is widening when the migration's start contract gives the column a default, and additive otherwise. `setDefault` takes no `operationClass`; a call that still passes one runs and ignores it. `setDefault` has no postcheck, so it always runs, and replacing an existing default works. After the last operation, `db migrate` checks that the default matches your contract. `setNotNull` has an extra precheck that fails while the column still holds a `NULL`, so backfill the column with a `dataTransform` first. [Editing a migration](/orm/migrations/editing-a-migration#worked-example-making-a-column-required) shows the three steps. diff --git a/apps/docs/content/docs/orm/reference/orm-client.mdx b/apps/docs/content/docs/orm/reference/orm-client.mdx index 627c0e862fc..fa9e3fd0a18 100644 --- a/apps/docs/content/docs/orm/reference/orm-client.mdx +++ b/apps/docs/content/docs/orm/reference/orm-client.mdx @@ -44,7 +44,7 @@ enum Priority { } model User { - id Uuid @id @default(uuid()) + id Uuid @id @onCreate(uuid()) email String displayName String createdAt DateTime @default(now()) @@ -57,7 +57,7 @@ model User { } model Post { - id Uuid @id @default(uuid()) + id Uuid @id @onCreate(uuid()) title String userId Uuid priority Priority @default(Low) @@ -71,7 +71,7 @@ model Post { } model Tag { - id Uuid @id @default(uuid()) + id Uuid @id @onCreate(uuid()) label String @unique posts Post[] @@ -91,7 +91,7 @@ model PostTag { } model Task { - id Uuid @id @default(uuid()) + id Uuid @id @onCreate(uuid()) title String description String? status String @default("open") @@ -179,8 +179,8 @@ 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 exactly as written, `User`. -- 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. +- On MongoDB, `id ObjectId @id @map("_id")` stores the field as `_id` in the database. The ORM names it `id`, its name in the schema: you filter on `id`, write `id`, and the returned document has an `id` key. The same holds for every field with `@map`. The [pipeline builder](/orm/reference/pipeline-builder) and [raw commands](/orm/reference/raw-queries) work on stored documents, so they name the field `_id`. +- `Uuid` is a built-in type for a PostgreSQL `uuid` column. You do not import it. `@onCreate(uuid())` makes Prisma ORM fill 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 members as a PostgreSQL enum type, created with `CREATE TYPE`. Prisma ORM 7 stored every `enum` this way, so a database that Prisma ORM 7 created already has these types, and you keep them by declaring each one with `native_enum`, as [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) shows. A plain `enum` block in Prisma ORM 8 is stored as text. Give every `native_enum` member a value, because a member with no value is rejected. A field that uses it is typed `pg.enum(Priority)`. This page calls an enum written this way a native enum. The two sort differently; see [`orderBy()`](#orderby). @@ -192,7 +192,7 @@ Some parts of the schema the examples rely on: } model Post { - id Uuid @id @default(uuid()) + id Uuid @id @onCreate(uuid()) priority pg.enum(Priority) } ``` @@ -212,10 +212,12 @@ Create a PostgreSQL client with `postgres(...)`. `db.orm` holds your models by m ```typescript import postgres from '@prisma/orm-postgres/runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -const db = postgres({ contractJson, url: process.env.DATABASE_URL }); +export const contract = contractFromJson(contractJson); +export const db = postgres({ contract, url: process.env.DATABASE_URL! }); const users = await db.orm.public.User.all(); ``` @@ -225,14 +227,14 @@ If your contract uses a type from an extension package, pass that package to `ex ```typescript import pgvector from '@prisma/orm-extension-pgvector/runtime'; -const db = postgres({ - contractJson, - url: process.env.DATABASE_URL, +const db = postgres({ + contract, + 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). +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 pass the subclass to `postgres(...)` in the `collections` option. The code is under [Custom `Collection` subclass](#custom-collection-subclass). ### MongoDB @@ -240,10 +242,12 @@ Create a MongoDB client with `mongo(...)`. `db.orm` holds your models by collect ```typescript import mongo from '@prisma/orm-mongo/runtime'; +import { contractFromJson } from '@prisma/orm-mongo/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -const db = mongo({ contractJson, url: process.env.MONGODB_URL, dbName: 'app' }); +export const contract = contractFromJson(contractJson); +export const db = mongo({ contract, url: process.env.MONGODB_URL!, dbName: 'app' }); const users = await db.orm.users.all(); ``` @@ -273,6 +277,7 @@ Restrict a query to rows matching a filter. - 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. +- A filter value has the type you write to the field. A field whose type has parameters, such as `VarChar(50)`, reads as `Varchar<50>` but takes a plain `string` in `where()`, `first()`, `create()`, `update()`, and `upsert()`, so you need no cast. The same holds for `Char<36>` and `Numeric<10, 2>`. - `.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 @@ -370,7 +375,7 @@ Eagerly load a relation onto the returned rows. - 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, 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 @@ -406,7 +411,7 @@ 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 an ObjectId; compare it as 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). @@ -890,27 +895,28 @@ For Prisma ORM 7 users, `findUnique` and `findFirst` map onto `first()`: ### Custom `Collection` subclass -On PostgreSQL you can subclass `Collection` to add your own methods to a model, and register the subclass when you create the client. +On PostgreSQL and SQLite you can subclass `Collection` to add your own methods to a model, and pass the subclass to the client in the `collections` option. #### Remarks -- 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. +- PostgreSQL and SQLite. You cannot subclass a collection on MongoDB. +- `collections` maps a model name to your class. `postgres(...)`, `postgresServerless(...)`, `sqlite(...)`, and `supabase(...)` take it. The classes are used on `db.orm`, on every transaction's `tx.orm`, on each connection a serverless client opens, and on Supabase's role-bound clients. Models you leave out keep the built-in class. +- If the module that defines your classes needs the contract type, import only the type, `import type { Contract } from './contract.d'`, so that it does not import the module that creates the client. - `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. -- Your methods stay available after `where()`, `orderBy()`, `limit()`, `offset()`, `include()`, `distinct()`, `distinctOn()`, and `cursor()`, because each of them returns the collection's own class. `tasks.Task.where({ title }).bugs()` compiles. +- Your methods stay available after `where()`, `orderBy()`, `limit()`, `offset()`, `include()`, `distinct()`, `distinctOn()`, and `cursor()`, because each of them returns the collection's own class. `db.orm.public.Task.where({ title }).bugs()` compiles. - To override one of those chaining methods, or a method that returns rows, give the override a type parameter for the receiver and a `this` parameter of that type, and call the base method with `call`: `override limit(this: Self, n: number): Self { return super.limit.call(this, Math.min(n, 100)); }`. -- `apply` and `scope` are methods of every collection, so a member of your own with either name and another signature does not compile. See [Scopes](#scopes). +- `apply`, `scope`, and `using` are members of every collection, so a member of your own with one of those names does not compile. See [Scopes](#scopes) and [Package methods with `using`](#package-methods-with-using). +- The type `PostgresClient` describes a client without classes, and a client with classes fits it too. To name a client with classes, pass them as the second type argument: `PostgresClient`. #### 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 { Collection } from '@prisma/orm-postgres/orm-client'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; @@ -923,18 +929,55 @@ class TaskCollection extends Collection { } } -const client = postgres({ contractJson, url: process.env.DATABASE_URL }); -const runtime = await client.connect(); - -const tasks = orm({ - runtime, - context: client.context, +const db = postgres({ + contract: contractFromJson(contractJson), + url: process.env.DATABASE_URL!, collections: { Task: TaskCollection }, -}).public; // .public is the PostgreSQL schema +}); + +const bugs = await db.orm.public.Task.bugs().all(); +const features = await db.orm.public.Task.features().all(); +const recentBugs = await db.orm.public.Task.orderBy((t) => t.id.desc()).limit(10).bugs().all(); +const lockedBugs = await db.transaction((tx) => tx.orm.public.Task.bugs().forUpdate().all()); +``` + +On SQLite, `db.orm` holds the models directly, so the same method is `db.orm.Task.bugs()`. + +### Package methods with `using` [#package-methods-with-using] + +Packages add methods to a model's collection under its `using` member. Which methods a model has depends on its fields and indexes. + +#### Remarks + +- PostgreSQL. Each method returns a collection, so you chain it with `where()`, `orderBy()`, `limit()`, and the other methods, and finish with `all()` or `first()`. +- `@prisma/orm-postgres` adds one method for each [full-text index](/orm/contract-authoring/psl-syntax#full-text-indexes), named after the index. It takes a search query, the same kind that [`fullTextMatches()`](#full-text-search) takes, keeps the rows that match it, and orders them by relevance, best match first. +- `@prisma/orm-extension-pgvector` adds one method for each vector field, named `_nearest`. It takes a vector and orders the rows by cosine distance to it, nearest first. Pass a maximum distance as the second argument to keep only the rows within it. Without one, rows whose field is null come back last. The method exists only when the client has pgvector in `extensions`. +- `using` is a member of every collection, so your own [collection subclass](#custom-collection-subclass) cannot have a member named `using`. + +#### Examples + +##### Search through a full-text index -const bugs = await tasks.Task.bugs().all(); -const features = await tasks.Task.features().all(); -const recentBugs = await tasks.Task.orderBy((t) => t.id.desc()).limit(10).bugs().all(); +```prisma title="src/prisma/contract.prisma (excerpt)" +model Post { + id Uuid @id @onCreate(uuid()) + title String + + @@fullTextIndex([title], name: "post_search") + @@map("post") +} +``` + +```typescript +import { websearchToTsquery } from '@prisma/orm-postgres/target/full-text'; + +const posts = await db.orm.public.Post.using.post_search(websearchToTsquery('prisma orm')).limit(10).all(); +``` + +##### Find the nearest vectors + +```typescript +const similar = await db.orm.public.Post.using.embedding_nearest(queryEmbedding, 0.3).limit(5).all(); ``` ### Scopes @@ -1009,8 +1052,8 @@ Insert a single row and return it. - Available for PostgreSQL and MongoDB. - 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 document as it is stored, with the `_id` the server assigned, and converts its values the same way a read does. It does not query the database again to do this. On PostgreSQL the returned row is read from the database already. +- On MongoDB, `id`, the field stored as `_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 document as it is stored, with the `id` the server assigned, and converts its values the same way a read does. It does not query the database again to do this. On PostgreSQL the returned row is read from the database already. - On MongoDB, `create()` fails with an error whose `code` is `RUNTIME.ENCODE_FAILED`, before anything is written, when a value does not fit its field: a value of the wrong type, a fraction or an out-of-range number for an `Int32` field, a value that is not one of the field's enum values, or `null` for a field that is not optional. The error names the field. - 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. @@ -1045,7 +1088,7 @@ const user = await db.orm.users.create({ role: 'reader', address: null, }); -// user._id is the server-assigned id +// user.id is the server-assigned id ``` ##### Nested `create()` on the side without the foreign key (PostgreSQL) [#nested-create-on-a-child-owned-relation-postgresql] @@ -1221,14 +1264,14 @@ const updated = await db.orm.public.User.where({ id: bobId }).update({ displayNa ``` ```typescript tab="MongoDB" -const updated = await db.orm.users.where({ _id: bobId }).update({ bio: 'Now with a bio' }); +const updated = await db.orm.users.where({ id: bobId }).update({ bio: 'Now with a bio' }); ``` ##### Field-operations callback (MongoDB) ```typescript const updated = await db.orm.posts - .where({ _id: postId }) + .where({ id: postId }) .update((p) => [p.title.set('Updated title'), p.content.set('Rewritten')]); ``` @@ -1359,7 +1402,7 @@ const deleted = await db.orm.public.Tag.where({ id: created.id }).delete(); ``` ```typescript tab="MongoDB" -const deleted = await db.orm.users.where({ _id: userId }).delete(); +const deleted = await db.orm.users.where({ id: userId }).delete(); ``` For Prisma ORM 7 users, `delete()` moves the filter into `where()`: @@ -1448,7 +1491,7 @@ Insert a row if none matches, otherwise update the existing row. - 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`. +- On MongoDB, the `id` on the returned row is a hex string, not the driver's `ObjectId`. #### Options @@ -1506,7 +1549,7 @@ const user = await db.orm.users.where({ email: 'newperson@example.com' }).upsert ```typescript const post = await db.orm.posts .variant('tutorial') - .where({ _id: tutorialId }) + .where({ id: tutorialId }) .upsert({ create: { title: 'Should not be used', @@ -1547,7 +1590,7 @@ The examples below use a separate `Customer` / `Order` schema, where `Order.amou ```prisma model Customer { - id Uuid @id @default(uuid()) + id Uuid @id @onCreate(uuid()) name String segment String @@ -1557,7 +1600,7 @@ model Customer { } model Order { - id Uuid @id @default(uuid()) + id Uuid @id @onCreate(uuid()) customerId Uuid channel String amount Int @@ -1843,7 +1886,7 @@ PostgreSQL full-text search on a text field. ```prisma title="src/prisma/contract.prisma (excerpt)" model Post { - id Uuid @id @default(uuid()) + id Uuid @id @onCreate(uuid()) title String @@fullTextIndex([title], name: "post_title_search", language: "german") @@ -1992,6 +2035,7 @@ A plain object of equality matches, ANDed together. - Available for PostgreSQL and MongoDB. - 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 MongoDB, a list field takes a whole list, which matches a stored list that is equal to it, an empty list `[]`, or `null`. `null` matches a document whose list is null or missing, not a list that holds a `null` item. - 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 @@ -2101,7 +2145,7 @@ On MongoDB, `update()`, `updateAll()`, `updateAndCount()`, and `upsert()` take a - 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()`, and 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, and `updateAll()` returns an [`AsyncIterableResult`](#asynciterableresult) of the changed documents. [`updateAndCount()`](#updateandcount) exists on both databases, and it returns the number of changed documents. +All four methods require `.where()`, and 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')`. An `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, and `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) @@ -2124,10 +2168,10 @@ Return as many operations as you want, and they are applied together, in one wri ```typescript const updated = await db.orm.users - .where({ _id: '6650f1c2a1b2c3d4e5f60002' }) + .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')]); +const moved = await db.orm.users.where({ id: '6650f1c2a1b2c3d4e5f60002' }).update((u) => [u('address.city').set('San Francisco')]); ``` ##### `inc()` and `mul()` (MongoDB) @@ -2137,13 +2181,13 @@ const moved = await db.orm.users.where({ _id: '6650f1c2a1b2c3d4e5f60002' }).upda ```typescript const incremented = await db.orm.posts .variant('tutorial') - .where({ _id: '6650f1c2a1b2c3d4e5f60003' }) + .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: '6650f1c2a1b2c3d4e5f60003' }) + .where({ id: '6650f1c2a1b2c3d4e5f60003' }) // @ts-expect-error duration is a Tutorial field, not a Post field .update((u) => [u.duration.mul(3)]); ``` @@ -2151,8 +2195,8 @@ const multiplied = await db.orm.posts ##### `unset()` (MongoDB) ```typescript -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()]); +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) @@ -2171,10 +2215,10 @@ The update builder implements eight operations, and only these eight: `set`, `un - 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)]); +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 diff --git a/apps/docs/content/docs/orm/reference/pipeline-builder.mdx b/apps/docs/content/docs/orm/reference/pipeline-builder.mdx index 18276d09938..646aca801b3 100644 --- a/apps/docs/content/docs/orm/reference/pipeline-builder.mdx +++ b/apps/docs/content/docs/orm/reference/pipeline-builder.mdx @@ -94,7 +94,7 @@ Enter the pipeline builder on a collection. - `from()` takes the collection name, the same name you use on `db.orm` (`'posts'`, `'users'`), not the model name `Post`. The collection name is the model's `@@map` value, or the model name exactly as written when there is no `@@map`. - Passing an unknown collection name throws right away, with an error whose `code` is `ORM.MODEL_UNKNOWN` and whose message is `Unknown root: "". Valid roots: ...` (the message says root; it means the collection name). - `from()` returns a builder you chain stages onto. Call `insertOne()` and `insertMany()` on `from()` directly, before any stage. -- You can build the same pipelines without a client. `import { mongoQuery } from '@prisma/orm-mongo/query-builder'`, then `mongoQuery({ contractJson }).from('posts')`. Import `contractJson` the same way the example below does. `mongoQuery` only builds queries. It never runs one, so you still need a client's `runtime` to get results. Use it in tests, or to build a query once and run it through any client you like. +- You can build the same pipelines without a client. `import { mongoQuery } from '@prisma/orm-mongo/query-builder'`, then `mongoQuery({ contract }).from('posts')`, where `contract` is the value the example below builds with `contractFromJson`. `mongoQuery` only builds queries. It never runs one, so you still need a client's `runtime` to get results. Use it in tests, or to build a query once and run it through any client you like. #### Options @@ -114,10 +114,12 @@ Enter the pipeline builder on a collection. ```typescript import mongo from '@prisma/orm-mongo/runtime'; +import { contractFromJson } from '@prisma/orm-mongo/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -const db = mongo({ contractJson, url: process.env.MONGODB_URL, dbName: 'app' }); +const contract = contractFromJson(contractJson); +const db = mongo({ contract, url: process.env.MONGODB_URL!, dbName: 'app' }); const runtime = await db.runtime(); const plan = db.query.from('posts').build(); @@ -133,6 +135,7 @@ const posts = await runtime.query(plan); - On MongoDB, `db.runtime()` returns a promise, so you must await it: `const runtime = await db.runtime()`. Hold it in a variable and reuse it. - `await` that call for an array of documents, or write `for await (const doc of runtime.query(plan)) { ... }` to take one document at a time. - There is no `db.execute` on the MongoDB client. +- The pipeline builder works on documents as they are stored, so it names fields by their stored names. A field declared `id ObjectId @id @map("_id")` is `f._id` in a stage and `_id` in the rows, while the [ORM client](/orm/reference/orm-client) calls the same field `id`. To pass a pipeline row to code that expects an ORM row, rename the field: `({ _id, ...rest }) => ({ ...rest, id: _id })`. - **Read results are decoded.** If every stage in your pipeline is one of `match()`, `sort()`, `limit()`, `skip()`, `sample()`, `project()`, `addFields()`, and `vectorSearch()`, `_id` comes back as a hex string. If any stage is not in that list, including `lookup()` and `group()`, every document comes back raw, with each value as MongoDB stores it. An `_id` that MongoDB generated is then an `ObjectId` object from the `mongodb` package, the MongoDB driver Prisma ORM uses. Compare those ids as strings, as [`lookup()`](#lookup) shows. A `DateTime` field comes back as a JavaScript `Date` either way. #### Examples @@ -220,7 +223,7 @@ const [post] = await runtime.query(plan); To fetch one document by its id, the [ORM client](/orm/reference/orm-client) is shorter, because it converts the hex string for you: ```typescript -const post = await db.orm.posts.where({ _id: postId }).first(); +const post = await db.orm.posts.where({ id: postId }).first(); ``` ::: diff --git a/apps/docs/content/docs/orm/reference/raw-queries.mdx b/apps/docs/content/docs/orm/reference/raw-queries.mdx index f9b481fb210..7e429bb5be0 100644 --- a/apps/docs/content/docs/orm/reference/raw-queries.mdx +++ b/apps/docs/content/docs/orm/reference/raw-queries.mdx @@ -16,20 +16,20 @@ Raw SQL comes in two forms. A **raw fragment** is a piece of SQL you put inside a builder query. Write it as a tagged template with `fns.raw` and pass it to a `select()`, `where()`, `orderBy()`, or `update()` call. The rest of the query stays typed. -A **whole raw statement** is a complete SQL statement written with `db.raw.sql`. If it returns rows, end it with `.returnsRow(spec).build()`, where the spec is a column list [defined below](#the-client-level-dbrawsql-tag), and pass the result to `runtime.query(...)`. If it does not, end it with `.affectedCount().build()` and pass the result to `runtime.execute(...)`. +A **whole raw statement** is a complete SQL statement written with `db.raw.sql`. If it returns rows, end it with `.returnsRow(spec).build()`, where the spec is a column list [defined below](#the-client-level-dbrawsql-tag), and pass the result to `db.query(...)`. If it does not, end it with `.affectedCount().build()` and pass the result to `db.execute(...)`. To see the SQL a built query would send, without running it, pass it to [`db.toSql(...)`](/orm/reference/transactions-and-runtime#dbtosqlplan). :::warning[Only `.returnsRow()` converts values for you] A type id such as `pg/text@1` tells Prisma ORM what JavaScript type a column should become, and the property is named `codecId`. `.returnsRow(spec)` names a type id for every column of a whole `db.raw.sql` statement, so you get real JavaScript values back. A fragment's `.returns(...)` does not convert anything: it only tells TypeScript what type to expect, so the value is whatever the `pg` package, the PostgreSQL driver underneath, gives you. For a `text` or an `integer` column that is already a JavaScript string or number, while a `numeric` column arrives as a string. A date column gets no conversion by Prisma ORM, so you get the `pg` package's own value for that column. ::: -The examples in this section run against the `user` / `post` schema from the SQL query builder page. See its [example schema](/orm/reference/sql-query-builder#example-schema) for the full model definitions. As on that page, create the client with `postgres(...)` and reach tables through `db.sql.public`, which is keyed by table name (the model name exactly as written, unless the model sets the `@@map` attribute, which renames a table). `db.runtime()` gives you the connection that runs a built query. +The examples in this section run against the `user` / `post` schema from the SQL query builder page. See its [example schema](/orm/reference/sql-query-builder#example-schema) for the full model definitions. As on that page, create the client with `postgres(...)` and reach tables through `db.sql.public`, which is keyed by table name (the model name exactly as written, unless the model sets the `@@map` attribute, which renames a table). `db.query(...)` and `db.execute(...)` run a built query. ```ts import postgres from '@prisma/orm-postgres/runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -const db = postgres({ contractJson, url: process.env.DATABASE_URL }); -const runtime = db.runtime(); +const db = postgres({ contract: contractFromJson(contractJson), url: process.env.DATABASE_URL! }); const aliceId = '00000000-0000-4000-8000-000000000001'; ``` @@ -43,7 +43,7 @@ const plan = db.sql.public.user .select('upperEmail', (f, fns) => fns.raw`UPPER(${f.email})`.returns('pg/text@1')) .where((f, fns) => fns.eq(f.id, aliceId)) .build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); // rows === [{ id: aliceId, upperEmail: 'ALICE@EXAMPLE.COM' }] ``` @@ -58,7 +58,7 @@ const plan = db.sql.public.user .select('id', 'email') .where((f, fns) => fns.raw`LENGTH(${f.email}) > 15`.returns('pg/bool@1')) .build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); // only users whose email is longer than 15 characters ``` @@ -71,7 +71,7 @@ const plan = db.sql.public.user .select('id', 'kind') .select('kindLabel', (f, fns) => fns.raw`CASE WHEN ${fns.eq(f.kind, 'admin')} THEN 'admin' ELSE 'regular user' END`.returns('pg/text@1')) .build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); // each row's kindLabel is 'admin' or 'regular user' ``` @@ -101,7 +101,7 @@ const plan = db.sql.public.user .select('id', 'email') .where((f, fns) => fns.raw`${f.createdAt} > ${cutoff}`.returns('pg/bool@1')) .build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); // users created after the start of 2024 ``` @@ -114,7 +114,7 @@ const plan = db.sql.public.user .select('id') .select('adminName', (f, fns) => fns.raw`CASE WHEN ${fns.eq(f.kind, 'admin')} THEN ${f.displayName} END`.returns({ codecId: 'pg/text@1', nullable: true })) .build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); // adminName is the displayName for admins, null for everyone else ``` @@ -130,7 +130,7 @@ const plan = db.sql.public.user .select((f) => ({ id: f.id, serverNow })) .where((f, fns) => fns.eq(f.id, aliceId)) .build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); ``` Use `.returnsRow(spec)` to run a whole statement and get converted rows back. The spec names the columns of every returned row, and each entry is either a column from your contract or a type id. A column such as `db.sql.public.user.columns.id` brings its own conversion, nullability, and TypeScript type. Write a type id for a column your contract has no match for, and the object form `{ codecId: 'pg/text@1', nullable: true }` when that column can be `NULL`, as a `LEFT JOIN` column can. If a returned row has no column your spec names, the query throws an error whose `code` is `RUNTIME.RAW_ROW_COLUMN_MISSING`. A failing statement throws a `SqlQueryError` whose `sqlState` is the PostgreSQL error code for the failure, such as `23505` for a unique-constraint violation. @@ -150,11 +150,11 @@ const plan = db.raw.sql` .returnsRow({ id: user.columns.id, email: user.columns.email, postCount: 'pg/int8@1' }) .build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); // row.email is a string; row.postCount is a bigint ``` -Use `.affectedCount()`, which takes no spec, for a statement that returns no rows. Pass the built query to `runtime.execute(...)`, which resolves to `{ affectedRows }`, the number of rows the statement changed. You can also run a built raw query inside a transaction: [`db.transaction()`](/orm/reference/transactions-and-runtime#dbtransactioncallback) gives you a `tx`, and `tx.query(...)` and `tx.execute(...)` take the same built queries as `runtime.query(...)` and `runtime.execute(...)` do. +Use `.affectedCount()`, which takes no spec, for a statement that returns no rows. Pass the built query to `db.execute(...)`, which resolves to `{ affectedRows }`, the number of rows the statement changed. You can also run a built raw query inside a transaction: [`db.transaction()`](/orm/reference/transactions-and-runtime#dbtransactioncallback) gives you a `tx`, and `tx.query(...)` and `tx.execute(...)` take the same built queries as `db.query(...)` and `db.execute(...)` do. ```ts const plan = db.raw.sql` @@ -163,7 +163,7 @@ const plan = db.raw.sql` WHERE "userId" IN (SELECT id FROM "user" WHERE kind = ${'admin'}) `.affectedCount().build(); -const stats = await runtime.execute(plan); +const stats = await db.execute(plan); // stats.affectedRows ``` @@ -204,20 +204,21 @@ To bind any value whose type is not in the list, wrap it in [`param(value, { cod `db.raw.collection(collectionName)` returns an object with nine methods for running MongoDB commands directly against a collection. The collection name is the one your contract declares (`'users'`, `'posts'`), the same name the ORM client uses (`db.orm.users`). MongoDB has no schemas, so there is no `public` segment here and none on `db.orm`. There is no raw `find()`, so use `aggregate()` with a `$match` stage. MongoDB has no transactions in Prisma ORM, so a raw command cannot run inside one. See [Transactions (MongoDB)](/orm/reference/transactions-and-runtime#transactions-mongodb). -Each of these nine methods builds a command: call `.build()` on it, then run every one of them with `runtime.query(...)`, including the writes. The raw collection methods all run through `query()`. A write returns a one-element array, so destructure it with `const [result] = ...`. +Each of these nine methods builds a command: call `.build()` on it, then run every one of them with `db.query(...)`, including the writes. The raw collection methods all run through `query()`. A write returns a one-element array, so destructure it with `const [result] = ...`. :::warning[MongoDB raw results are native BSON values] -Nothing on the MongoDB side of this page is converted for you. An `_id` comes back as it is stored: one that MongoDB generated is an `ObjectId` instance from the `mongodb` package, the MongoDB driver underneath, not a hex string. Compare one with `String(row._id) === aliceId`. Raw filters take native BSON values too, so build ids with that package's `ObjectId` class. The write methods give you the counts and ids under the `mongodb` package's own key names, `{ insertedId }`, `{ matchedCount, modifiedCount, upsertedCount, upsertedId }`, and `{ deletedCount }`, and you read those keys directly. +Nothing on the MongoDB side of this page is converted for you. Raw commands name fields as they are stored in the database, so a field the ORM calls `id` because it is declared `id ObjectId @id @map("_id")` is `_id` here, in filters and in the rows you get back. An `_id` comes back as it is stored: one that MongoDB generated is an `ObjectId` instance from the `mongodb` package, the MongoDB driver underneath, not a hex string. Compare one with `String(row._id) === aliceId`. Raw filters take native BSON values too, so build ids with that package's `ObjectId` class. The write methods give you the counts and ids under the `mongodb` package's own key names, `{ insertedId }`, `{ matchedCount, modifiedCount, upsertedCount, upsertedId }`, and `{ deletedCount }`, and you read those keys directly. ::: The examples in this section run against the `users` / `posts` schema from the pipeline builder page. See its [example schema](/orm/reference/pipeline-builder#example-schema) for the full model definitions. Create the client with `mongo(...)`, get the connection that runs a built query from `await db.runtime()`, and declare the two example ids once: ```ts import mongo from '@prisma/orm-mongo/runtime'; +import { contractFromJson } from '@prisma/orm-mongo/static'; import { ObjectId } from 'mongodb'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -const db = mongo({ contractJson, url: process.env.MONGODB_URL, dbName: 'app' }); +const db = mongo({ contract: contractFromJson(contractJson), url: process.env.MONGODB_URL!, dbName: 'app' }); const runtime = await db.runtime(); const aliceId = '6539a1f2c3d4e5f60718293a'; const bobId = '6539a1f2c3d4e5f60718293b'; 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 2909e8c0f4a..5cbe4ecba0d 100644 --- a/apps/docs/content/docs/orm/reference/sql-query-builder.mdx +++ b/apps/docs/content/docs/orm/reference/sql-query-builder.mdx @@ -48,7 +48,7 @@ enum Priority { } model User { - id Uuid @id @default(uuid()) + id Uuid @id @onCreate(uuid()) email String displayName String createdAt DateTime @default(now()) @@ -61,7 +61,7 @@ model User { } model Post { - id Uuid @id @default(uuid()) + id Uuid @id @onCreate(uuid()) title String userId Uuid priority Priority @default(Low) @@ -75,7 +75,7 @@ model Post { } model Tag { - id Uuid @id @default(uuid()) + id Uuid @id @onCreate(uuid()) label String @unique posts Post[] @@ -95,7 +95,7 @@ model PostTag { } model Task { - id Uuid @id @default(uuid()) + id Uuid @id @onCreate(uuid()) title String description String? status String @default("open") @@ -136,20 +136,19 @@ Table accessors are keyed by table name, not model name. A table name is the mod ### The `db.sql` builder [#the-dbsql-facet] -`db.runtime()` returns the connection that runs a built query. On PostgreSQL it is synchronous, so no `await` is needed. Hold it in a variable and reuse it. When your app shuts down, close the client with [`db.close()`](/orm/reference/transactions-and-runtime#close). - -A `select()`, `insert()`, `update()`, or `delete()` call starts a query. [`build()`](#build) turns the chain into a query object, which the examples name `plan`. Nothing touches the database until you pass that object to `runtime.query(...)`. You can run the same built query more than once. +A `select()`, `insert()`, `update()`, or `delete()` call starts a query. [`build()`](#build) turns the chain into a query object, which the examples name `plan`. Nothing touches the database until you pass that object to `db.query(...)` or `db.execute(...)`. You can run the same built query more than once. To see the SQL without running it, pass it to [`db.toSql(...)`](#executing-a-plan). When your app shuts down, close the client with [`db.close()`](/orm/reference/transactions-and-runtime#close). ```ts import postgres from '@prisma/orm-postgres/runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -const db = postgres({ contractJson, url: process.env.DATABASE_URL }); -const runtime = db.runtime(); +const contract = contractFromJson(contractJson); +const db = postgres({ contract, url: process.env.DATABASE_URL! }); const plan = db.sql.public.user.select('id', 'email').build(); -const users = await runtime.query(plan); +const users = await db.query(plan); ``` The example schema's `Embedding1536` column is a pgvector type, so a client for that schema also needs pgvector in `extensions`, as [Built-in functions](#built-in-functions) shows. @@ -171,7 +170,7 @@ Call `.as(alias)` on a `SELECT` chain before `build()` to use that query as a su ## SELECT queries -These methods build and refine a `SELECT`. They chain, and you resolve the query with [`build()`](#build) followed by `runtime.query(...)`. Chain the methods in any order, with two exceptions: a join must come before any call that names the joined table's columns, and the columns you pass to `distinctOn()` must be the first sort keys in `orderBy()`. `SelectQuery` in the tables below is the type they return. +These methods build and refine a `SELECT`. They chain, and you resolve the query with [`build()`](#build) followed by `db.query(...)`. Chain the methods in any order, with two exceptions: a join must come before any call that names the joined table's columns, and the columns you pass to `distinctOn()` must be the first sort keys in `orderBy()`. `SelectQuery` in the tables below is the type they return. The set of column names a query can use is its scope: it starts as the columns of the table you began with, and every join adds the joined table's columns to it. @@ -217,7 +216,7 @@ const newestPostId = '00000000-0000-4000-8000-000000000012'; ```ts const plan = db.sql.public.user.select('id', 'email').build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); // rows[0] is { id, email }, with no displayName ``` @@ -229,7 +228,7 @@ const plan = db.sql.public.user .select('emailLength', (f, fns) => fns.raw`LENGTH(${f.email})`.returns('pg/int4@1')) .where((f, fns) => fns.eq(f.id, aliceId)) .build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); ``` ##### Project multiple computed columns at once @@ -243,7 +242,7 @@ const plan = db.sql.public.user })) .where((f, fns) => fns.eq(f.id, aliceId)) .build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); // rows === [{ id: aliceId, upperEmail: 'ALICE@EXAMPLE.COM', emailLength: 17 }] ``` @@ -277,7 +276,7 @@ const plan = db.sql.public.post ), ) .build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); ``` ### `innerJoin()` @@ -307,7 +306,7 @@ const plan = db.sql.public.post .select((f) => ({ postId: f.post.id, authorEmail: f.user.email })) .where((f, fns) => fns.eq(f.post.id, helloWorldId)) .build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); // rows === [{ postId: helloWorldId, authorEmail: 'alice@example.com' }] ``` @@ -325,7 +324,7 @@ const plan = db.sql.public.post .select((f) => ({ postId: f.post.id, tagId: f.post_tag.tagId })) .where((f, fns) => fns.eq(f.post.id, untaggedPostId)) .build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); // rows === [{ postId: untaggedPostId, tagId: null }] ``` @@ -371,7 +370,7 @@ const plan = db.sql.public.user .select((f) => ({ userId: f.user.id, latestPostId: f.latestPost.id })) .where((f, fns) => fns.eq(f.user.id, aliceId)) .build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); // rows === [{ userId: aliceId, latestPostId: newestPostId }] ``` @@ -401,7 +400,7 @@ Without `options.nulls`, PostgreSQL puts nulls last for `'asc'` and first for `' ```ts const plan = db.sql.public.user.select('id', 'email').orderBy('email', { direction: 'asc' }).build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); ``` ##### Put nulls last @@ -411,7 +410,7 @@ const plan = db.sql.public.task .select('id', 'description') .orderBy('description', { direction: 'desc', nulls: 'last' }) .build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); ``` ```sql no-copy @@ -425,7 +424,7 @@ const plan = db.sql.public.user .select('id', 'email') .orderBy((f, fns) => fns.raw`LENGTH(${f.email})`.returns('pg/int4@1'), { direction: 'asc' }) .build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); ``` ### `distinct()` @@ -444,7 +443,7 @@ De-duplicate identical projected rows (`SELECT DISTINCT`). ```ts const plan = db.sql.public.post.select('priority').distinct().build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); // distinct priorities: ['high', 'low', 'urgent'] ``` @@ -480,7 +479,7 @@ const plan = db.sql.public.post .orderBy('createdAt', { direction: 'asc' }) .distinctOn('userId') .build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); // one row per user, each the earliest post by createdAt ``` @@ -512,7 +511,7 @@ const plan = db.sql.public.user .limit(1) .offset(1) .build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); ``` ### Row locking: `forUpdate()`, `forNoKeyUpdate()`, `forShare()`, `forKeyShare()` [#row-locking] @@ -593,7 +592,7 @@ const plan = db.sql.public.user .innerJoin(highPriorityPosts, (f, fns) => fns.eq(f.user.id, f.hp.userId)) .select((f) => ({ userId: f.user.id, postId: f.hp.id })) .build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); ``` ## Grouped queries @@ -604,7 +603,7 @@ The examples below use an extra table, `order`, whose `amount` column is an inte ```prisma model Order { - id Uuid @id @default(uuid()) + id Uuid @id @onCreate(uuid()) customerId Uuid amount Int placedAt DateTime @default(now()) @@ -634,17 +633,15 @@ Group rows by one or more columns or by a computed expression, producing one row ##### Group by a column with a count -The examples below reuse the `runtime` created here, and name each built query `plan`, the object `build()` returns. +The examples below name each built query `plan`, the object `build()` returns. ```ts -const runtime = db.runtime(); - const plan = db.sql.public.order .select('customerId') .select('orderCount', (f, fns) => fns.count(f.id)) .groupBy('customerId') .build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); // rows === [{ customerId: '', orderCount: 5 }] ``` @@ -655,7 +652,7 @@ const rows = await runtime.query(plan); ::: ```ts -try { await runtime.query(plan); } catch (error) { +try { await db.query(plan); } catch (error) { if (error instanceof Error && 'code' in error && error.code === 'RUNTIME.DECODE_FAILED') { /* the count is too big for a number */ } } ``` @@ -668,7 +665,7 @@ const plan = db.sql.public.order .select('orderCount', (f, fns) => fns.count(f.id)) .groupBy('yearPlaced') .build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); // rows === [{ yearPlaced: '2024', orderCount: 10 }]. PostgreSQL computes EXTRACT as numeric, which arrives as the string '2024'. Write Number(row.yearPlaced) for 2024. ``` @@ -712,7 +709,7 @@ const plan = db.sql.public.order .groupBy('customerId') .having((f, fns) => fns.gt(fns.sum(f.amount), 1000)) .build(); -const rows = await runtime.query(plan); // only the customer whose orders sum over 1000, with totalAmount 1500 +const rows = await db.query(plan); // only the customer whose orders sum over 1000, with totalAmount 1500 ``` ##### Compare with `count()`, `avg()`, `min()`, `max()` @@ -727,7 +724,7 @@ const plan = db.sql.public.order .groupBy('customerId') .having((f, fns) => fns.and(fns.gt(fns.count(), 1), fns.gt(fns.avg(f.amount), 100))) .build(); -const rows = await runtime.query(plan); // avgAmount is 300, exactAvg is the exact string '300.0000000000000000', minAmount is 100, maxAmount is 500 +const rows = await db.query(plan); // avgAmount is 300, exactAvg is the exact string '300.0000000000000000', minAmount is 100, maxAmount is 500 ``` ### Ordering and limiting a grouped query @@ -746,7 +743,7 @@ const plan = db.sql.public.order .orderBy('totalAmount', { direction: 'desc' }) .limit(1) .build(); -const rows = await runtime.query(plan); // the single highest-spending customer +const rows = await db.query(plan); // the single highest-spending customer ``` ## Mutations @@ -755,7 +752,7 @@ const rows = await runtime.query(plan); // the single highest-spending customer `where()` is optional on `update()` and `delete()`: leave it out and every row in the table is changed, with no warning. ::: -Mutations start from a table with `insert()`, `update()`, or `delete()`, and end with [`build()`](#build). Add [`returning()`](#returning) to get the affected rows back. Run a mutation with no `returning()` using `runtime.execute(plan)`, which resolves to `{ affectedRows }`. With `returning()`, run it with `runtime.query(plan)`. The examples below go back to this page's example models, so they use `db.sql.public.tag` and `db.sql.public.user` again. +Mutations start from a table with `insert()`, `update()`, or `delete()`, and end with [`build()`](#build). Add [`returning()`](#returning) to get the affected rows back. Run a mutation with no `returning()` using `db.execute(plan)`, which resolves to `{ affectedRows }`. With `returning()`, run it with `db.query(plan)`. The examples below go back to this page's example models, so they use `db.sql.public.tag` and `db.sql.public.user` again. ### `insert()` @@ -764,7 +761,7 @@ Insert one or more rows in a single statement. #### Remarks - `insert()` always takes an **array**. A single-row insert is a one-element array. There is no separate single-row overload. An empty array throws an error whose `code` is `ORM.MUTATION_DATA_MISSING`. -- Leave a column out and the `@default(...)` in `contract.prisma` is applied. Prisma ORM generates `@default(uuid())` and `@default(now())` values when you call `build()`, not when the query runs. The built query then holds fixed values, so running it twice inserts the same id twice, which fails on a unique column. Call `.build()` again for each insert. +- Leave a column out and the database fills it from its `@default(...)` in `contract.prisma`, or Prisma ORM generates it from its `@onCreate(...)`. Prisma ORM generates `@onCreate(uuid())` values when you call `build()`, not when the query runs. The built query then holds fixed values, so running it twice inserts the same id twice, which fails on a unique column. Call `.build()` again for each insert. - There is no `ON CONFLICT` and no upsert here. For "insert or update", use the ORM client's [`upsert()`](/orm/reference/orm-client#upsert) or write the statement with [`db.raw.sql`](/orm/reference/raw-queries). #### Options @@ -787,14 +784,14 @@ PostgreSQL still rejects a missing required column that has no default. Never ca ```ts const plan = db.sql.public.tag.insert([{ label: 'single-row-tag' }]).build(); -await runtime.execute(plan); // execute runs a write that returns no rows. id is filled in from @default(uuid()) +await db.execute(plan); // execute runs a write that returns no rows. id is filled in from @onCreate(uuid()) ``` ##### Insert multiple rows in one statement ```ts const plan = db.sql.public.tag.insert([{ label: 'multi-row-a' }, { label: 'multi-row-b' }]).build(); -await runtime.execute(plan); +await db.execute(plan); ``` ### `returning()` @@ -824,7 +821,7 @@ Return columns from the rows affected by an `insert()`, `update()`, or `delete() ```ts const plan = db.sql.public.tag.insert([{ label: 'returned-tag' }]).returning('id', 'label').build(); -const rows = await runtime.query(plan); // rows === [{ id: '', label: 'returned-tag' }] +const rows = await db.query(plan); // rows === [{ id: '', label: 'returned-tag' }] ``` ### `update()` @@ -858,7 +855,7 @@ const plan = db.sql.public.user .where((f, fns) => fns.eq(f.email, 'bob@example.com')) .returning('id', 'displayName') .build(); -const rows = await runtime.query(plan); // one row, with displayName now 'Bobby' +const rows = await db.query(plan); // one row, with displayName now 'Bobby' ``` ##### Derive a value from an existing column @@ -872,7 +869,7 @@ const plan = db.sql.public.user .where((f, fns) => fns.eq(f.email, 'carol@example.com')) .returning('id', 'displayName') .build(); -const rows = await runtime.query(plan); // one row, with displayName now 'CAROL' and a fixed new email +const rows = await db.query(plan); // one row, with displayName now 'CAROL' and a fixed new email ``` ### `delete()` @@ -895,7 +892,7 @@ const plan = db.sql.public.tag .where((f, fns) => fns.eq(f.label, 'to-delete')) .returning('id', 'label') .build(); -const rows = await runtime.query(plan); // one row, the tag that was deleted +const rows = await db.query(plan); // one row, the tag that was deleted ``` ### `param()` @@ -933,7 +930,7 @@ const plan = db.sql.public.user .select('id', 'email') .where((f, fns) => fns.raw`${f.id} = ${targetId}`.returns('pg/bool@1')) .build(); -const rows = await runtime.query(plan); +const rows = await db.query(plan); ``` ## Expressions and functions @@ -1018,7 +1015,7 @@ const plan = db.sql.public.post .limit(10) .build(); -const results = await runtime.query(plan); +const results = await db.query(plan); // { id: string; title: string; rank: number; headline: string }[] ``` @@ -1029,9 +1026,9 @@ pgvector is the PostgreSQL extension for vector columns. If your contract uses a ```ts title="src/prisma/db.ts" import pgvector from '@prisma/orm-extension-pgvector/runtime'; -const db = postgres({ - contractJson, - url: process.env.DATABASE_URL, +const db = postgres({ + contract, + url: process.env.DATABASE_URL!, extensions: [pgvector], }); ``` @@ -1079,18 +1076,21 @@ Compile a query so you can run it. | Return type | Example | Description | |---|---|---| -| Built query | `db.sql.public.user.select('id').build()` | A query you run with `runtime.query(...)`. You can get the TypeScript type of one row with [`ResultType`](#resulttype). | +| Built query | `db.sql.public.user.select('id').build()` | A query you run with `db.query(...)`. You can get the TypeScript type of one row with [`ResultType`](#resulttype). | ### Running a built query [#executing-a-plan] -Run a built query with `runtime`, which is `db.runtime()`. +Run a built query on the client. A transaction's `tx` and the runtime from `db.runtime()` have the same `query` and `execute` calls. | Call | What you get | |---|---| -| `runtime.query(plan)` | Rows. `await` it for an array of rows (`Row[]`), or `for await` over it to take one row at a time. | -| `runtime.execute(plan)` | `{ affectedRows }`. | +| `db.query(plan)` | Rows. `await` it for an array of rows (`Row[]`), or `for await` over it to take one row at a time. | +| `db.execute(plan)` | `{ affectedRows }`. | +| `db.toSql(plan)` | `{ sql, params }`: the SQL the query would send and its values. It runs nothing and needs no database. | | Which one | `query` for anything that returns rows, which means a `SELECT` or a write with `returning()`. `execute` for everything else. | +See [`db.query(plan)` and `db.execute(plan)`](/orm/reference/transactions-and-runtime#run-a-plan) and [`db.toSql(plan)`](/orm/reference/transactions-and-runtime#dbtosqlplan). + ### `ResultType` Recover a built query's row type at the type level. @@ -1098,7 +1098,7 @@ Recover a built query's row type at the type level. #### Remarks - Import `ResultType` from `@prisma/orm-postgres/components/runtime`. It works with the ORM client as well as the SQL builder. -- `ResultType` is **one row**, not an array. `await runtime.query(plan)` resolves to `Row[]`, but `ResultType` is `Row`. +- `ResultType` is **one row**, not an array. `await db.query(plan)` resolves to `Row[]`, but `ResultType` is `Row`. - It also works on ORM queries: `ResultType`. `db.orm` is keyed by model name, so the model is `User` there and the table is `user` under `db.sql`. See [Model and result types](/orm/reference/orm-client#model-and-result-types). #### Examples @@ -1111,11 +1111,11 @@ import type { ResultType } from '@prisma/orm-postgres/components/runtime'; const plan = db.sql.public.user.select('id', 'email').build(); type Row = ResultType; // { id: string; email: string } -const rows = await runtime.query(plan); // Row[] +const rows = await db.query(plan); // Row[] ``` ### Streaming vs. collecting -`await runtime.query(plan)` collects every row into an array, which is the common case and what every example on this page uses. You can also loop the same result with `for await` to handle one row at a time. On PostgreSQL every row is loaded before the loop starts, so `for await` saves nothing there. Use `await`. The exception is a client created with the [`cursor` option](/orm/reference/transactions-and-runtime#postgresoptions), which reads rows through a server-side cursor in batches. +`await db.query(plan)` collects every row into an array, which is the common case and what every example on this page uses. You can also loop the same result with `for await` to handle one row at a time. On PostgreSQL every row is loaded before the loop starts, so `for await` saves nothing there. Use `await`. The exception is a client created with the [`cursor` option](/orm/reference/transactions-and-runtime#postgresoptions), which reads rows through a server-side cursor in batches. -Keep the result in a variable, as in `const result = runtime.query(plan)`, and you can `await result` as many times as you like. You cannot mix `await` and `for await` on the same result, and you cannot `for await` it twice. Either one throws an error whose `code` is `RUNTIME.ITERATOR_CONSUMED`. See [`AsyncIterableResult`](/orm/reference/orm-client#asynciterableresult). +Keep the result in a variable, as in `const result = db.query(plan)`, and you can `await result` as many times as you like. You cannot mix `await` and `for await` on the same result, and you cannot `for await` it twice. Either one throws an error whose `code` is `RUNTIME.ITERATOR_CONSUMED`. See [`AsyncIterableResult`](/orm/reference/orm-client#asynciterableresult). diff --git a/apps/docs/content/docs/orm/reference/transactions-and-runtime.mdx b/apps/docs/content/docs/orm/reference/transactions-and-runtime.mdx index 6862accbe6c..7396e0059b2 100644 --- a/apps/docs/content/docs/orm/reference/transactions-and-runtime.mdx +++ b/apps/docs/content/docs/orm/reference/transactions-and-runtime.mdx @@ -6,7 +6,7 @@ metaTitle: Prisma ORM transactions and runtime reference metaDescription: 'Reference for the Prisma ORM client lifecycle, transactions, prepared statements, and execution options.' --- -Prisma ORM 8 renames `schema.prisma` to `contract.prisma`. Run `npx prisma contract emit`, which writes two files next to it: `contract.json`, which your app imports, and `contract.d.ts`, which gives you the TypeScript types. A new project keeps all three files in `src/prisma/`. Every client you create needs `contract.json`. The query you came for, `prisma.user.findMany({ where })`, is now `db.orm.public.User.where(...).all()`. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) lists the packages to install, the commands to run, and the rest of the renamed calls. +Prisma ORM 8 renames `schema.prisma` to `contract.prisma`. Run `npx prisma contract emit`, which writes two files next to it: `contract.json`, which your app imports, and `contract.d.ts`, which gives you the TypeScript types. A new project keeps all three files in `src/prisma/`. Every client you create needs the contract from `contract.json`, turned into a value with `contractFromJson`. The query you came for, `prisma.user.findMany({ where })`, is now `db.orm.public.User.where(...).all()`. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) lists the packages to install, the commands to run, and the rest of the renamed calls. `db` is the client that `postgres(...)` or `mongo(...)` returns. `db.runtime()` gives you the object that actually runs queries, and that object is called the runtime. @@ -24,8 +24,8 @@ Create a PostgreSQL client. #### Remarks -- Import `contract.json` and pass it as `contractJson`, as every example below does. Pass `contract` only if you already hold a contract object; most code passes `contractJson`. Supply exactly one. -- The `` type argument is what types `db.orm` and `db.sql`. `contract.d.ts` exports it under the name `Contract`. Pass it yourself whenever you pass `contractJson`, because TypeScript cannot read a type out of a JSON file. It is inferred only when you use the `contract` option. +- Pass the contract as a value with the `contract` option. Turn `contract.json` into that value once with `contractFromJson(contractJson)` from `@prisma/orm-postgres/static`, as every example below does. `Contract` is the type that `contract.d.ts` exports; you name it here because TypeScript cannot read a type out of a JSON file. The client takes its types for `db.orm` and `db.sql` from the value, so `postgres(...)` takes no type argument. A contract written in TypeScript with `defineContract` is already a value: pass it as `contract` directly. +- `contractFromJson` checks the contract once. Build every client from the same value, and export it from the module that creates the client so that tests and other modules can import it. - Tell the client which database to use in one of three ways: `url`, `pg`, or `binding`. A `binding` wraps any of the three choices in one object, with a `kind` field saying which it is: `{ kind: 'url', url }`, `{ kind: 'pgPool', pool }`, or `{ kind: 'pgClient', client }`. Use it when your code picks the source at run time. Pass exactly one of `url`, `pg`, and `binding`, or leave all three out and pass one to [`connect()`](#connect). - When you pass a `pg` pool or client, you own it: call `db.close()` first, then close it yourself with `pool.end()`. When you pass a `url`, the client creates the pool and closes it. Set that pool's timeouts with `poolOptions`, which has no effect on a pool you supply yourself. - Some types come from a PostgreSQL extension, such as a `pgvector.Vector(1536)` type in your `contract.prisma`. Run `npm install @prisma/orm-extension-pgvector` first. Then `import pgvector from '@prisma/orm-extension-pgvector/runtime'` and pass `extensions: [pgvector]`. @@ -41,7 +41,7 @@ Pass exactly one of `url`, `pg`, and `binding`. | Name | Type | Required | Description | |---|---|---|---| -| `contractJson` / `contract` | JSON contract or contract value | Yes | The contract. Supply exactly one. | +| `contract` | Contract value | Yes | The contract, from `contractFromJson(contractJson)` or `defineContract`. | | `url` | `string` | See note | A PostgreSQL connection string. | | `pg` | `Pool` or `Client` from the `pg` package | See note | An existing `pg` instance to use. You close it yourself. | | `binding` | `{ kind: 'url' \| 'pgPool' \| 'pgClient', ... }` | See note | The same three choices in one object. | @@ -50,17 +50,21 @@ Pass exactly one of `url`, `pg`, and `binding`. | `middleware` | Array of middleware | No | Code that runs around every query. See [How middleware works](/orm/middleware/how-middleware-works). | | `verifyMarker` | `'onFirstUse'` or `false` | No | Whether the first query checks that the database was set up for this contract. Defaults to `'onFirstUse'`. | | `cursor` | `PostgresCursorOptions`, which is `{ batchSize?: number }` | No | Read through a server-side cursor in batches of `batchSize` rows, 100 when you leave `batchSize` out. Without `cursor`, reads are not batched. | +| `mode` | `'strict'` or `'permissive'` | No | How middleware treats a rule violation. Defaults to `'strict'`. See [`mode`](#mode). | +| `collections` | Object mapping model name to a collection class | No | Your own collection classes, used on `db.orm` and on every transaction's `tx.orm`. See [Custom collections](/orm/reference/orm-client#custom-collection-subclass). | + +The type of the options object is `PostgresOptions`, exported from `@prisma/orm-postgres/runtime`. #### Return type | Return type | Example | Description | |---|---|---| -| `PostgresClient` | `postgres({ contractJson, url })` | A client that has not connected yet. Its members are listed below. | +| `PostgresClient` | `postgres({ contract, url })` | A client that has not connected yet. Its members are listed below. | - `.orm` holds your models by model name. See the [ORM client reference](/orm/reference/orm-client). - `.sql` is the SQL query builder, keyed by PostgreSQL schema, then by table name: `db.sql.public.tag`. The table name is the model's `@@map` value, or the model name exactly as written when the model sets no `@@map`. See the [SQL query builder reference](/orm/reference/sql-query-builder). Write raw SQL with ``db.raw.sql`...` ``, as the [raw queries reference](/orm/reference/raw-queries) shows. - `.enums` holds the enum values from your contract, by PostgreSQL schema: `db.enums.public.Role`. `.nativeEnums` holds the values of enum types that exist in PostgreSQL itself, created with `CREATE TYPE`, also by schema. Look the type up by name in brackets: `db.nativeEnums.auth['AalLevel'].values` is `['aal1', 'aal2', 'aal3']`. -- `.contract` is the contract as an object. [`connect()`](#connect), [`runtime()`](#runtime), [`transaction()`](#dbtransactioncallback), [`prepare()`](#dbpreparedeclaration-callback), and [`close()`](#close) are documented on this page. +- `.contract` is the contract as an object. [`connect()`](#connect), [`runtime()`](#runtime), [`query()` and `execute()`](#run-a-plan), [`toSql()`](#dbtosqlplan), [`transaction()`](#dbtransactioncallback), [`prepare()`](#dbpreparedeclaration-callback), and [`close()`](#close) are documented on this page. #### Examples @@ -68,10 +72,12 @@ Pass exactly one of `url`, `pg`, and `binding`. ```typescript import postgres from '@prisma/orm-postgres/runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -const db = postgres({ contractJson, url: process.env.DATABASE_URL! }); +export const contract = contractFromJson(contractJson); +export const db = postgres({ contract, url: process.env.DATABASE_URL! }); ``` These examples assume the file is beside `contract.json` in `src/prisma/`, so from anywhere else, change the relative paths. Write `./contract.d` exactly like that, with no extension. It resolves under `preserve` or `esnext` with `bundler`, the settings `prisma orm init` writes, and under `nodenext` as CommonJS; under `nodenext`, `node18`, or `node20` as ES modules it does not, and the upgrade guide covers that case. The line `import contractJson from './contract.json' with { type: 'json' }` needs Node.js 22.18 or newer and TypeScript 5.9 or newer. In `tsconfig.json` it needs `resolveJsonModule: true` and one of these `module` settings, each with its `moduleResolution`: `preserve` with `bundler`, `esnext` with `bundler`, or `nodenext` with `nodenext`. `node18` and `node20` also work, with `moduleResolution` left unset. `prisma orm init` writes `preserve` and `bundler` into new projects. When `module` is `nodenext` and `package.json` has no `"type": "module"`, TypeScript treats the file as CommonJS, and there the import is `import contractJson from './contract.json';` with no `with` part. The [Prisma ORM 7 to 8 upgrade guide](/guides/upgrade-prisma-orm/postgresql#26-set-up-tsconfigjson-for-the-prisma-orm-8-client) walks through which setting fits which kind of project. TypeScript types every `process.env` value as possibly undefined, so the examples write `!` after it, the same way `prisma orm init` does. @@ -80,12 +86,14 @@ These examples assume the file is beside `contract.json` in `src/prisma/`, so fr ```typescript import postgres from '@prisma/orm-postgres/runtime'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import { Pool } from 'pg'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; +const contract = contractFromJson(contractJson); const pool = new Pool({ connectionString: process.env.DATABASE_URL! }); -const db = postgres({ contractJson, pg: pool }); +const db = postgres({ contract, pg: pool }); ``` ### `connect()` @@ -133,6 +141,80 @@ Get the current runtime synchronously. |---|---|---| | `Runtime` | `const runtime = db.runtime()` | The runtime, returned synchronously. | +### `db.query(plan)` and `db.execute(plan)` [#run-a-plan] + +Run a built query on the client. + +#### Remarks + +- `db.query(plan)` runs a built query and returns its rows as an [`AsyncIterableResult`](#asynciterableresult). `db.execute(plan)` runs a built query that returns no rows and resolves to `{ affectedRows }`. They do what `db.runtime().query(plan)` and `db.runtime().execute(plan)` do, so you do not need the runtime to run a query. +- Both take [`RuntimeExecuteOptions`](#runtimeexecuteoptions) as a second argument. +- A transaction's `tx`, a serverless connection, and SQLite's `sqlite(...)` client have the same two members. +- Keep `db.runtime()` for `connection()` and `telemetry()`. + +#### Examples + +##### Run a SQL builder query and a raw write + +```typescript +const posts = await db.query(db.sql.public.post.select('id', 'title').limit(10).build()); +const { affectedRows } = await db.execute( + db.raw.sql`UPDATE post SET pinned = false`.affectedCount().build(), +); +``` + +### `db.toSql(plan)` + +Render a built query as the SQL it would send, without running it. + +#### Remarks + +- Returns `{ sql, params }`. `params` holds the query's values before Prisma ORM converts them for the database. +- `toSql()` needs no database. It works on a client created with no `url`, `pg`, or `binding`, on the serverless client, on a transaction's `tx`, and on the static context from `@prisma/orm-postgres/static`. +- It takes a query from `db.sql`, `db.raw`, or the ORM. Middleware does not run, so changes a `beforeCompile` hook would make are not in the result. +- A query built for [`prepare()`](#dbpreparedeclaration-callback), with parameters in it, throws an error whose `code` is `RUNTIME.PREPARE_BIND_ON_ADHOC`. A query built from another contract throws `PLAN.HASH_MISMATCH`. Running either query throws the same errors. + +#### Return type + +| Return type | Example | Description | +|---|---|---| +| `{ sql: string; params: readonly unknown[] }` | `db.toSql(plan)` | The SQL text and its parameter values. | + +#### Examples + +##### Print the SQL of a query + +```typescript +const db = postgres({ contract }); // no database needed +const { sql, params } = db.toSql(db.sql.public.post.select('id', 'title').limit(10).build()); +// sql is 'SELECT … LIMIT 10' +``` + +### `mode` + +The `mode` option says how middleware treats a rule violation. + +#### Remarks + +- `'strict'`, the default, makes the [budgets middleware](/orm/middleware/built-in-budgets) throw on every violation. +- With `'permissive'`, a budgets violation whose severity is `'warn'` is logged with the client's `log.warn`, and the query runs. Set the severity with the budgets `severities` option. A violation whose severity is `'error'` still throws. +- `postgres(...)`, `postgresServerless(...)`, `sqlite(...)`, and `supabase(...)` take `mode`. Its type is `RuntimeMode`. + +#### Examples + +##### Log budget warnings instead of throwing + +```typescript +import { budgets } from '@prisma/orm-postgres/family-runtime'; + +const db = postgres({ + contract, + url: process.env.DATABASE_URL!, + middleware: [budgets({ maxRows: 1000, severities: { rowCount: 'warn' } })], + mode: 'permissive', +}); +``` + ### `close()` Close the client and release its pool. @@ -154,7 +236,7 @@ Close the client and release its pool. ##### Close when finished ```typescript -const db = postgres({ contractJson, url: process.env.DATABASE_URL! }); +const db = postgres({ contract, url: process.env.DATABASE_URL! }); const admins = await db.orm.public.User.where((u) => u.kind.eq('admin')).all(); await db.close(); ``` @@ -174,8 +256,8 @@ The client implements `Symbol.asyncDispose`, so `await using` closes it automati ```typescript { - await using db = postgres({ contractJson, url: process.env.DATABASE_URL! }); - const tags = await db.runtime().query(db.sql.public.tag.select('id', 'label').build()); + await using db = postgres({ contract, url: process.env.DATABASE_URL! }); + const tags = await db.query(db.sql.public.tag.select('id', 'label').build()); } // db is closed here ``` @@ -189,8 +271,8 @@ Create a serverless client. #### Remarks -- It takes the options of `postgres(...)` that say how queries run: `contractJson` or `contract`, `extensions`, `middleware`, `verifyMarker`, and `cursor`. It takes no database options. You pass the connection string to `connect()` instead. -- It has `sql`, `raw`, `enums`, `nativeEnums`, and `contract`, which need no connection, and `connect()`. It has no `orm`, `runtime()`, or `transaction()`. Those are on the connection. +- It takes the options of `postgres(...)` that say how queries run: `contract`, `extensions`, `middleware`, `verifyMarker`, `cursor`, `mode`, and `collections`. The type of its options object is `PostgresServerlessOptions`. It takes no database options. You pass the connection string to `connect()` instead. +- It has `sql`, `raw`, `enums`, `nativeEnums`, `contract`, and [`toSql()`](#dbtosqlplan), which need no connection, and `connect()`. It has no `orm`, `runtime()`, or `transaction()`. Those are on the connection. - As on `postgres(...)`, reads load the whole result unless you pass `cursor`. Behind Cloudflare Hyperdrive, reads through a cursor hang, so leave `cursor` out there. ### `connect({ url })` on the serverless client @@ -199,7 +281,7 @@ Open a database connection and return it. #### Remarks -- Returns a `Promise`. The connection has the members of a `postgres(...)` client except `connect()`: `orm`, `sql`, `raw`, `enums`, `nativeEnums`, `contract`, [`runtime()`](#runtime), [`transaction()`](#dbtransactioncallback), [`prepare()`](#dbpreparedeclaration-callback), and [`close()`](#close). Code written for a `postgres(...)` client works on it. Import the type `PostgresServerlessConnection` from `@prisma/orm-postgres/serverless`. +- Returns a `Promise`. The connection has the members of a `postgres(...)` client except `connect()`: `orm`, `sql`, `raw`, `enums`, `nativeEnums`, `contract`, [`runtime()`](#runtime), [`query()`, `execute()`](#run-a-plan), [`toSql()`](#dbtosqlplan), [`transaction()`](#dbtransactioncallback), [`prepare()`](#dbpreparedeclaration-callback), and [`close()`](#close). The collection classes you passed as `collections` are on its `orm`. Code written for a `postgres(...)` client works on it. Import the type `PostgresServerlessConnection` from `@prisma/orm-postgres/serverless`. - `connect()` opens the connection before it returns. It rejects with an error whose `code` is `DRIVER.CONNECTION_FAILED` when the database refuses the connection, rejects the credentials, or does not answer within 20 seconds, and it leaves nothing open. A URL that does not start with `postgres://` or `postgresql://` makes it reject with `RUNTIME.BINDING_INVALID`. - Close the connection with `await using`, as the example below does, or with `close()`. Await every query before the connection closes. A query whose rows you read after that fails. - Each connection that `connect()` returns uses one database connection. While a `for await` loop reads through a cursor, a query on the same connection inside the loop waits forever. @@ -210,10 +292,11 @@ Open a database connection and return it. ```typescript title="src/index.ts" import postgresServerless from '@prisma/orm-postgres/serverless'; +import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './prisma/contract.d'; import contractJson from './prisma/contract.json' with { type: 'json' }; -const postgres = postgresServerless({ contractJson }); +const postgres = postgresServerless({ contract: contractFromJson(contractJson) }); export default { async fetch(_request: Request, env: Env): Promise { @@ -234,7 +317,7 @@ Create a MongoDB client. #### Remarks -- Pass the contract with `contractJson` or `contract` (supply exactly one), the same way as on PostgreSQL. +- Pass the contract as a value with the `contract` option, the same way as on PostgreSQL. Import `contractFromJson` from `@prisma/orm-mongo/static` and build the value with `contractFromJson(contractJson)`. The type of the options object is `MongoOptions`. - Tell the client which database to use in one of four ways: `url`, `uri`, `mongoClient`, or `binding`. A `binding` wraps the choices in one object, with a `kind` field saying which it is: `{ kind: 'url', url, dbName }` or `{ kind: 'mongoClient', client, dbName }`. Use it when your code picks the source at run time, and use the `url` kind for a `uri`. Pass exactly one of the four, or leave all four out and pass one to [MongoDB `connect()`](#connect-1). `dbName` is separate and does not count as one of the four, so `{ url, dbName }` is allowed. - With `url`, the database name comes from the path of the connection string, as in `mongodb://host:27017/app`. Add `dbName` to override that name, or when the string has no name in its path. `uri` takes the same kind of string, and always needs `dbName`. - **Client ownership**: with `url` or `uri`, Prisma ORM creates the underlying `MongoClient` and closes it on `close()`. With `mongoClient`, you supplied the client, so Prisma ORM does **not** close it. Because `close()` leaves your client open, you can use one `MongoClient` both through Prisma ORM and in code you write against the `mongodb` package directly. You close that client yourself. @@ -246,7 +329,7 @@ Pass exactly one of `url`, `uri`, `mongoClient`, and `binding`. `dbName` is sepa | Name | Type | Required | Description | |---|---|---|---| -| `contractJson` / `contract` | JSON contract or contract value | Yes | The contract. Supply exactly one. | +| `contract` | Contract value | Yes | The contract, from `contractFromJson(contractJson)` or `defineContract`. | | `url` | `string` | See note | A `mongodb://` or `mongodb+srv://` string, with the database name in its path. | | `uri` + `dbName` | `string` + `string` | See note | The same kind of string, plus the database name, which is required here. | | `mongoClient` + `dbName` | `MongoClient` + `string` | See note | An existing `MongoClient` you own, plus the database name. | @@ -258,7 +341,7 @@ Pass exactly one of `url`, `uri`, `mongoClient`, and `binding`. `dbName` is sepa | Return type | Example | Description | |---|---|---| -| `MongoClient` | `mongo({ contractJson, url, dbName })` | A client that has not connected yet. This is Prisma ORM's own type. The `mongodb` driver has a class of the same name, so when you import both, name this one `db`. | +| `MongoClient` | `mongo({ contract, url, dbName })` | A client that has not connected yet. This is Prisma ORM's own type. The `mongodb` driver has a class of the same name, so when you import both, name this one `db`. | - `.orm` holds your models by collection name, with no `.public` in the path: `db.orm.users`. The collection name is the model's `@@map` value, or the model name exactly as written when the model sets no `@@map`. The [example schema](/orm/reference/orm-client#example-schema) sets `@@map("users")` on `User`, which is why the key here is `users`. See the [ORM client reference](/orm/reference/orm-client). - `.query` is the pipeline builder, which builds MongoDB aggregation pipelines. See the [pipeline builder reference](/orm/reference/pipeline-builder). `.raw` sends raw MongoDB commands. See the [raw queries reference](/orm/reference/raw-queries). @@ -270,23 +353,26 @@ Pass exactly one of `url`, `uri`, `mongoClient`, and `binding`. `dbName` is sepa ```typescript import mongo from '@prisma/orm-mongo/runtime'; +import { contractFromJson } from '@prisma/orm-mongo/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -const db = mongo({ contractJson, url: process.env.MONGODB_URL!, dbName: 'app' }); +export const contract = contractFromJson(contractJson); +export const db = mongo({ contract, url: process.env.MONGODB_URL!, dbName: 'app' }); ``` ##### Share an existing `MongoClient` ```typescript import mongo from '@prisma/orm-mongo/runtime'; +import { contractFromJson } from '@prisma/orm-mongo/static'; import { MongoClient } from 'mongodb'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; // `MongoClient` here is the driver's class; the `db` below is Prisma ORM's own MongoClient type const client = new MongoClient(process.env.MONGODB_URL!); -const db = mongo({ contractJson, mongoClient: client, dbName: 'app' }); +const db = mongo({ contract: contractFromJson(contractJson), mongoClient: client, dbName: 'app' }); ``` ### `connect()` @@ -383,7 +469,7 @@ The MongoDB client implements `Symbol.asyncDispose` too, so `await using` closes ```typescript { - await using db = mongo({ contractJson, url: process.env.MONGODB_URL!, dbName: 'app' }); + await using db = mongo({ contract, url: process.env.MONGODB_URL!, dbName: 'app' }); await db.connect(); // ... run queries ... } // db is closed here @@ -398,10 +484,10 @@ Run a callback inside a transaction. The transaction commits when the callback r #### Remarks -- Query through `tx`, not `db`. `tx.orm` holds your models. Build queries with `tx.sql`, then run them with `tx.query(...)` to get rows back, or `tx.execute(...)` when the write returns no rows. `tx.execute(...)` resolves to `{ affectedRows }`, the number of rows the statement changed. Every call on `tx` uses the same transaction connection, and queries on `db` run outside the transaction. +- Query through `tx`, not `db`. `tx` has the client's query members: `orm`, `sql`, `raw`, `prepare`, `query`, `execute`, and `toSql`. `tx.orm` holds your models, with your [custom collection classes](/orm/reference/orm-client#custom-collection-subclass) if you passed any. Build queries with `tx.sql` or `tx.raw`, then run them with `tx.query(...)` to get rows back, or `tx.execute(...)` when the write returns no rows. `tx.execute(...)` resolves to `{ affectedRows }`, the number of rows the statement changed. Every call on `tx` uses the same transaction connection, and queries on `db` run outside the transaction. - `db.transaction(...)` takes the callback and nothing else. There are no `isolationLevel`, `timeout`, or `maxWait` options, and Prisma ORM offers no way to set the isolation level. Nothing retries a failed transaction for you. Write the retry loop yourself. A transaction has no time limit. PostgreSQL's `idle_in_transaction_session_timeout` ends one that sits idle between statements, and on PostgreSQL 17 and later `transaction_timeout` caps its total length. Set either in your connection string or server configuration. See the [fundamentals transactions guide](/orm/fundamentals/transactions#options-and-isolation-level). - To put a time limit on the queries inside a transaction, pass a `signal` to each `tx.query(...)` and `tx.execute(...)` call, as the example below does. See [`RuntimeExecuteOptions`](#runtimeexecuteoptions). -- `tx` has no `transaction()` method, so transactions do not nest. If a helper you call starts its own transaction, change it to take `tx` as a parameter instead. `TransactionContext`, imported from `@prisma/orm-postgres/family-runtime`, has `query` and `execute` only, so a helper typed with it cannot use `tx.orm` or `tx.sql`: `async function addTag(tx: TransactionContext) { ... }`. There is no exported type for the full `tx`, so derive it: `type Tx = Parameters[0]>[0]`. +- `tx` has no `transaction()` method, so transactions do not nest. If a helper you call starts its own transaction, change it to take `tx` as a parameter instead. Type the helper's parameter as `PostgresTransactionContext`, imported from `@prisma/orm-postgres/runtime`: `async function addTag(tx: PostgresTransactionContext) { ... }`. `TransactionContext`, imported from `@prisma/orm-postgres/family-runtime`, has `query` and `execute` only, so a helper typed with it cannot use `tx.orm` or `tx.sql`. - `tx.sql` is a full SQL builder, keyed the same way as `db.sql`: use `tx.sql.public.
`, where `public` is the PostgreSQL schema. - Reads inside the transaction see the transaction's own uncommitted writes. - `tx.enums` and `tx.nativeEnums` are the same enum accessors the client has, and `.values` is the list of an enum's members. @@ -412,7 +498,7 @@ Run a callback inside a transaction. The transaction commits when the callback r | Name | Type | Required | Description | |---|---|---|---| -| `callback` | `(tx) => Promise` | Yes | The transactional work. `tx` exposes `orm`, `sql`, `query`, `execute`, `enums`, and `nativeEnums`. | +| `callback` | `(tx) => Promise` | Yes | The transactional work. `tx` exposes `orm`, `sql`, `raw`, `prepare`, `query`, `execute`, `toSql`, `enums`, and `nativeEnums`. | #### Return type diff --git a/apps/docs/content/docs/orm/release-status.mdx b/apps/docs/content/docs/orm/release-status.mdx index c4f4feeb9f3..c8105dd8ab7 100644 --- a/apps/docs/content/docs/orm/release-status.mdx +++ b/apps/docs/content/docs/orm/release-status.mdx @@ -42,8 +42,8 @@ The two packages have different version numbers, and that is normal: they are re | Package | `latest` on 21 October 2026 | | --- | --- | | `prisma` (the command-line tool) | `8.0.0-rc.24` | -| `@prisma/orm-postgres` (the library) | `8.0.0-rc.18` | -| `@prisma/orm-mongo` (the MongoDB library) | `8.0.0-rc.18` | +| `@prisma/orm-postgres` (the library) | `8.0.0-rc.19` | +| `@prisma/orm-mongo` (the MongoDB library) | `8.0.0-rc.19` | To see the `latest` version of each package today, run `npm view prisma dist-tags`, `npm view @prisma/orm-postgres dist-tags`, and `npm view @prisma/orm-mongo dist-tags`, and read the `latest` entry in each result. You need all three commands because the command-line tool and the libraries are versioned separately. From 6dd19532dc5c18a62f47190c5c5dc4ca4098aaea Mon Sep 17 00:00:00 2001 From: willbot Date: Wed, 21 Oct 2026 18:50:24 +0200 Subject: [PATCH 2/3] docs(docs): plain-language pass on the Prisma ORM 8 pages changed for 8.0.0-rc.19 Signed-off-by: willbot Signed-off-by: Will Madden --- .../add-to-existing-project/mongodb.mdx | 4 ++- .../docs/(index)/prisma-orm/from-scratch.mdx | 8 +++-- apps/docs/content/docs/cli/configuration.mdx | 16 ++++++++-- apps/docs/content/docs/cli/contract-emit.mdx | 4 ++- apps/docs/content/docs/cli/contract-infer.mdx | 19 ++++++++++-- apps/docs/content/docs/cli/contract-print.mdx | 8 +++-- apps/docs/content/docs/cli/db-schema.mdx | 10 +++--- apps/docs/content/docs/cli/db-sign.mdx | 8 ++++- apps/docs/content/docs/cli/db-verify.mdx | 10 ++++-- apps/docs/content/docs/cli/migration-plan.mdx | 14 ++++++--- apps/docs/content/docs/cli/orm-init.mdx | 2 +- .../guides/deployment/cloudflare-workers.mdx | 31 ++++++++++++++++--- .../switch-to-prisma-orm/from-mongoose.mdx | 6 ++-- .../guides/upgrade-prisma-orm/postgresql.mdx | 4 +-- .../docs/orm/coming-from-prisma-orm-7.mdx | 10 +++--- .../orm/contract-authoring/psl-syntax.mdx | 29 ++++++++++------- .../the-contract-artifact.mdx | 8 ++++- .../typescript-schema-builder.mdx | 23 +++++++++++--- .../content/docs/orm/data-modeling/index.mdx | 2 +- .../docs/orm/data-modeling/mongodb.mdx | 6 +++- .../data-modeling/relational-databases.mdx | 2 +- .../docs/orm/extensions/using-extensions.mdx | 12 +++++-- .../docs/orm/fundamentals/reading-data.mdx | 2 +- .../docs/orm/fundamentals/transactions.mdx | 5 ++- .../docs/orm/fundamentals/writing-data.mdx | 4 +-- .../authoring-custom-middleware.mdx | 12 ++++--- .../docs/orm/middleware/built-in-budgets.mdx | 26 ++++++++++++---- .../docs/orm/middleware/built-in-lints.mdx | 6 ++-- .../orm/middleware/how-middleware-works.mdx | 12 +++---- .../orm/migrations/generating-a-migration.mdx | 2 +- .../orm/migrations/how-migrations-work.mdx | 8 +++-- .../docs/orm/reference/migration-api.mdx | 10 ++++-- .../content/docs/orm/reference/orm-client.mdx | 6 ++-- .../docs/orm/reference/raw-queries.mdx | 4 +-- .../reference/transactions-and-runtime.mdx | 19 +++++++----- 35 files changed, 250 insertions(+), 102 deletions(-) diff --git a/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/mongodb.mdx b/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/mongodb.mdx index 9527a2912a9..49e978590a7 100644 --- a/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/mongodb.mdx +++ b/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/mongodb.mdx @@ -109,7 +109,7 @@ model Post { } ``` -Keep the first line, because `contract emit` reads only `.prisma` files that start with it. `@@map("users")` names the collection that stores the model. MongoDB stores the identifier of every document in `_id`, and `@map("_id")` stores the `id` field there. In `db.orm` queries and in their results you use the field name, `id`, and Prisma ORM reads and writes `_id` for you. The same goes for any other field with `@map`. A pipeline built with `db.query` reads documents as MongoDB stores them, so it uses `_id`. +Keep the first line, because `contract emit` reads only `.prisma` files that start with it. `@@map("users")` names the collection that stores the model. MongoDB stores the identifier of every document in `_id`, and `@map("_id")` stores the `id` field there. In `db.orm` queries and in their results you use the field name, `id`, and Prisma ORM reads and writes `_id` for you. The same goes for any other field with `@map`. Change the models to match your documents: one model for each collection, with one field for each field of its documents. [Data modeling](/orm/data-modeling) lists the field types. @@ -166,6 +166,8 @@ The document has no `username` field, and `username` is optional in the contract `db.query` builds a MongoDB aggregation pipeline on a collection, which it names as it is stored in MongoDB. `.build()` returns the pipeline, and `runtime.query()` runs it, where `runtime` is the object that `await db.runtime()` returns. Replace `users`, `email`, and the value with your own again. +A pipeline reads documents as MongoDB stores them, so it uses stored names, not the field names in your schema. Write `_id`, not `id`, and use the name in `@map` for every other field that has one. + Replace `script.ts` with this version: ```typescript title="script.ts" diff --git a/apps/docs/content/docs/(index)/prisma-orm/from-scratch.mdx b/apps/docs/content/docs/(index)/prisma-orm/from-scratch.mdx index 2a22c270e26..d7a19f4865a 100644 --- a/apps/docs/content/docs/(index)/prisma-orm/from-scratch.mdx +++ b/apps/docs/content/docs/(index)/prisma-orm/from-scratch.mdx @@ -43,7 +43,11 @@ npm install @prisma/orm-postgres dotenv npm install --save-dev prisma tsx typescript ``` -`@prisma/orm-postgres` is the library your code imports, and `prisma` is the command-line tool. `dotenv` loads `.env`, `tsx` runs `index.ts` without a build step, and `typescript` 5.9 or newer is a peer dependency of `@prisma/orm-postgres`. So is `temporal-polyfill`, which npm installs for you; with Yarn, add it yourself. The two Prisma packages have different version numbers (for example, `prisma` 8.0.0-rc.24 and `@prisma/orm-postgres` 8.0.0-rc.19); that is normal, and any two `latest` versions work together. +`@prisma/orm-postgres` is the library your code imports, and `prisma` is the command-line tool. `dotenv` loads `.env`, `tsx` runs `index.ts` without a build step, and `typescript` 5.9 or newer is a peer dependency of `@prisma/orm-postgres`. + +`temporal-polyfill` is a peer dependency of `@prisma/orm-postgres` too. It adds `Temporal`, the newer JavaScript date and time API, to runtimes such as Node.js 24 that do not have it built in. npm installs it for you, but with Yarn you add it yourself. + +The two Prisma packages have different version numbers, for example `prisma` 8.0.0-rc.24 and `@prisma/orm-postgres` 8.0.0-rc.19. That is normal, and any two `latest` versions work together. ## 2. Create the config file and `.env` @@ -134,7 +138,7 @@ The `marker` line in the output is the record that signing stores in the databas ## 5. Write and read data -Create `index.ts`, which turns the two emitted files into a typed contract value with `contractFromJson`, builds the client from it, and then deletes, creates, updates, and reads rows on the `User` model: +Create `index.ts`, which passes the two files from step 4 to `contractFromJson`. That function checks `contract.json` and gives it the types from `contract.d.ts`, and the file then builds the client from the result and deletes, creates, updates, and reads rows on the `User` model: ```typescript title="index.ts" import "dotenv/config"; diff --git a/apps/docs/content/docs/cli/configuration.mdx b/apps/docs/content/docs/cli/configuration.mdx index 80e8e331363..61d70c9c1f7 100644 --- a/apps/docs/content/docs/cli/configuration.mdx +++ b/apps/docs/content/docs/cli/configuration.mdx @@ -123,13 +123,25 @@ export default definePrismaConfig({ }); ``` -`prisma7Schema` takes one schema file, or a directory of `.prisma` files as Prisma ORM 7 reads it. A `DateTime` field, and a field with `@db.Timestamp`, `@db.Timestamptz`, `@db.Date`, or `@db.Time`, reads and writes strings such as `"2026-09-14 10:00:00.123"`, the text PostgreSQL prints, not `Temporal` values, so your application needs no `Temporal`. A `DateTime` value is in UTC, as Prisma ORM 7 writes it. An `@updatedAt` field is filled with such text from your application's clock, also on a `@db.Date`, `@db.Time`, or `@db.Timetz` field, and it may sit beside `@default(now())`, which stays the database default. A list such as `String[]` reads as a list whose elements may be `null`, because the database Prisma ORM 7 built has no check against `null` elements. To get `Temporal` values, write a Prisma ORM 8 contract with `npx prisma contract print --output prisma/contract.prisma` and change the types there. It reads every `.prisma` file it is given, so these files do not need the `// use prisma-8` first line. `contract emit` writes `contract.json` and `contract.d.ts` into the directory that holds the schema, here `prisma/`, unless you set `output`. Prisma ORM 7 keeps owning the database and its migrations, and you run its commands as `prisma7`, the Prisma ORM 7 CLI that [`orm init --from-prisma7-schema`](/cli/orm-init#on-a-prisma-orm-7-project) installs next to Prisma ORM 8. +`prisma7Schema` takes one schema file, or a directory of `.prisma` files as Prisma ORM 7 reads it. `prisma7Schema` reads every `.prisma` file it is given, so these files do not need the `// use prisma-8` first line. `contract emit` writes `contract.json` and `contract.d.ts` into the directory that holds the schema, here `prisma/`, unless you set `output`. Prisma ORM 7 keeps owning the database and its migrations, and you run its commands as `prisma7`, the Prisma ORM 7 CLI that [`orm init --from-prisma7-schema`](/cli/orm-init#on-a-prisma-orm-7-project) installs next to Prisma ORM 8. `orm init --from-prisma7-schema` also writes this config for you. + +Prisma ORM 7 gave your application date and time values as JavaScript `Date` objects. With `prisma7Schema`, Prisma ORM 8 gives them as strings instead. This applies to a `DateTime` field and to a field with `@db.Timestamp`, `@db.Timestamptz`, `@db.Date`, or `@db.Time`: each value is the text PostgreSQL prints, such as `"2026-09-14 10:00:00.123"`. A `DateTime` value is in UTC, as Prisma ORM 7 writes it. + +Prisma ORM 8 can also give these values as `Temporal` objects. `Temporal` is the newer JavaScript date and time API, and runtimes without it, such as Node.js 24, need the `temporal-polyfill` package. To get `Temporal` values, write a Prisma ORM 8 contract with `npx prisma contract print --output prisma/contract.prisma`, and in that file change `TimestampString(3)` to `Timestamp(3)`, `TimestamptzString(3)` to `Timestamptz(3)`, `DateString` to `Date`, and `TimeString(3)` to `Time(3)`. + +When your code creates or updates a row, Prisma ORM fills each `@updatedAt` field with the time from your application's clock, as text in the same form. This also works on a `@db.Date`, `@db.Time`, or `@db.Timetz` field. A field can have both `@updatedAt` and `@default(now())`, and `@default(now())` stays the column's default in the database. + +A list such as `String[]` reads as a list whose elements may be `null`, because the database that Prisma ORM 7 built does not stop a list from holding `null` elements. Once the config is in place, run `npx prisma contract emit` and then [`npx prisma db sign`](/cli/db-sign). `db sign` checks that the database matches the contract and then records that in the database, without changing your tables. Run both commands again after each Prisma ORM 7 migration, whether you ran `npx prisma7 migrate dev` or `npx prisma7 migrate deploy`, so that the record names the new contract. An `Unsupported("ltree")` field becomes a column of that database type with no field on the model, as in the Prisma ORM 7 client: migrations and `db verify` keep the column, and your code cannot read or write it. `contract print` cannot write such a column yet, so a schema with `Unsupported(...)` fields stays your contract source. -`contract emit` fails on any part of the schema that Prisma ORM 8 cannot represent exactly, such as a `view` block or `relationMode = "prisma"`. The error names the file and the line and suggests an edit to the Prisma ORM 7 schema. When that edit would also change the database on the next Prisma ORM 7 migration, the error says so. A default function argument Prisma ORM 7 read as something else, such as `uuid(5)` or `cuid(1)`, is refused with `PSL.PRISMA7_UNKNOWN_DEFAULT`, and the message names the value to write. Prisma ORM 8 has no views, so for a `view` block the error suggests removing the view or replacing it with a model over the table the view reads. `orm init --from-prisma7-schema` writes this config for you. +`contract emit` fails on any part of the schema that Prisma ORM 8 cannot represent exactly, such as a `view` block or `relationMode = "prisma"`. The error names the file and the line and suggests an edit to the Prisma ORM 7 schema. When that edit would also change the database on the next Prisma ORM 7 migration, the error says so. + +Prisma ORM 8 has no views, so for a `view` block the error suggests removing the view or replacing it with a model over the table the view reads. + +`contract emit` also fails when a default function gets an argument it does not take, such as `uuid(5)` or `cuid(1)`. The error code is `PSL.PRISMA7_UNKNOWN_DEFAULT`, and the message names the values the function takes, for example `uuid: version must be 4 or 7, got 5`. ## Use a Prisma ORM 6 MongoDB schema diff --git a/apps/docs/content/docs/cli/contract-emit.mdx b/apps/docs/content/docs/cli/contract-emit.mdx index b6f0c5d4513..2afbdc28438 100644 --- a/apps/docs/content/docs/cli/contract-emit.mdx +++ b/apps/docs/content/docs/cli/contract-emit.mdx @@ -52,7 +52,9 @@ npx prisma db sign --db "$DATABASE_URL" The script runs on any Node.js version Prisma ORM supports: 22.18 or newer on the 22 line, 24.11 or newer on the 24 line, or 26 or newer. It rewrites every contract, snapshot, migration, ref, `migration.ts`, and `contract.d.ts` in the project, and every storage hash changes once. Until a database is signed, `db migrate` refuses with `MIGRATION.MARKER_MISMATCH`. Commit your work before you run it, so you can review its changes. -Since `8.0.0-rc.19`, `contract.d.ts` writes field and column types as generics applied to the contract, so a SQL `contract.d.ts` emitted by an earlier release no longer compiles. Run `contract emit` after you upgrade. The same emit stores each PostgreSQL default in the one form its column's type gives, such as `"1.50"` on a `numeric(10,2)` column, and a MongoDB model's stored field names under `storage.fields`; the CLI refuses a contract without them with `CONTRACT.VALIDATION_FAILED`. Then run [`db sign`](/cli/db-sign) against every database whose storage hash changed. The [upgrade instructions](https://github.com/prisma/orm/blob/v8.0.0-rc.19/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.18-to-8.0.0-rc.19/) include scripts for the contracts stored under `migrations/`. +When you upgrade to `8.0.0-rc.19`, run `contract emit` again, because the files an earlier release wrote do not work with this one. A SQL `contract.d.ts` from an earlier release no longer compiles. The CLI refuses every older MongoDB `contract.json`, and some older PostgreSQL ones, with `CONTRACT.VALIDATION_FAILED`. + +Then look at the `storageHash` value in `contract.json`. It identifies the tables and columns your contract describes, and `db sign` records it in each database. If the new emit changed it, run [`db sign`](/cli/db-sign) against every database that uses the contract, such as development, staging, and production. Until you do, `db migrate` fails with `MIGRATION.MARKER_MISMATCH` and `db verify` fails with `CONTRACT.MARKER_MISMATCH`, because the database still records the old value. The [upgrade instructions](https://github.com/prisma/orm/blob/v8.0.0-rc.19/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.18-to-8.0.0-rc.19/) include scripts that update the copies of the contract stored under `migrations/`. When the contract source cannot be read, the command fails with `CONTRACT.SOURCE_LOAD_FAILED` and prints each finding, meaning each problem it found in the source, with the file and line where it knows them. In `--json` output, the findings are the entries of the `envelope.diagnostics` array in the final `result` event, next to `envelope.error`. Each entry has a `code`, a `summary`, and, where known, `where.path` and `where.line`. If an entry's `code` is `CONTRACT.SOURCE_DIAGNOSTIC`, the code of the specific problem, such as `PSL_NO_OPTED_IN_SCHEMA_FILES`, is in its `meta.code`. diff --git a/apps/docs/content/docs/cli/contract-infer.mdx b/apps/docs/content/docs/cli/contract-infer.mdx index 64376be0f11..cca2e145db9 100644 --- a/apps/docs/content/docs/cli/contract-infer.mdx +++ b/apps/docs/content/docs/cli/contract-infer.mdx @@ -45,11 +45,24 @@ Inference gives you a starting point, not a finished design. Review: The file starts with `// use prisma-8`, because `contract emit` reads only `.prisma` files that start with that line. Each model is named after its table, and gets `@@map` only when the table name differs from the model name: a table `User` becomes `model User`, and a table `user_profile` becomes `model UserProfile` with `@@map("user_profile")`. -Every column default is written in a form `contract emit` accepts, so you can emit the file without editing it. [Default values](/orm/contract-authoring/psl-syntax#default-values) shows those forms. A list column that allows `NULL` is written `Type[]?`, and a list column that is `NOT NULL` is written `Type[]`. A list column without the check Prisma ORM adds against `NULL` elements is written with `?` on the element type, as `Type?[]`. +Every column default is written in a form `contract emit` accepts, so you can emit the file without editing it. [Default values](/orm/contract-authoring/psl-syntax#default-values) shows those forms. -Each column's type is read from the data types of the configured target and the extension packs in `prisma.config.ts`, and written as the shortest type constructor that matches it, such as `VarChar(20)`, `Bit(8)`, `VarBit(16)`, or `Interval(3)`. When columns have types that no pack declares, the command fails once and lists every one of them, instead of writing a contract that `contract emit` cannot read. Add the pack that declares the type to `extensions`. A contract you write by hand can declare such a column as ``Raw(sql`...`)``, as [Columns of any database type](/orm/contract-authoring/psl-syntax#columns-of-any-database-type) shows. +In a list column's type, a `?` means something different depending on where it is: -Date and time columns are written as text types, so your application reads and writes them as strings and needs no `Temporal`: a `timestamp(3)` column is written `TimestampString(3)`, `timestamptz(3)` is `TimestamptzString(3)`, `date` is `DateString`, and `time(3)` is `TimeString(3)`. A `timestamp` value reads as `"2026-09-14 10:00:00.123"`. To get `Temporal` values instead, change the types to `Timestamp(3)`, `Timestamptz(3)`, `Date`, and `Time(3)` in the inferred file. A `bytea` default is written as a `bytea` tagged literal in the hexadecimal PostgreSQL prints, such as ``@default(bytea`\x68656c6c6f`)``. +- `Type[]?`, with `?` after the brackets: the column itself can be `NULL`. +- `Type?[]`, with `?` before the brackets: the list can hold `NULL` elements. + +A list column that Prisma ORM migrations created cannot hold `NULL` elements, because the migration adds a check that stops it, so it gets no `?` before the brackets. Any other list column gets one. + +Each column gets a type such as `VarChar(20)`, `Bit(8)`, `VarBit(16)`, or `Interval(3)`, written with the fewest parameters that describe the column exactly. The types come from Prisma ORM's types for your database and from the extension packs listed under `extensions` in `prisma.config.ts`. An extension pack is the Prisma ORM package for a database extension, such as pgvector. + +When a column has a type that none of these provide, the command writes no file. It fails with `CONTRACT.INFER_COLUMN_TYPE_UNCLAIMED` and lists every such column, such as `public.doc.meta: hstore`. To get past the error, do one of the following for each listed column: + +- If the type comes from a database extension that has a Prisma ORM pack, add the pack to `extensions` in `prisma.config.ts` and run `contract infer` again. +- Write the contract by hand, and declare the column's field as ``Raw(sql`hstore`)``. `Raw(...)` declares a field by the database's own name for its type, here the type the error lists. [Columns of any database type](/orm/contract-authoring/psl-syntax#columns-of-any-database-type) shows how. +- Change the column's type, or remove the column from the database. + +Prisma ORM 7 read date and time columns as `DateTime` fields, which gave your application JavaScript `Date` objects. `contract infer` writes them as text types instead, so your application reads and writes them as strings. It also needs no `Temporal`, the newer JavaScript date and time API. A `timestamp(3)` column is written `TimestampString(3)`, `timestamptz(3)` is `TimestamptzString(3)`, `date` is `DateString`, and `time(3)` is `TimeString(3)`. A `timestamp` value reads as `"2026-09-14 10:00:00.123"`. To get `Temporal` values instead, change the types to `Timestamp(3)`, `Timestamptz(3)`, `Date`, and `Time(3)` in the inferred file. A `bytea` default is written as the hexadecimal text PostgreSQL prints, inside backticks with `bytea` in front, such as ``@default(bytea`\x68656c6c6f`)``. The word in front of the backticks names the type of the value. A contract inferred before `8.0.0-rc.15` keeps its types until you run `contract infer` again. When you do, the date and time fields and any `bytea` default change, and so does the storage hash: emit the contract, update the code that used `Temporal` values for these fields, then run `prisma db sign` for a database you keep with `db update`, or create an empty migration with [`migration new`](/cli/migration-new) and apply it when you use migrations. diff --git a/apps/docs/content/docs/cli/contract-print.mdx b/apps/docs/content/docs/cli/contract-print.mdx index 1557d65c7a9..32304bc0133 100644 --- a/apps/docs/content/docs/cli/contract-print.mdx +++ b/apps/docs/content/docs/cli/contract-print.mdx @@ -42,11 +42,13 @@ npx prisma contract print > contract.v8.prisma ## What it writes -The file starts with `// use prisma-8`. When you point `contract` at the file and run `contract emit`, you get the same contract as before, with the same hashes, so the database needs no migration and no new `db sign`. +The file starts with `// use prisma-8`, the first line `contract emit` looks for in a `.prisma` file. When you point `contract` at the file and run `contract emit`, you get the same contract as before, with the same hashes, so the database needs no migration and no new `db sign`. -A value the client generates is written as `@onCreate(...)` and `@onUpdate(...)`, after the field's type. A Prisma ORM 7 `@default(uuid())` prints as `@onCreate(uuid())`, and a field set from your application's clock on create and update prints as, for example, `Timestamptz(3) @onCreate(now()) @onUpdate(now())`. A date, time, or bytes default prints as a tagged literal, and a column of a database type no field type covers prints as ``Raw(sql`...`)``. +A value that Prisma ORM generates in your application, rather than in the database, is written with `@onCreate(...)` and `@onUpdate(...)` after the field's type. `@onCreate` sets the value when a row is created, and `@onUpdate` sets it again on every update. So a Prisma ORM 7 `@default(uuid())` prints as `@onCreate(uuid())`, and an `@updatedAt` field prints as, for example, `Timestamptz(3) @onCreate(now()) @onUpdate(now())`. In a Prisma ORM 8 schema you write these attributes too: `contract emit` refuses `@default(uuid())` and points you to `@onCreate(uuid())`. `@default(now())` and `@default(autoincrement())` still work, because the database fills those values. -A Prisma ORM 7 schema with an `Unsupported(...)` field cannot be printed yet: its column has no field, and Prisma ORM 8 PSL cannot declare one. Keep the Prisma ORM 7 schema as the contract source until then. +A date, time, or bytes default prints with the name of its type in front of the value, and the value in backticks, such as ``@default(timestamptz`2024-01-01T00:00:00Z`)``. A column whose database type has no Prisma ORM 8 field type prints as ``Raw(sql`...`)``, which declares the column by the database's name for its type, such as ``Raw(sql`ltree`)``. + +A Prisma ORM 7 schema with an `Unsupported(...)` field cannot be printed yet. Prisma ORM 8 reads such a field as a column with no field on the model, so your code cannot read or write it. A `Raw(...)` field is different: it is a field on the model, and your code reads and writes its value. Prisma ORM 8 PSL has no way to declare a column without a field, so keep the Prisma ORM 7 schema as the contract source until it does. When a part of the contract has no PSL form, the command names that part, fails, and writes nothing. It also refuses an `--output` path that is one of the files it reads, `prisma.config.ts`, or a file `contract emit` writes. diff --git a/apps/docs/content/docs/cli/db-schema.mdx b/apps/docs/content/docs/cli/db-schema.mdx index a4a4b97057d..cc40286d3c3 100644 --- a/apps/docs/content/docs/cli/db-schema.mdx +++ b/apps/docs/content/docs/cli/db-schema.mdx @@ -33,19 +33,17 @@ npx prisma db schema --db "$DATABASE_URL" --json > schema.json ## Column types -The tree and the JSON output name each column's type as the database reports it. In the JSON output it is the column's `typeText`: +On PostgreSQL and SQLite, the tree and the JSON output show each column's type as the database names it. In the JSON output, this text is the column's `typeText`: ```bash npx prisma db schema --db "$DATABASE_URL" --json | jq '.schema.tables[].columns[].typeText' ``` -On PostgreSQL, `typeText` is the text `format_type` prints, such as `integer` or `character varying(20)`, not a short name such as `int4`. It differs from what the database prints in three cases: +On PostgreSQL, `typeText` is the full type name, such as `integer` or `character varying(20)`, not a short name such as `int4`. An enum is named as your contract names it, such as `My Mood`, or `app.status` for an enum outside the `public` schema. A type from a schema that is not on the search path keeps the schema name, such as `extensions.vector(3)` for a pgvector column. -- On SQLite, the declared type is lower-cased: a column declared `VARCHAR(255)` reads `varchar(255)`. -- On PostgreSQL, an enum is named as a contract names it: `My Mood` where `format_type` prints `"My Mood"`, and `app.status` for an enum outside `public`. -- On PostgreSQL, a type in a schema that is not on the search path keeps its schema: `extensions.vector(3)` for a pgvector column when `extensions` is not on the search path. +On SQLite, which Prisma ORM 8 supports as an [experimental package](/orm/supported-databases), `typeText` is the type the column was declared with, in lower case: a column declared `VARCHAR(255)` reads `varchar(255)`. -Before `8.0.0-rc.19`, the JSON output had `nativeType` and `resolvedNativeType` instead of `typeText`. +If a script reads `nativeType` or `resolvedNativeType` from the JSON output, change it to read `typeText`, which replaced both fields in `8.0.0-rc.19`. ## Related commands diff --git a/apps/docs/content/docs/cli/db-sign.mdx b/apps/docs/content/docs/cli/db-sign.mdx index d340854ad7d..e5e4619be10 100644 --- a/apps/docs/content/docs/cli/db-sign.mdx +++ b/apps/docs/content/docs/cli/db-sign.mdx @@ -106,6 +106,12 @@ Use `db sign` only after you believe the live database already matches the emitt - a database restore that you need to mark as matching the current contract - a Prisma ORM upgrade that changes your contract's storage hash but not the database -Upgrading to `8.0.0-rc.19` changes the storage hash of a SQLite contract with a datetime or JSON column, of a PostgreSQL contract whose defaults `contract emit` rewrites, and of a contract that used `@noCheck`. The database still holds the old hash, so `db migrate` fails with `MIGRATION.MARKER_MISMATCH` and `db verify` with `CONTRACT.MARKER_MISMATCH` until you sign. After you emit the new contract, run `db sign` against every database: development, staging, and production. +When you upgrade to `8.0.0-rc.19` and emit the contract again, the contract's storage hash can change even though the database does not. The storage hash is the `storageHash` value in `contract.json`, and `db sign` records it in the database. It changes for these contracts: + +- a SQLite contract with a datetime or JSON column +- a PostgreSQL contract whose defaults `contract emit` now writes in another form +- a contract that used `@noCheck`, which this release removes + +Until you sign, the database still holds the old hash, so `db migrate` fails with `MIGRATION.MARKER_MISMATCH` and `db verify` fails with `CONTRACT.MARKER_MISMATCH`. So after you emit the new contract, run `db sign` against every database that Prisma ORM 8 has already signed or migrated, which usually means development, staging, and production. Do not use `db sign` to hide drift. If verification fails, `db sign` writes no signature and offers two ways out. One changes the database: run `prisma db update` to make the database match the contract, then sign again. The other leaves the database as it is: change the contract source to describe the database, run `contract emit` again, then sign again. diff --git a/apps/docs/content/docs/cli/db-verify.mdx b/apps/docs/content/docs/cli/db-verify.mdx index a14984f10ea..ed94ab72c88 100644 --- a/apps/docs/content/docs/cli/db-verify.mdx +++ b/apps/docs/content/docs/cli/db-verify.mdx @@ -63,9 +63,15 @@ Fix the database or contract, emit again if needed, then verify again. ## How column types are compared -`db verify` compares a column's type as a data type with its parameters, not as text. It reads each database column's type, finds the data type of your target or extensions that declares it, and compares that with your contract. So `Numeric(10)` in your contract matches a `numeric(10,0)` column, and `VarChar(20)` matches `character varying(20)`. The migration planner compares types the same way. +`db verify` treats two ways of writing the same column type as equal. So `Numeric(10)` in your contract matches a `numeric(10,0)` column, and `VarChar(20)` matches `character varying(20)`. The migration planner compares types the same way, so neither command reports a difference that is only in how a type is written. -A database column whose type no data type of your target or extensions declares gets a message that says so, such as `The database column is "point", which no data type in the configured stack declares.` Add the extension that provides the type to your config, or declare the column with ``Raw(sql`point`)``. +When a database column has a type that Prisma ORM has no type for, either for your database or in the extensions listed in `prisma.config.ts`, the message says so: + +```text +The database column is "point", which no data type in the configured stack declares. +``` + +"The configured stack" in the message means your database and those extensions. Add the extension that provides the type to your config, or declare the column in your contract with ``Raw(sql`point`)``. ## How PostgreSQL defaults are compared diff --git a/apps/docs/content/docs/cli/migration-plan.mdx b/apps/docs/content/docs/cli/migration-plan.mdx index 642ca043975..9a04217244a 100644 --- a/apps/docs/content/docs/cli/migration-plan.mdx +++ b/apps/docs/content/docs/cli/migration-plan.mdx @@ -43,13 +43,19 @@ npx prisma migration plan --name add_users_table | `--from ` | Uses a specific starting contract state instead of the `db` ref. Accepts a [contract reference](/orm/migrations/the-migration-graph#contract-reference-forms): a hash, a hash prefix, a [ref name](#contract-references), a migration directory name, [`^`](#contract-references), or [`@empty`](#contract-references). `@db` is not accepted here, because `migration plan` is offline and cannot read a database. `@contract` is not accepted either. | | `--to ` | Sets the destination contract state. Defaults to the emitted contract. Accepts the same forms as `--from`, except `@empty`, which is refused as a destination. | | `--rename :` | Plans a model or field rename that keeps its rows, such as `--rename User.name:User.fullName`, instead of a drop and a create. Repeatable. | -| `--delete ` | Lets a dropped model or field lose its data, such as `--delete User.legacy`. Repeatable. | -| `--convert ` | For a type change that can lose values: writes a slot in `migration.ts` for the conversion you write yourself. | -| `--backfill ` | For a new required field: writes a slot in `migration.ts` to fill existing rows yourself. | +| `--delete ` | Lets a dropped model or field lose its data. Name a model, such as `--delete Legacy`, or a field, such as `--delete User.legacy`. Repeatable. | +| `--convert ` | For a type change that can lose values: adds a `placeholder(...)` call to `migration.ts`, which you replace with your own conversion code. | +| `--backfill ` | For a new required field: adds a `placeholder(...)` call to `migration.ts`, which you replace with code that fills the field in existing rows. | | `--config ` | Read this config file instead of `./prisma.config.ts`. | | `--json` | Prints a machine-readable result. | -On MongoDB, `--rename` and `--delete` name fields by their names in the schema, not by the names they are stored under. For a field `id ObjectId @id @map("_id")`, write `--rename User.id:User.userId`, not `User._id`. The first migration you plan after upgrading to `8.0.0-rc.19` starts from a contract snapshot that the upgrade script rewrote, which names each field by its stored name, so in that one migration name the old field by its stored name. +When the migration would lose data, because it drops a model or field or changes a field's type, `migration plan` asks you what to do before it writes anything. In a terminal you answer the question there. With `--yes`, or when the command cannot ask, it fails with `CLI.CONSENT_REQUIRED` and lists the flags that answer each question. + +When you add a required field without `--backfill`, the migration fills the existing rows with a temporary value, such as `''` for text or `0` for a number, and `migration plan` prints that value. If the field cannot take a temporary value, for example because it is unique, the migration works only on an empty table and fails on a table that has rows. + +On MongoDB, `--rename` and `--delete` name fields by their names in the schema, not by the names they are stored under. For a field `id ObjectId @id @map("_id")`, write `--rename User.id:User.userId`, not `User._id`. + +The first MongoDB migration you plan after upgrading to `8.0.0-rc.19` is the exception. The [upgrade instructions](https://github.com/prisma/orm/blob/v8.0.0-rc.19/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.18-to-8.0.0-rc.19/) include a script that rewrites the copies of your old contract under `migrations/`, and those copies name each field by its stored name. That first migration starts from such a copy, so in it you name the old field by its stored name and the new field by its schema name. For a field that was `name String @map("display_name")` and is now `title String`, write `--rename User.display_name:User.title`. From the next migration on, use schema names only. ### Contract references [#contract-references] diff --git a/apps/docs/content/docs/cli/orm-init.mdx b/apps/docs/content/docs/cli/orm-init.mdx index 4d6c906cf2d..52687eb172c 100644 --- a/apps/docs/content/docs/cli/orm-init.mdx +++ b/apps/docs/content/docs/cli/orm-init.mdx @@ -45,7 +45,7 @@ The exact files depend on the target and authoring style, but a Postgres PSL set - `prisma.config.ts` - a starter contract file such as `src/prisma/contract.prisma`, whose first line is `// use prisma-8`. Keep that line, because `contract emit` reads only `.prisma` files that start with it. - emitted contract artifacts after installation runs -- a runtime client file (`src/prisma/db.ts`) that imports `contract.json`, turns it into a typed contract value with `contractFromJson`, and creates the client from that value +- a runtime client file (`src/prisma/db.ts`) that loads `contract.json` with `contractFromJson` and creates the client from it - `.env.example`, `tsconfig.json`, and updated `package.json` - `prisma-8.md`, a short quick reference for writing your first typed query diff --git a/apps/docs/content/docs/guides/deployment/cloudflare-workers.mdx b/apps/docs/content/docs/guides/deployment/cloudflare-workers.mdx index 2e51b1762f2..d6ec8c944b7 100644 --- a/apps/docs/content/docs/guides/deployment/cloudflare-workers.mdx +++ b/apps/docs/content/docs/guides/deployment/cloudflare-workers.mdx @@ -184,11 +184,12 @@ export default { } satisfies ExportedHandler; ``` -Three things to notice: +Keep these points in mind when you change the handler: - The client is created inside `fetch`, with the connection string from `env`, and closed after the response with `ctx.waitUntil(db.close())`. This is the opposite of a Node.js server, where you keep one client for the life of the process. Workers do not let a socket opened during one request be used by another, so a module-level client works for the first request and hangs the next one. That is why the Worker does not import `src/prisma/db.ts`. -- Models are namespace-qualified on PostgreSQL: `db.orm.public.User`. `.create(...)` returns the inserted row, database defaults included. -- `contract.json` is imported with `with { type: "json" }`. Wrangler bundles it into the Worker. `contractFromJson` turns it into a typed contract value once, at module level, and every request's client takes that value. The value is not exported, because a Worker treats every named export of its entry module as an entrypoint. If another module needs it, declare it in a separate module and import it from there. +- On PostgreSQL, you reach a model through the database namespace it lives in: `db.orm.public.User`, where `public` is the PostgreSQL schema (a namespace inside the database, not your `contract.prisma` file). `.create(...)` returns the inserted row, database defaults included. +- `contract.json` is imported with `with { type: "json" }`. Wrangler bundles it into the Worker. The client does not take this JSON directly, so pass it through `contractFromJson` first. The handler does this once, outside `fetch`, and passes the result as `contract` to the client it creates in each request. +- Do not export `contract` from `src/index.ts`. Cloudflare treats every named export of the Worker's entry file as an entrypoint. If another file needs `contract`, create it in its own file and import it from there in both places. ### 3.1. Generate the Env types @@ -299,7 +300,29 @@ Create the client inside `fetch` and close it with `ctx.waitUntil(db.close())`, :::info -`@prisma/orm-postgres/serverless` exports `postgresServerless`, a client built for serverless runtimes. It runs in Workers with `nodejs_compat`. Create it once at module level, as `const postgres = postgresServerless({ contract })`, from the same `contract` value. Several serverless clients, for example one with a `cursor` option and one without, share that one value. It holds no database connection. Inside `fetch`, open a connection for the request with `await using db = await postgres.connect({ url: env.DATABASE_URL })`. The connection has `db.orm`, `db.sql`, and `db.transaction(...)`, like the client this guide creates, and `await using` closes it when the request scope ends, so await every query before then. `connect()` opens the connection before it returns, and rejects with `DRIVER.CONNECTION_FAILED` when the database cannot be reached. Reads load the whole result unless you create the serverless client with a `cursor` option, and reads through a cursor hang behind Hyperdrive. See [Serverless client on PostgreSQL](/orm/reference/transactions-and-runtime#serverless-client-on-postgresql). +You can also use `postgresServerless` from `@prisma/orm-postgres/serverless` in place of the `postgres(...)` client above. Both open one database connection per request, and both work in Workers with `nodejs_compat`. They differ in what you create inside `fetch`. With `postgres(...)`, you create a whole client in each request. With `postgresServerless`, you create the client once, outside `fetch`, and each request only opens a connection from it with `connect()`: + +```typescript title="src/index.ts" +import postgresServerless from "@prisma/orm-postgres/serverless"; +import { contractFromJson } from "@prisma/orm-postgres/static"; +import type { Contract } from "./prisma/contract.d"; +import contractJson from "./prisma/contract.json" with { type: "json" }; + +const contract = contractFromJson(contractJson); +const postgres = postgresServerless({ contract }); + +export default { + async fetch(_request, env): Promise { + await using db = await postgres.connect({ url: env.DATABASE_URL }); + const { total } = await db.orm.public.User.aggregate((a) => ({ total: a.count() })); + return new Response(`Number of users in the database: ${total}.\n`); + }, +} satisfies ExportedHandler; +``` + +The connection `db` has `db.orm`, `db.sql`, and `db.transaction(...)`, like the client this guide creates. `await using` closes the connection when `fetch` returns, so await every query before then. If the database cannot be reached, `connect()` rejects with the error code `DRIVER.CONNECTION_FAILED`. + +Reads load the whole result into memory unless you pass a `cursor` option. Leave `cursor` out behind Hyperdrive, because reads through a cursor hang there. See [Serverless client on PostgreSQL](/orm/reference/transactions-and-runtime#serverless-client-on-postgresql). ::: diff --git a/apps/docs/content/docs/guides/switch-to-prisma-orm/from-mongoose.mdx b/apps/docs/content/docs/guides/switch-to-prisma-orm/from-mongoose.mdx index d6c21539c51..50ffa2eece6 100644 --- a/apps/docs/content/docs/guides/switch-to-prisma-orm/from-mongoose.mdx +++ b/apps/docs/content/docs/guides/switch-to-prisma-orm/from-mongoose.mdx @@ -461,7 +461,7 @@ curl http://localhost:3000/users [{"profile":{"bio":"Writes about databases"},"id":"6aa2cd2ed97f8d7357dc9bd1","email":"alice@prisma.io","name":"Alice"},{"profile":null,"id":"6aa2cd2ed97f8d7357dc9bd2","email":"bob@prisma.io","name":"Bob"}] ``` -The same documents Mongoose returned, because both read the same collection. The id is under `id`, the field name in the contract, and `__v` is left out, because the contract does not declare it. Bob's document has no `profile`, and because `profile` is optional in the contract, Prisma ORM returns it as `null`. +These are the documents Mongoose returned, because both read the same collection, but the JSON differs in two ways. The id is under `id`, the field name in the contract, instead of `_id`, so update any client of your API that reads `_id`. And `__v` is left out, because the contract does not declare it. Bob's document has no `profile`, and because `profile` is optional in the contract, Prisma ORM returns it as `null`. ```bash curl http://localhost:3000/posts @@ -471,6 +471,8 @@ curl http://localhost:3000/posts [{"_id":"6aa2cd2ed97f8d7357dc9bd5","title":"Hello MongoDB","content":"First post","published":true,"author":{"_id":"6aa2cd2ed97f8d7357dc9bd1","email":"alice@prisma.io","name":"Alice","profile":{"bio":"Writes about databases"},"__v":0},"categories":[{"_id":"6aa2cd2ed97f8d7357dc9bd3","name":"Databases","__v":0}],"__v":0},{"_id":"6aa2cd2ed97f8d7357dc9bd7","title":"Bob's post","content":"Hi","published":true,"author":{"_id":"6aa2cd2ed97f8d7357dc9bd2","email":"bob@prisma.io","name":"Bob","__v":0},"categories":[],"__v":0}] ``` +This route still returns `_id` and `__v`, unlike `GET /users`. It reads through the pipeline builder, which returns documents as MongoDB stores them. The `__v` keys go away when you remove them from the documents in step 4.2. + ```bash curl -X POST http://localhost:3000/users \ -H "content-type: application/json" \ @@ -587,7 +589,7 @@ The ORM query API uses the field names in the contract, not the names in `@map`. :::warning[ObjectId filters in the pipeline builder] -In the pipeline builder, `.match((f) => f._id.eq(id))` sends the id as a plain string and matches nothing. Filter ObjectId fields through the ORM, `db.orm.posts.where({ id })` or `db.orm.posts.where(MongoFieldFilter.eq("id", id))`, which converts the id for you. In a pipeline, pass an `ObjectId` instead, as the [`match()` reference](/orm/reference/pipeline-builder#match) shows. +In the pipeline builder, `.match((f) => f._id.eq(id))` sends the id as a plain string and matches nothing. Filter ObjectId fields through the ORM, `db.orm.posts.where({ id })` or `db.orm.posts.where(MongoFieldFilter.eq("id", id))`, which converts the id for you. Import `MongoFieldFilter` from `@prisma/orm-mongo/query-ast/execution`. In a pipeline, pass an `ObjectId` from the `mongodb` package instead, as the [`match()` reference](/orm/reference/pipeline-builder#match) shows. ::: diff --git a/apps/docs/content/docs/guides/upgrade-prisma-orm/postgresql.mdx b/apps/docs/content/docs/guides/upgrade-prisma-orm/postgresql.mdx index a4ef8068241..eb8a56b1e6b 100644 --- a/apps/docs/content/docs/guides/upgrade-prisma-orm/postgresql.mdx +++ b/apps/docs/content/docs/guides/upgrade-prisma-orm/postgresql.mdx @@ -10,11 +10,11 @@ This guide is for teams running a Prisma ORM 7 application on PostgreSQL who wan Both versions run against the same PostgreSQL database the whole time. The database, its data, and its connection string do not change; only application code and tooling do. Because each route stays on Prisma ORM 7 until you deliberately move it, the application remains shippable at every point in the migration. -The guide covers **PostgreSQL only**. Guidance for other databases will follow. If you are coming from v6 on MongoDB, see the [MongoDB guide](/guides/upgrade-prisma-orm/mongodb). +This guide covers **PostgreSQL only**, and guidance for other databases will follow. If you are coming from v6 on MongoDB, see the [MongoDB guide](/guides/upgrade-prisma-orm/mongodb). :::info -Step 2.1 installs the `latest` version of both Prisma ORM 8 packages: `prisma`, the Prisma ORM 8 CLI, and `@prisma/orm-postgres`. This guide was written with `prisma` as of `8.0.0-rc.21` and `@prisma/orm-postgres` as of `8.0.0-rc.19`: the CLI is released separately, so the two numbers differ. Prisma ORM 7 stays at `7.10.1`: the example app in step 1.1 starts on it, and step 1.2 installs `@prisma/prisma7@7.10.1`. +Step 2.1 installs the latest Prisma ORM 8 packages. This guide was tested with `prisma` (the Prisma ORM 8 CLI) `8.0.0-rc.24` and `@prisma/orm-postgres` `8.0.0-rc.19`. The two packages have separate version numbers. The Prisma ORM 7 side of the app stays at `7.10.1` throughout. ::: diff --git a/apps/docs/content/docs/orm/coming-from-prisma-orm-7.mdx b/apps/docs/content/docs/orm/coming-from-prisma-orm-7.mdx index c0169bc5f96..4c2bcb2cdd9 100644 --- a/apps/docs/content/docs/orm/coming-from-prisma-orm-7.mdx +++ b/apps/docs/content/docs/orm/coming-from-prisma-orm-7.mdx @@ -61,10 +61,10 @@ The biggest visible change is in field types. Where Prisma ORM 7 wrote a Prisma | `@default(dbgenerated("gen_random_uuid()"))` | ``@default(sql`gen_random_uuid()`)`` | `dbgenerated` is rejected. Write any SQL expression as `` sql`...` `` instead, but keep writing `now()` and `autoincrement()` as plain functions. [Default values](/orm/contract-authoring/psl-syntax#default-values) covers every kind of default | | `Json` | `Jsonb` | Prisma ORM 7's `Json` stored a PostgreSQL `jsonb` column, so write `Jsonb`. In Prisma ORM 8, `Json` means PostgreSQL's older `json` type, which you almost certainly do not want | | `Json @default("{}")` | ``Jsonb @default(json`{}`)`` | write a JSON default as `` json`...` ``. A quoted string is text, and a JSON column does not accept it | -| `DateTime @default("2024-01-01T00:00:00Z")`, `Bytes @default("aGVsbG8=")` | ``DateTime @default(timestamptz`2024-01-01T00:00:00Z`)``, ``Bytes @default(bytea`\x68656c6c6f`)`` | a fixed date, time, or bytes default is a tagged literal named after the column's type. A `bytea` default holds hexadecimal after `\x`, not base64 | +| `DateTime @default("2024-01-01T00:00:00Z")`, `Bytes @default("aGVsbG8=")` | ``DateTime @default(timestamptz`2024-01-01T00:00:00Z`)``, ``Bytes @default(bytea`\x68656c6c6f`)`` | write a fixed date, time, or bytes default as the column's type name followed by the value in backticks. A `bytea` default holds hexadecimal after `\x`, not base64 | | `enum Role { ADMIN USER }`, in a new database | `enum Role { ADMIN USER }` | same syntax. You can give members explicit values (`ADMIN = "admin"`). The column is stored as text, not as a PostgreSQL enum type | | `enum Role { ADMIN USER }` with a field `role Role`, in an existing Prisma ORM 7 database | `native_enum Role { ADMIN = "ADMIN" USER = "USER" }` with the field `role pg.enum(Role)` | your database already has a PostgreSQL enum type for it, and `native_enum` keeps it. See the note below the table | -| `String[]` | `String[]`, or `String?[]` | Prisma ORM 8 adds a check that rejects `NULL` elements in a `String[]` column. Write `String?[]` to allow them, for example when a column Prisma ORM 7 created already holds them. The list filters (`has`, `hasEvery`, `hasSome`, `isEmpty`) are not available; filter on a list with raw SQL for now (see below) | +| `String[]` | `String[]`, or `String?[]` | for a `String[]` column, Prisma ORM 8 creates a database check constraint that rejects `NULL` elements. Write `String?[]` to allow them, for example when a column Prisma ORM 7 created already holds them. The list filters (`has`, `hasEvery`, `hasSome`, `isEmpty`) are not available; filter on a list with raw SQL for now (see below) | | `@@schema("billing")` | `namespace billing { model ... }` | a `namespace` block is a PostgreSQL schema; wrap the models in it. They become `db.orm.billing.Invoice` and so on | | implicit many-to-many (`Post[]` on `Tag` and `Tag[]` on `Post`, with no model for the join table) | the same two list fields, plus a model for the join table | you write the join table as a model yourself. Example below | | `@id`, `@unique`, `@default`, `@relation`, `@map`, `@@map`, `@@index`, `@@id`, `@@unique` | unchanged | a model without `@@map` names its table after the model, exactly as written, as in Prisma ORM 7 | @@ -201,7 +201,7 @@ The rest are in the [CLI reference](/cli). ## `src/prisma/db.ts` replaces `PrismaClient` -There is no generated `PrismaClient`: you create the client once, in `src/prisma/db.ts`, from the two files `prisma contract emit` wrote: +Prisma ORM 8 does not generate a `PrismaClient`, so you create the client yourself, once, in `src/prisma/db.ts`, from `contract.json` and `contract.d.ts`, which `prisma contract emit` wrote: ```ts title="src/prisma/db.ts" import "dotenv/config"; @@ -225,7 +225,7 @@ export const db = postgres({ - `db.orm`: your models, by model name (`db.orm.public.User`). - `db.sql`: the [SQL query builder](/orm/reference/sql-query-builder). It holds your tables by table name, as in `db.sql.public.User`, and a table's name is its model's name unless you set `@@map`. On this page, the raw SQL examples use it only to give each column's type. - `db.raw.sql`: lets you write a SQL query as text. -- `db.query(plan)` and `db.execute(plan)`: run a built query, such as a raw query. `db.toSql(plan)` shows the SQL it would send. +- `db.query(query)` and `db.execute(query)`: run a query that you built and finished with `.build()`, such as the raw queries below. `db.toSql(query)` shows the SQL it would send. - `db.transaction`: runs several queries in one transaction. To add middleware, add a `middleware: [...]` key to the `postgres({ ... })` call above. A middleware is a plain object with a name and one or more hook functions, for example `{ name: "log", async beforeQuery(plan) { console.log(plan.sql); } }`. [How middleware works](/orm/middleware/how-middleware-works) lists the hooks. @@ -329,7 +329,7 @@ await db.execute(update); The table is `User`, because a model without `@@map` names its table after the model. As in Prisma ORM 7, a value you put in the SQL with `${...}`, such as `${id}`, is sent to the database as a query parameter. -Every raw query that returns rows needs `.returnsRow(...)` with a type per column, and you take the type from the table, as above. A computed column has no table column to take it from, so write the type name as a string instead: for `SELECT count(*) AS total FROM "User"`, write `.returnsRow({ total: "pg/int8@1" })`. [Raw queries](/orm/reference/raw-queries) lists the type names. Finish the query with `.build()` and pass it to `db.query()` for rows, or to `db.execute()` for the number of rows a write changed. On PostgreSQL, `db.runtime()` returns the connection directly, so it needs no `await`. +Every raw query that returns rows needs `.returnsRow(...)` with a type per column, and you take the type from the table, as above. A computed column has no table column to take it from, so write the type name as a string instead: for `SELECT count(*) AS total FROM "User"`, write `.returnsRow({ total: "pg/int8@1" })`. Here `int8` is the PostgreSQL column type, and every type name ends in `@1` today. [Raw queries](/orm/reference/raw-queries) lists the type names. Finish the query with `.build()` and pass it to `db.query()` for rows, or to `db.execute()` for the number of rows a write changed. ### Transaction diff --git a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx index cbcd823fd42..2f4cede1d97 100644 --- a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx @@ -181,13 +181,13 @@ model Post { } ``` -Each function makes a value for the field's type: `uuid()` works on `String`, `VarChar(n)`, `Char(n)`, and `Uuid`, and `now()` works on every PostgreSQL date and time type, `Date`, `Time`, and `Timetz` included. The database does not fill these columns, so a row written by raw SQL or another application gets no value. A field may have both a database `@default` and a generator: `createdAt DateTime @default(now()) @onCreate(now())` takes its time from your application when Prisma ORM creates the row, and from the database for other inserts. On an optional field, the generator fills the field when you leave it out, and an explicit `null` is stored as `null`. +Each function makes a value for the field's type: `uuid()` works on `String`, `VarChar(n)`, `Char(n)`, and `Uuid`, and `now()` works on every PostgreSQL date and time type, `Date`, `Time`, and `Timetz` included. The database does not fill these columns, so a row written by raw SQL or another application gets no value. A field may have both a database `@default` and an `@onCreate(...)`: `createdAt DateTime @default(now()) @onCreate(now())` takes its time from your application when Prisma ORM creates the row, and from the database for other inserts. On an optional field, `@onCreate(...)` fills the field when you leave it out, and an explicit `null` is stored as `null`. -`@default(uuid())` and the other generator functions in `@default` are refused with `PSL_GENERATOR_IN_DEFAULT`, which names the `@onCreate(...)` to write. `@default(now())` and `@default(autoincrement())` stay: they are database defaults. +`@default(uuid())` and the other functions that generate a value are refused in `@default` with `PSL_GENERATOR_IN_DEFAULT`, which names the `@onCreate(...)` to write. `@default(now())` and `@default(autoincrement())` stay: they are database defaults. -An argument a function does not take, such as `uuid(5)`, `cuid(1)`, or `nanoid(300)`, is refused with `PSL_INVALID_GENERATOR_ARGUMENT`, and `cuid()` needs its version, `cuid(2)`. An argument written with a fraction, such as `nanoid(8.0)`, or in quotes, is refused with `PSL_VALUE_TYPE_INCOMPATIBLE`. A generator must also fit its column: `uuid()` writes 36 characters, `ksuid()` 27, `ulid()` 26, `cuid(2)` 24, `nanoid()` 21, and `nanoid(n)` `n`, so `VarChar(5) @onCreate(uuid())` is refused when you run `contract emit`. A bare `Char` holds one character, and a bare `VarChar` has no limit. +An argument a function does not take, such as `uuid(5)`, `cuid(1)`, or `nanoid(300)`, is refused with `PSL_INVALID_GENERATOR_ARGUMENT`, and `cuid()` needs its version, `cuid(2)`. An argument written with a fraction, such as `nanoid(8.0)`, or in quotes, is refused with `PSL_VALUE_TYPE_INCOMPATIBLE`. The value a function writes must also fit its column: `uuid()` writes 36 characters, `ksuid()` 27, `ulid()` 26, `cuid(2)` 24, `nanoid()` 21, and `nanoid(n)` `n`, so `VarChar(5) @onCreate(uuid())` is refused when you run `contract emit`. A bare `Char` holds one character, and a bare `VarChar` has no limit. -`@updatedAt` is gone. Write `@onCreate(now()) @onUpdate(now())`, as `updatedAt` above does, or `temporal.updatedAt()` where the field's type would go: +Prisma ORM 8 has no `@updatedAt`, so write `@onCreate(now()) @onUpdate(now())`, as `updatedAt` above does, or `temporal.updatedAt()` where the field's type would go: ```prisma model Post { @@ -195,11 +195,15 @@ model Post { } ``` -Prisma ORM sets the field to the current time on every create and update. `temporal` is a built-in group of timestamp field types, such as `temporal.createdAt()` and `temporal.updatedAt()`, so you do not import it or declare it. On PostgreSQL the column is `timestamptz`, and you can still set the field yourself on a write. `temporal.createdAt()` sets the field once, when the row is created. Both take the time from your application's clock, not from the database, so the column has no database default, and a row you insert with raw SQL must supply the value. For a value the database generates, use `DateTime @default(now())` instead. `temporal.createdAtJsDate()` and `temporal.updatedAtJsDate()` do the same with a JavaScript `Date` in place of a `Temporal` value. `temporal.createdAt()` and `temporal.updatedAt()` need a global `Temporal` for writes as well as reads, so when your runtime has none, install `temporal-polyfill` and add `import "temporal-polyfill/full/global"` to your app's entry file. +Prisma ORM sets the field to the current time on every create and update, and you can still set it yourself on a write. `temporal.createdAt()` sets its field once, when the row is created. You do not import or declare `temporal`: it is a group of timestamp field types built into the contract syntax. On PostgreSQL the column is `timestamptz`. + +Both take the time from your application's clock, not from the database, so the column has no database default, and a row you insert with raw SQL must supply the value. For a value the database generates, use `DateTime @default(now())` instead. + +`temporal.createdAt()` and `temporal.updatedAt()` need a global `Temporal` for writes as well as reads. When your Node.js version has none, install `temporal-polyfill` and add `import "temporal-polyfill/full/global"` at the top of `src/prisma/db.ts`. `temporal.createdAtJsDate()` and `temporal.updatedAtJsDate()` work the same way, but with a JavaScript `Date` in place of a `Temporal` value. ### Columns of any database type -For a column of a database type no field type covers, write ``Raw(sql`...`)`` with the type exactly as the database prints it: +For a column of a database type no field type covers, write ``Raw(sql`...`)`` with the type written the way the database reports it. To see that text for a column that already exists, run [`prisma db schema`](/cli/db-schema): ```prisma model Category { @@ -209,7 +213,7 @@ model Category { } ``` -Migrations create the column with that text, and [`db verify`](/cli/db-verify) compares it with the type the database reports, so write `integer`, not `int4`, and quote a name that needs it, as in ``Raw(sql`"MyType"`)``. Your code reads and writes the value as `unknown`, and a default is a `sql` tagged literal. `Raw("ltree")`, without the `sql` tag, is refused. `Raw` works on PostgreSQL and SQLite. +Migrations create the column with that text, and [`db verify`](/cli/db-verify) compares it with the type the database reports, so write `integer`, not `int4`, and quote a name that needs it, as in ``Raw(sql`"MyType"`)``. Your code reads and writes the value as `unknown`, and a default is a `sql` tagged literal. `Raw("ltree")`, without the `sql` tag, is refused. `Raw` works on PostgreSQL and on SQLite, which Prisma ORM 8 supports as an [experimental package](/orm/supported-databases). ### Default values @@ -233,13 +237,12 @@ model Account { ``` - A JSON default is a `json` tagged literal: `json` followed by the JSON document in backticks. Write a JSON array the same way, as `tags` shows. -- A decimal default is a bare number. `contract emit` stores it with the scale of its column, so `@default(1.5)` on a `Numeric(10, 2)` field is stored as `"1.50"`. +- A decimal default is a bare number. - `NaN`, `Infinity`, and `-Infinity` are written without quotes, and so are `true` and `false`. - A text default is a quoted string. - A fixed date, time, or interval is a tagged literal named after the column's PostgreSQL type: `timestamptz` for `DateTime` and `Timestamptz`, and `timestamp`, `date`, `time`, `timetz`, and `interval` for the types of those names, as in ``@default(interval`P1D`)``. A `timestamptz` default is a point in time with a UTC offset, such as ``timestamptz`2024-01-01T00:00:00Z` ``, and a `timestamp` default has no offset, such as ``timestamp`2024-01-01 00:00:00` ``. A quoted string on one of these fields is refused, and the message shows the tagged literal to write. - A `Bytes` default is a `bytea` tagged literal holding the bytes as hexadecimal after `\x`, as `payload` shows. Base64 text is not accepted. - On SQLite, a `DateTime` default uses the `datetime` tag, as in ``@default(datetime`2024-01-01T00:00:00Z`)``, and a `Bytes` default the `blob` tag, which holds hexadecimal digits, as in ``@default(blob`68656c6c6f`)``. -- `contract emit` stores a fixed date or time default in one canonical text for each value, however you write it. ``@default(timestamptz`2024-01-01T01:00:00+01:00`)`` on a `DateTime` field is stored as `"2024-01-01T00:00:00Z"`, the same instant in UTC, and ``@default(timestamp`2024-01-01 12:34:56.500`)`` on a `Timestamp` field is stored as `"2024-01-01T12:34:56.5"`. `contract infer` and `contract print` write date and time defaults in the same form. - Each default, and each member of an `enum`, is checked by the column's type, so a value the column cannot hold unchanged is refused: more characters than a `VarChar(n)` or `Char(n)` takes, a number a `Numeric(p, s)` would round, more digits of a second than a `Timestamptz(3)` keeps, an integer outside the range of `Int` or `SmallInt`, or text that is not a UUID on a `Uuid` field. - A date or time default that the field's type does not hold makes `contract emit` fail with `PSL_INVALID_DEFAULT_LITERAL`, and the message shows a text the field takes. That includes an offset on a `Timestamp`, `Date`, or `Time` default, a missing offset on a `DateTime`, `Timestamptz`, or `Timetz` default, more than six digits after the decimal point (three on SQLite), and a date or time that does not exist, such as `2024-02-30`. The CLI checks these defaults on Node.js 24 too, which has no built-in `Temporal`. - On a field typed with an `enum` block, the default is the member's name without quotes, as in `@default(Low)`. On a `pg.enum(...)` field, it is the value the member stores, in quotes, as in `@default("admin")`. [Enums](#enums) covers both. @@ -358,7 +361,9 @@ enum Priority { In `pg/text@1`, `pg` is PostgreSQL, `text` is the column type, and `@1` is the version of how the value is stored and read. Write `@@type("pg/int4@1")` to store the values as integers instead. When a member has no explicit value, the member name itself is stored. -Each member's value is a value of the enum's type, written as a `@default` of that type is: a quoted string for text, a bare number for a number type, and a tagged literal for a date or time. A quoted member on a number type, such as `Half = "0.5"` under `@@type("pg/numeric@1")`, is refused, and the message shows what to write, here `Half = 0.5`. Members are compared as values of the type, so on `pg/numeric@1` the members `0.5` and `0.50` are the same value and are refused together. An enum cannot use `pg/jsonb@1`, `pg/timetz@1`, `pg/enum@1`, `pg/vector@1`, or `pg/geometry@1`: use a text enum, or a `native_enum` block in place of `pg/enum@1`. +Each member's value is a value of the enum's type, written as a `@default` of that type is: a quoted string for text, a bare number for a number type, and a tagged literal for a date or time. A quoted member on a number type, such as `Half = "0.5"` under `@@type("pg/numeric@1")`, is refused, and the message shows what to write, here `Half = 0.5`. Two members may not store the same value. Prisma ORM compares the values, not the text you wrote, so on `pg/numeric@1` the members `0.5` and `0.50` store the same value and are refused. + +Some types cannot hold enum members, so JSON (`pg/jsonb@1`), time with a time zone (`pg/timetz@1`), vectors (`pg/vector@1`), and geometries (`pg/geometry@1`) are refused in `@@type`: use a text enum instead. For a PostgreSQL `enum` type, use a `native_enum` block, not `@@type("pg/enum@1")`. `@@type` is optional, and when you leave it out Prisma ORM picks the type from the member values: bare member names and string values give the database's text type, and integer values give its integer type. Give every member the same kind of value, because a mix of string and integer values throws an error whose `code` is `PSL_ENUM_CANNOT_INFER_TYPE`. A `@default` names the member, as in `priority Priority @default(Low)`, even where the member stores a different value. @@ -375,7 +380,7 @@ model User { } ``` -Each member needs a value. `pg` comes with `@prisma/orm-postgres`, so there is nothing to import. You do not create the PostgreSQL type yourself: `npx prisma migration plan` includes the `CREATE TYPE`. A field can name a `native_enum` declared in another [namespace](#namespaces) by putting the namespace first, as in `pg.enum(auth.OrderStatus)`. +Unlike an `enum` block, a `native_enum` block needs a value for every member. `pg` comes with `@prisma/orm-postgres`, so there is nothing to import. You do not create the PostgreSQL type yourself: `npx prisma migration plan` includes the `CREATE TYPE`. A field can name a `native_enum` declared in another [namespace](#namespaces) by putting the namespace first, as in `pg.enum(auth.OrderStatus)`. ## Value objects @@ -572,7 +577,7 @@ model Bug { } ``` -Field and model names are written bare, as `type` and `Task` are, while table names and discriminator values are quoted, as `"task"` and `"bug"` are. Rows whose `type` column holds `"bug"` are `Bug` records. A variant reuses its base model's fields, so a `Bug` has `id`, `title`, and `type` as well as `severity` and `stepsToRepro`. The base model can be declared before or after its variants. A variant does not declare an `@id` of its own: it takes the base model's primary key. On PostgreSQL and SQLite the discriminator field must hold text: `String`, or on PostgreSQL `Char(n)` or `VarChar(n)`, and on SQLite `sql.String(n)`. +Field and model names are written bare, as `type` and `Task` are, while table names and discriminator values are quoted, as `"task"` and `"bug"` are. Rows whose `type` column holds `"bug"` are `Bug` records. A variant reuses its base model's fields, so a `Bug` has `id`, `title`, and `type` as well as `severity` and `stepsToRepro`. The base model can be declared before or after its variants. A variant does not declare an `@id` of its own: it takes the base model's primary key. On PostgreSQL and SQLite the discriminator field must hold text: `String`, or on PostgreSQL `Char(n)` or `VarChar(n)`, and on SQLite `sql.String(n)`, SQLite's text type with a length, the counterpart of `VarChar(n)` on PostgreSQL. You query a variant through the base model: `db.orm.public.Task.variant('bug')` returns the `Bug` rows only, and `public` here is the PostgreSQL schema. `variant()` takes the discriminator value from `@@base`, not the model name. Writing a row of a variant starts with the same `variant('bug')` call. See [`variant()`](/orm/reference/orm-client#variant). diff --git a/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx b/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx index ac464a8bed6..2e1999d4e04 100644 --- a/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx @@ -133,7 +133,13 @@ export const db = postgres({ }); ``` -The connection string comes from the `DATABASE_URL` environment variable: `prisma orm init` writes `.env.example`, and you put your `DATABASE_URL` in `.env`. Keep the `.d` in the import path: it names `contract.d.ts`, so the import still finds the generated type when you author the contract in TypeScript and a `contract.ts` sits beside it. The `tsconfig.json` settings the JSON import needs are on the [transactions and runtime page](/orm/reference/transactions-and-runtime). Before its first query, `db` reads the record in the database of which contract it matches and compares it with the contract's hashes. If they do not match, it logs a warning whose `code` is `CONTRACT.MARKER_MISMATCH` and runs the query anyway. If there is no record at all, because nothing has signed the database yet, it logs a warning whose `code` is `CONTRACT.MARKER_MISSING` and again runs the query. On MongoDB the client is `mongo({ contract, url })` from `@prisma/orm-mongo/runtime`, with `contractFromJson` from `@prisma/orm-mongo/static`, described under [`mongo(options)`](/orm/reference/transactions-and-runtime#mongooptions). +`db` reads the connection string from the `DATABASE_URL` environment variable. `prisma orm init` always writes `.env.example`, and it also writes `.env` when you pass `--write-env` or answer yes when it asks. Put your `DATABASE_URL` in `.env`. The `tsconfig.json` settings the JSON import needs are on the [transactions and runtime page](/orm/reference/transactions-and-runtime). + +Keep the `.d` in the import path. It names `contract.d.ts`, so the import cannot pick up a `contract.ts` in the same folder. That matters when you write the contract in TypeScript and the config sets no `output`, because `contract emit` then writes its files next to `contract.ts`. + +Before its first query, `db` reads a record in the database that says which contract the database matches. Error codes call this record the marker. `db` compares it with the contract's hashes, and if they do not match, it logs a warning whose `code` is `CONTRACT.MARKER_MISMATCH` and runs the query anyway. If there is no record at all, because nothing has signed the database yet, it logs a warning whose `code` is `CONTRACT.MARKER_MISSING` and again runs the query. {/* plain-language:defined */} + +On MongoDB the client is `mongo({ contract, url })` from `@prisma/orm-mongo/runtime`, with `contractFromJson` from `@prisma/orm-mongo/static`, described under [`mongo(options)`](/orm/reference/transactions-and-runtime#mongooptions). When `CONTRACT.MARKER_MISMATCH` shows up in production, apply the pending migration, or run `npx prisma db sign` when the database already matches the contract. The client warns by default, and you can set [`verifyMarker`](/orm/reference/transactions-and-runtime#postgresoptions) to `false` to skip the check. It cannot be made to fail, so run `prisma db verify` when you need a check that fails. diff --git a/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx b/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx index 0ab0ae78494..8ad2841fddb 100644 --- a/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx @@ -136,9 +136,9 @@ The options object takes four more keys, all optional: | `defaultControlPolicy: "managed"` | The [control policy](#control-policy) for every model that does not set its own. | | `namespaces: ["audit"]` | The PostgreSQL schemas other than `public` that models may use; see [Namespaces](#namespaces). | -Take `field`, `model`, `rel`, `type`, and `gen` from the function's one argument, and import `defineContract`, `enumType`, and `member` from the package. The package also exports `model`, `rel`, `field`, and `gen` for use outside the function, without the helpers of extension packs. +Take `field`, `model`, `rel`, `type`, and `gen` from the function's one argument, because those include the helpers your extension packs add, and import `defineContract`, `enumType`, and `member` from the package. The package also exports `model`, `rel`, `field`, and `gen`. Use those only in code outside the function, and expect them to lack the extension packs' helpers. -On MongoDB, `defineContract` also accepts a single object holding `models`, as above, and you import `field`, `model`, and `rel` from the package instead. The MongoDB builder differs from the PostgreSQL one in six ways: +On MongoDB, `defineContract` also accepts a single object holding `models`, as above, and you import `field`, `model`, and `rel` from the package instead. The MongoDB builder differs from the PostgreSQL one in how it handles the primary key, field helpers, collection names, relations, indexes, and validators: - The field stored as `_id` is always the primary key, so you never mark it with `.id()` and there is no `.attributes(...)` on MongoDB. Declare it as `id: field.objectId().mongo({ field: "_id" })` to name it `id` in your code, as `id ObjectId @id @map("_id")` does in PSL, or as `_id: field.objectId()` to keep the name `_id`. `.mongo({ field })` sets the name a field is stored under in its documents, for any field of a model. A model without an `_id` field gets one from MongoDB, an ObjectId, on every insert. - The scalar helpers are `field.objectId()`, `field.string()`, `field.int32()`, `field.double()`, `field.bool()`, and `field.date()`. @@ -200,7 +200,7 @@ The examples below use the PostgreSQL builder. There is no helper for a date without a time, or a time without a date. For a date-only column, name the type yourself: `field.column({ codecId: "pg/date-temporal@1" } as const)`. `field.column(...)` and the chained `.column(...)` below are different calls: `field.column(descriptor)` builds a field from a type description, and `.column(name)` on an existing field sets its column name. Extension packs add more helpers of their own. -For a column of a database type no helper covers, use ``type.Raw(sql`...`)`` from the function's argument, with the type exactly as the database prints it, as in ``path: field.column(type.Raw(sql`ltree`))``, with `sql` imported from the package. It works as [``Raw(sql`...`)`` in PSL](/orm/contract-authoring/psl-syntax#columns-of-any-database-type) does: your code reads and writes the value as `unknown`, and a default is a `sql` expression. +For a column of a database type no helper covers, use ``type.Raw(sql`...`)`` from the function's argument, with the type written the way the database reports it, as in ``path: field.column(type.Raw(sql`ltree`))``, with `sql` imported from the package. It works as [``Raw(sql`...`)`` in PSL](/orm/contract-authoring/psl-syntax#columns-of-any-database-type) does: your code reads and writes the value as `unknown`, and a default is a `sql` expression. For a primary key your app generates, use a `field.id.*` helper. Each one marks the field as the primary key and generates the ID in your app when you create a row. `field.id.uuidv4String()` is the common choice, and the others are `field.id.uuidv7String()`, `field.id.ulid()`, `field.id.nanoid()`, `field.id.cuid2()`, and `field.id.ksuid()`. To store a UUID in a `uuid` column instead of a `character(36)` column, use `field.id.uuidv4Native()` or `field.id.uuidv7Native()`. @@ -216,7 +216,7 @@ const Post = model("Post", { }); ``` -A field may have both: `field.dateTime().default(now()).onCreate(gen.now())` takes its time from your app when Prisma ORM creates the row, and from the database for other inserts. `field.generated(...)` still works, but `.onCreate(...)` replaces it. +A field may have both: `field.dateTime().default(now()).onCreate(gen.now())` takes its time from your app when Prisma ORM creates the row, and from the database for other inserts. For a key the database generates, chain `.default(...)` and `.id()` on a field. An integer that counts up is `field.int().default(autoincrement()).id()`, and a UUID is ``field.uuidNative().default(sql`gen_random_uuid()`).id()``. Import `autoincrement` and `sql` from `@prisma/orm-postgres/contract-builder`. @@ -229,13 +229,26 @@ For a key the database generates, chain `.default(...)` and `.id()` on a field. Every field builder supports chained modifiers: - `.optional()` makes the field nullable. -- `.default(value)` sets a fixed default. A decimal default is a string, as in `.default("1.50")`, and a JSON default is the object or array itself, as in `.default({ plan: "free" })`. The value has the type your code writes to that field, because the field's codec encodes it: a `field.dateTime()` default is a `Temporal.Instant`, as in `.default(Temporal.Instant.from("2024-01-01T00:00:00Z"))`, and a `field.bigint()` default is a `bigint`, as in `.default(1n)`. To write the time as a string, use `field.temporal.timestamptzString().default("2024-01-01T00:00:00Z")`. A date or time default is stored in the same canonical form as in a [PSL contract](/orm/contract-authoring/psl-syntax#default-values): `.default(new Date("2024-01-01T00:00:00Z"))` on `field.temporal.timestamptzJsDate()` is stored as `"2024-01-01T00:00:00Z"`, and a `Temporal` default with digits below one microsecond is refused. A value of the wrong type is a type error, and a value the codec refuses makes `contract emit` fail with [`CONTRACT.DEFAULT_INVALID`](/orm/reference/error-reference#CONTRACT.DEFAULT_INVALID). Do not write `.default(null)`: an `.optional()` field without a default is already `NULL` in the database when you leave it out. +- `.default(value)` sets a fixed default, written as a value of the type your code writes to the field. [Fixed defaults](#fixed-defaults) below shows each kind. - `.default(...)` also takes a default the database computes: `.default(now())`, `.default(autoincrement())`, or any other SQL expression as a `sql` template, such as ``.default(sql`gen_random_uuid()`)``. Import `now`, `autoincrement`, and `sql` from `@prisma/orm-postgres/contract-builder`. `` sql`now()` `` and `` sql`autoincrement()` `` are refused, so use the named helpers. `.defaultSql(expression)` still works but is deprecated, and it will be removed in the stable `8.0.0` release. - `.unique()` adds a unique constraint. `.id()` marks the primary key, for a key that is not generated, such as an integer you set yourself. - `.column("column_name")` sets the column name in the database when it differs from the field name. - `.many()` makes the field a list, stored as a PostgreSQL array column such as `text[]`. A list column gets a check constraint that rejects `NULL` elements. To allow `NULL` elements, write `.many({ elementsNullable: true })`: the field's TypeScript type becomes `ReadonlyArray`. Add `.optional()` to allow a `NULL` list as well. - `.onCreate(gen.uuid())` and `.onUpdate(gen.now())` generate a value in your app, as described above. +### Fixed defaults + +A fixed default passed to `.default(value)` has the type your code writes to the field: + +- A decimal default is a string: `.default("1.50")`. +- A JSON default is the object or array itself: `.default({ plan: "free" })`. +- A `field.dateTime()` default is a `Temporal.Instant`: `.default(Temporal.Instant.from("2024-01-01T00:00:00Z"))`. To write the time as a string instead, use `field.temporal.timestamptzString().default("2024-01-01T00:00:00Z")`. +- A `field.bigint()` default is a `bigint`: `.default(1n)`. + +A value of the wrong type is a type error in your editor. A value the column's type cannot hold makes `contract emit` fail with [`CONTRACT.DEFAULT_INVALID`](/orm/reference/error-reference#CONTRACT.DEFAULT_INVALID), and a `Temporal` default with digits below one microsecond is refused too. + +Do not write `.default(null)`, because when you leave out an `.optional()` field that has no default, the database already stores `NULL`. + ## Enums `enumType` declares an enum, the type its values are stored as, and its members, as `Priority` does in the full example above. diff --git a/apps/docs/content/docs/orm/data-modeling/index.mdx b/apps/docs/content/docs/orm/data-modeling/index.mdx index 2b59cb9157d..eaf78bdb098 100644 --- a/apps/docs/content/docs/orm/data-modeling/index.mdx +++ b/apps/docs/content/docs/orm/data-modeling/index.mdx @@ -108,7 +108,7 @@ model ApiToken { } ``` -A UUID takes more space than an integer, but independent services never produce the same one. `@onCreate(uuid())` generates a random UUID, and `@onCreate(uuid(7))` generates one that sorts by creation time. On MongoDB there is no choice to make, because the key is always the `ObjectId` shown above. +A UUID takes more space than an integer, but independent services never produce the same one. `@onCreate(uuid())` generates a random UUID, and `@onCreate(uuid(7))` generates one that sorts by creation time. Because the field is typed `String`, the id is stored in a `text` column. To store it in PostgreSQL's `uuid` column instead, type the field `Uuid`. On MongoDB there is no choice to make, because the key is always the `ObjectId` shown above. ### Composite keys diff --git a/apps/docs/content/docs/orm/data-modeling/mongodb.mdx b/apps/docs/content/docs/orm/data-modeling/mongodb.mdx index 77e8ecd2cca..44df4bce96d 100644 --- a/apps/docs/content/docs/orm/data-modeling/mongodb.mdx +++ b/apps/docs/content/docs/orm/data-modeling/mongodb.mdx @@ -21,7 +21,11 @@ model User { } ``` -`@map("_id")` stores the field as the document's `_id`, and your code calls it `id`, its name in the contract. So you write `user.id` and `where({ id })`, and the same goes for `select`, `orderBy`, and the values you write. Any other field with `@map` works the same way: `firstName String @map("first_name")` is `firstName` in your code and `first_name` in the document. Only the [pipeline builder](/orm/fundamentals/advanced-queries#mongodb-pipeline-builder) reads documents as they are stored, so it uses `_id`. `@@map("users")` names the collection and is optional: without it the collection takes the model name exactly as written, so `User`. `@unique` on `email` becomes a unique index when you migrate. The migration also gives each collection a `$jsonSchema` validator built from its model, from a PSL contract and a [TypeScript contract](/orm/contract-authoring/typescript-schema-builder) alike, so MongoDB refuses a document with a field the model does not declare, a missing required field, or a value of the wrong type. MongoDB does not check documents already stored when the validator is added. +`@map("_id")` stores the field as the document's `_id`, and your code calls it `id`, its name in the contract. So you write `user.id` and `where({ id })`, and the same goes for `select`, `orderBy`, and the values you write. Any other field with `@map` works the same way: `firstName String @map("first_name")` is `firstName` in your code and `first_name` in the document. Only the [pipeline builder](/orm/fundamentals/advanced-queries#mongodb-pipeline-builder) reads documents as they are stored, so it uses `_id`. + +`@@map("users")` names the collection and is optional: without it the collection takes the model name exactly as written, so `User`. + +`@unique` on `email` becomes a unique index when you migrate. The migration also makes MongoDB check every document you write against its model, so MongoDB refuses a document that has a field the model does not declare, lacks a required field, or holds a value of the wrong type. Documents that are already stored are not checked. Write `ObjectId` for the key type. That is the contract type only: in TypeScript the id is a plain string, so there is no `ObjectId` to construct. The old `String @db.ObjectId` and `@default(auto())` are no longer allowed. diff --git a/apps/docs/content/docs/orm/data-modeling/relational-databases.mdx b/apps/docs/content/docs/orm/data-modeling/relational-databases.mdx index ff3f2be6bd9..a47339d91e4 100644 --- a/apps/docs/content/docs/orm/data-modeling/relational-databases.mdx +++ b/apps/docs/content/docs/orm/data-modeling/relational-databases.mdx @@ -175,7 +175,7 @@ model Feature { } ``` -`@@discriminator(type)` marks the field that records the variant, and `@@base(Task, "bug")` declares that `Bug` is the `"bug"` variant of `Task`. Both take a field or model name without quotes, while the variant value is a string. The discriminator field holds text: `String`, `Char(n)`, or `VarChar(n)` on PostgreSQL, and `String` or `sql.String(n)` on SQLite. A variant has every field of its base plus the ones it declares, and it needs no `@id` of its own because it uses the base model's primary key. +`@@discriminator(type)` marks the field that records the variant, and `@@base(Task, "bug")` declares that `Bug` is the `"bug"` variant of `Task`. Both take a field or model name without quotes, while the variant value is a string. The discriminator field holds text: `String`, `Char(n)`, or `VarChar(n)` on PostgreSQL, and `String` or `sql.String(n)` on SQLite. `sql.String(n)` is SQLite's text type with a length, the counterpart of `VarChar(n)`. SQLite support is an [experimental package](/orm/supported-databases). A variant has every field of its base plus the ones it declares, and it needs no `@id` of its own because it uses the base model's primary key. Read and write variants through the base model, where `.variant(...)` limits the query to one variant. It takes the discriminator value, `"bug"`, not the model name `"Bug"`, and a value the model does not declare throws. On a create, the discriminator value is filled in for you: diff --git a/apps/docs/content/docs/orm/extensions/using-extensions.mdx b/apps/docs/content/docs/orm/extensions/using-extensions.mdx index f011ce4ff54..530af603098 100644 --- a/apps/docs/content/docs/orm/extensions/using-extensions.mdx +++ b/apps/docs/content/docs/orm/extensions/using-extensions.mdx @@ -113,7 +113,7 @@ To get the rows closest to a query vector on `db.orm`, use the method pgvector a const nearest = await db.orm.public.Post.using.embedding_nearest(queryVector).limit(10).all(); ``` -See [Package methods with `using`](/orm/reference/orm-client#package-methods-with-using). To get the distance itself in each row, write the search with the SQL query builder. In that block: +[Package methods with `using`](/orm/reference/orm-client#package-methods-with-using) covers these methods. To get the distance itself in each row, write the search with the SQL query builder, as in the block below, where: - `db.orm` is the ORM API, and under it you address a model by the name in your contract, so the model `Post` is `db.orm.public.Post`. - `db.sql` is the [SQL query builder](/orm/fundamentals/advanced-queries), and under it you address a table by the name it has in the database, so the same data is `db.sql.public.Post`. The two names are the same because the model sets no `@@map`. @@ -214,7 +214,15 @@ export const db = await supabase({ `supabase(...)` takes the same `extensions` array as `postgres(...)`, so pgvector or another extension goes there in the same way. -`await db.asUser(jwt)` takes the user's Supabase JWT and gives you a client that runs as that user. `db.asAnon()` gives you one that runs as the anonymous role, and `db.asServiceRole()` one that bypasses row-level security. `db` itself has no `orm` or `sql`: those live on the three role-bound clients. Each role-bound client has `orm`, `sql`, `raw`, `prepare`, `query`, `execute`, `toSql`, and `transaction()`, the same members a `postgres(...)` client uses to run queries. Pass your own collection classes to `supabase(...)` in the `collections` option, as you would to `postgres(...)`, and the role-bound clients use them on their `orm`. See [Custom `Collection` subclass](/orm/reference/orm-client#custom-collection-subclass). +You run queries through a client bound to a role, not through `db` itself, which has no `orm` or `sql`. `db` gives you three of them: + +- `await db.asUser(jwt)` takes the user's Supabase JWT and runs as that user. +- `db.asAnon()` runs as the anonymous role. +- `db.asServiceRole()` bypasses row-level security. + +Each role-bound client has the members a `postgres(...)` client uses to run queries: `orm`, `sql`, `raw`, `prepare`, `query`, `execute`, `toSql`, and `transaction()`. For example, `prepare` prepares a statement you run many times with different values, as [`db.prepare`](/orm/reference/transactions-and-runtime#dbpreparedeclaration-callback) describes. + +If you add your own methods to a model by subclassing `Collection`, as [Custom `Collection` subclass](/orm/reference/orm-client#custom-collection-subclass) shows, pass the subclass to `supabase(...)` in the `collections` option, as you would to `postgres(...)`. The role-bound clients then use it on their `orm`. `@prisma/orm-extension-supabase` has a contract of its own, which describes Supabase's `auth` and `storage` tables, check constraints included. Prisma read those tables from one Supabase version, supabase/postgres 17.6.1.106, and a Supabase project on another version can have a different set of check constraints. [`npx prisma db verify`](/cli/db-verify) compares your database with the extension's contract as well as with yours, and when a check constraint the extension describes is missing from your database, `db verify` lists it and fails. You cannot fix that with a migration, because Prisma ORM never changes Supabase's own tables. Instead, [open an issue on prisma/orm](https://github.com/prisma/orm/issues) with the output of `db verify` and your Supabase version, so the extension can be updated to match. diff --git a/apps/docs/content/docs/orm/fundamentals/reading-data.mdx b/apps/docs/content/docs/orm/fundamentals/reading-data.mdx index ff91299f078..99766b827f1 100644 --- a/apps/docs/content/docs/orm/fundamentals/reading-data.mdx +++ b/apps/docs/content/docs/orm/fundamentals/reading-data.mdx @@ -412,7 +412,7 @@ for await (const post of db.orm.public.Post.all()) { } ``` -It does not fetch less: on the standard client (`postgres({...})` in `src/prisma/db.ts`) every row is loaded first. For large tables page with `.limit()` and `.cursor()` (see [Sort and paginate](#sort-and-paginate)). A client created with the `cursor` option, such as `postgres({ contractJson, url, cursor: { batchSize: 100 } })`, reads the rows through a server-side cursor instead, in batches, as your loop asks for them. See [`postgres(options)`](/orm/reference/transactions-and-runtime#postgresoptions). +It does not fetch less: with the `db` that `postgres({...})` creates in `src/prisma/db.ts`, every row is loaded into memory first. For large tables page with `.limit()` and `.cursor()` (see [Sort and paginate](#sort-and-paginate)). A client created with the `cursor` option, such as `postgres({ contract, url, cursor: { batchSize: 100 } })`, reads the rows through a server-side cursor instead, in batches, as your loop asks for them. See [`postgres(options)`](/orm/reference/transactions-and-runtime#postgresoptions). The queries on this page also run on the serverless client from `@prisma/orm-postgres/serverless`, through the connection that its `connect({ url })` returns. Its reads load every row first too, unless you create it with `cursor`. See [Serverless client on PostgreSQL](/orm/reference/transactions-and-runtime#serverless-client-on-postgresql). diff --git a/apps/docs/content/docs/orm/fundamentals/transactions.mdx b/apps/docs/content/docs/orm/fundamentals/transactions.mdx index b3b3c68ea09..982d4afa807 100644 --- a/apps/docs/content/docs/orm/fundamentals/transactions.mdx +++ b/apps/docs/content/docs/orm/fundamentals/transactions.mdx @@ -12,7 +12,7 @@ Prisma ORM 8 runs on PostgreSQL, SQLite, and MongoDB, but there is no MySQL supp ## Run writes in a transaction -Use a transaction whenever one business operation spans more than one write: creating a user with their first records, moving a value between two rows, or deleting a parent after its children. Pass a callback to `db.transaction(...)`, and inside it, query through `tx` instead of `db`. `tx.orm` is the model API, what `prisma.user` was in Prisma ORM 7; `tx.sql` is the SQL query builder, and both work the same as `db.orm` and `db.sql`. `tx` also has `raw`, `prepare`, `query`, and `execute`, as `db` does. Every query you make on `tx` runs inside the transaction, and `db.transaction(...)` returns whatever your callback returns: +Use a transaction whenever one business operation spans more than one write: creating a user with their first records, moving a value between two rows, or deleting a parent after its children. Pass a callback to `db.transaction(...)`, and inside it, query through `tx` instead of `db`. Every query you make on `tx` runs inside the transaction, and `db.transaction(...)` returns whatever your callback returns: ```typescript import { db } from "./prisma/db"; @@ -23,6 +23,8 @@ const result = await db.transaction(async (tx) => { }); ``` +`tx` has the same query members as `db`. `tx.orm` is the model API, what `prisma.user` was in Prisma ORM 7, and `tx.sql` is the SQL query builder. `tx` also has `raw` for raw SQL, `query` and `execute` to run a query you built, and `prepare` for a statement you run many times with different values. + `db` is the client you create once in `src/prisma/db.ts`, and `prisma orm init` writes that file for you. It writes the file once; it is your code, so edit it freely. Under `tx.orm.public`, the property name is the model name, so `User` and `Post` are spelled exactly as you spelled them in your contract. `public` is the PostgreSQL schema, the namespace your tables live in, which is `public` unless you set one. `create(...)` takes the fields of the record directly, with no `data:` wrapper: pass a field if it is required and has no default, while everything else is optional, and [Write data](/orm/fundamentals/writing-data) has the full rules. You don't need a transaction for a single write; every mutation is already atomic on its own. @@ -168,6 +170,7 @@ export const db = mongo({ ``` Keep the `contract` value built from `contract.json` and the `Contract` type, and run `prisma contract emit` after every change to your contract, on PostgreSQL and on MongoDB alike. + `dbName` is now the only thing that picks the database, so set it to the database your driver code reads, and `client.db("app")` and Prisma ORM stay on the same one. An empty `dbName` is an error. ```typescript diff --git a/apps/docs/content/docs/orm/fundamentals/writing-data.mdx b/apps/docs/content/docs/orm/fundamentals/writing-data.mdx index 6380354a804..189218627c7 100644 --- a/apps/docs/content/docs/orm/fundamentals/writing-data.mdx +++ b/apps/docs/content/docs/orm/fundamentals/writing-data.mdx @@ -16,7 +16,7 @@ On PostgreSQL the path to a model is `db.orm..`, so the `User ## Example schema -Examples use this contract. In Prisma ORM 8 the schema file is `contract.prisma` instead of `schema.prisma`; the docs call it your contract. `@onCreate(cuid(2))` means Prisma ORM generates the `id` for you, so you never pass one: +The examples on this page use the contract below. In Prisma ORM 8 the schema file is `contract.prisma` instead of `schema.prisma`, and the docs call it your contract. `@onCreate(cuid(2))` means Prisma ORM generates the `id` for you, so you never pass one:
@@ -213,7 +213,7 @@ The callback returns an array, so you can apply several operations in one update There is no `increment` on PostgreSQL, so to add to a number in place, write the update as raw SQL. For a `views Int` column added to `Post`, `db.raw.sql` writes a raw statement, `.affectedCount()` says you want the row count back as `{ affectedRows }`, `.build()` finishes it, and `db.execute(...)` runs it; [Advanced queries](/orm/fundamentals/advanced-queries) explains raw SQL: ```typescript -const query = db.raw.sql`UPDATE post SET views = views + 1 WHERE id = ${postId}` +const query = db.raw.sql`UPDATE "Post" SET views = views + 1 WHERE id = ${postId}` .affectedCount() .build(); const { affectedRows } = await db.execute(query); diff --git a/apps/docs/content/docs/orm/middleware/authoring-custom-middleware.mdx b/apps/docs/content/docs/orm/middleware/authoring-custom-middleware.mdx index 6a079a0cb16..234bd5f3881 100644 --- a/apps/docs/content/docs/orm/middleware/authoring-custom-middleware.mdx +++ b/apps/docs/content/docs/orm/middleware/authoring-custom-middleware.mdx @@ -51,7 +51,7 @@ The `name` is the label Prisma ORM uses when it talks about your middleware, so If a call gives you back a count instead of rows, `afterQuery` is not called for it and `afterExecute` is called instead. On the ORM API the calls that return a count are `createAndCount`, `updateAndCount`, and `deleteAndCount`. Every other call returns rows and reaches `afterQuery`, including `create`, `update`, `delete`, `updateAll`, and `deleteAll`. -The SQL query builder is the other way to write a query on the same client, `db.sql.public.User...` in place of `db.orm.public.User...`. With it you build a query and run it in two steps: `.build()` gives you the query as a plain object, the plan, and a method on `db` runs it. `db.query(plan)` returns rows and calls `afterQuery`, while `db.execute(plan)` reports how many rows the statement changed and calls `afterExecute`. +The same split applies to the [SQL query builder](/orm/reference/sql-query-builder), `db.sql.public.User...`. There you call `.build()` to get the built query, which is the `plan` your hooks receive. When you run it with `db.query(plan)`, which returns rows, your `afterQuery` is called. When you run it with `db.execute(plan)`, which returns only how many rows changed, your `afterExecute` is called. To have the logger cover the counting calls as well, add a second hook to the object `queryLogger()` returns in `src/prisma/query-logger.ts`, next to `afterQuery`: @@ -84,7 +84,11 @@ export const db = postgres({ }); ``` -The only line added here is the `middleware` option. The `src/prisma/db.ts` that `npm create prisma@latest` generates has more in it than this, including a `connectDatabase` export that the generated `seed.ts` imports, so keep that file and add the `middleware` option to the `postgres({ ... })` calls in it rather than replacing it with the file above. Those calls only run when the app is started with `DATABASE_URL` exported, as `npm run dev` does. With `npm run dev:composer`, the generated file takes its client from Composer through `service.load()` instead, and the `middleware` option in `db.ts` is never used, so run the app with `DATABASE_URL` exported while you follow this guide. If you set your project up by hand, [The data contract](/orm/contract-authoring/the-data-contract) covers where `contract.json` and `contract.d.ts` come from. +The only line added here is the `middleware` option. Do not replace the `src/prisma/db.ts` that `npm create prisma@latest` generated with the file above, because the generated file has more in it, such as a `connectDatabase` export that the generated `seed.ts` imports. Instead, add the `middleware` option to each `postgres({ ... })` call in it. + +The generated project can start the app in two ways, and your middleware runs in only one of them. `npm run dev` starts the app with `DATABASE_URL` exported, so the `postgres({ ... })` calls in `db.ts` create the client and your middleware runs. `npm run dev:composer` runs the app with [Prisma Composer](/composer), which describes your services and databases in TypeScript, and the app then gets its client from Composer instead, so the `middleware` option in `db.ts` is never read. Use `npm run dev` while you follow this guide. + +If you set your project up by hand, [The data contract](/orm/contract-authoring/the-data-contract) covers where `contract.json` and `contract.d.ts` come from. That one line is all the setup there is, and every query on this client now passes through the logger, whether you wrote it with the ORM API (`db.orm.public.User...`) or with the SQL query builder. @@ -373,10 +377,10 @@ Every hook receives a context object called `ctx`, holding the following: | `ctx.planExecutionId` | The same unique ID in every hook that runs for one query | | `ctx.now()` | The current time in milliseconds, the same value `Date.now()` gives | | `ctx.scope` | Where the query is running: `'runtime'`, `'connection'`, or `'transaction'` | -| `ctx.mode` | Whether a failed check blocks the query. Always `'strict'` on PostgreSQL, because `postgres(...)` has no way to set it | +| `ctx.mode` | The [`mode` option](/orm/reference/transactions-and-runtime#mode) of `postgres(...)`: `'strict'`, the default, or `'permissive'`. The budgets middleware reads it to decide whether a query over budget throws or is only logged | | `ctx.contentHash(plan)` | A promise of a string that is the same for the same statement and parameters, so `await` it | | `ctx.contract` | Your contract, for when a hook needs to know what your database looks like | -| `ctx.log` | Has nowhere to go on PostgreSQL today, so use your own logger | +| `ctx.log` | The [`log` option](/orm/reference/transactions-and-runtime#postgresoptions) of `postgres(...)`, such as `console`, with `info`, `warn`, and `error` methods. Without that option, what you pass to it is not printed | `ctx.scope` is `'runtime'` for a query you sent on `db` directly, `'transaction'` inside `db.transaction(...)`, and `'connection'` for a query you sent on a connection you took out of the pool yourself with `db.runtime().connection()`. The cache reads it to leave queries inside a transaction alone. diff --git a/apps/docs/content/docs/orm/middleware/built-in-budgets.mdx b/apps/docs/content/docs/orm/middleware/built-in-budgets.mdx index dc0890bda03..bfc16351d0a 100644 --- a/apps/docs/content/docs/orm/middleware/built-in-budgets.mdx +++ b/apps/docs/content/docs/orm/middleware/built-in-budgets.mdx @@ -1,21 +1,23 @@ --- title: 'Built-in: budgets' -description: The budgets middleware caps row counts and reports queries that took too long. +description: The budgets middleware stops queries that read too many rows or take too long. url: /orm/middleware/built-in-budgets metaTitle: 'Prisma ORM built-in middleware: budgets' -metaDescription: 'Wire and configure the budgets middleware in Prisma ORM to cap row counts, report query latency, and understand the errors it raises.' +metaDescription: 'Wire and configure the budgets middleware in Prisma ORM to cap row counts and query time, and understand the errors it raises.' --- The `budgets` middleware stops one careless query from reading far more of your data, or taking far more time, than you meant to give it. You give it a row limit and a time limit, and it checks the queries you send against both. When a query goes over either limit, your call fails with an error and you get no rows back. Before you register it, know that `budgets` rejects any `SELECT` from the ORM API or the SQL query builder that has no `LIMIT` on it, however few rows the table holds. A statement you wrote yourself with `db.raw.sql` is not checked this way. On the [ORM API](/orm/fundamentals/reading-data), the call that runs a query and gives you every matching row is `.all()`, so that means every `.all()` call with no `.limit(...)` earlier in the chain. A `.where(...).first()` call is fine, because `.first()` asks the database for one row. -Those `.all()` calls start throwing the moment you add `budgets` to your client, and there is no warn-only setting to ease the change in. The `severities` options in the table below look like one, but they do nothing on a client you build with `postgres(...)`. So find your `.all()` calls and give them limits before you turn `budgets` on, and searching your source for the call is a good starting point: +Those `.all()` calls start throwing the moment you add `budgets` to your client, so find them and give them limits before you turn `budgets` on. Searching your source for the call is a good starting point: ```bash grep -rn "\.all()" src scripts ``` +If you want to turn `budgets` on before every call has a limit, set the row check's severity to `'warn'` and create the client with `mode: 'permissive'`, as [Report instead of throwing](#report-instead-of-throwing) shows. A query with no limit then runs, and its warning goes to the client's `log` option, so you can add the limits as the warnings come in. + The fix is a `.limit(...)` in the chain. A query that counts rows is never a problem, because a count that is not split into groups comes back as a single row. In the calls below, `db.orm` is the ORM API and `public` is the PostgreSQL schema your tables are in: ```ts @@ -72,10 +74,22 @@ Every option is optional, and the default in the table applies when you leave on | `defaultTableRows` | `number` | `10_000` | How many rows to assume a table has when it is not listed in `tableRows` | | `tableRows` | `Record` | `{}` | How many rows to assume each table has, keyed by table name | | `maxLatencyMs` | `number` | `1_000` | The longest one query may take, in milliseconds | -| `severities.rowCount` | `'warn' \| 'error'` | `'error'` | Nothing on a client you build with `postgres(...)`, where going over the row budget always throws | -| `severities.latency` | `'warn' \| 'error'` | `'warn'` | Nothing on a client you build with `postgres(...)`, where going over the time budget always throws | +| `severities.rowCount` | `'warn' \| 'error'` | `'error'` | Whether going over the row budget throws or only reports, on a client created with `mode: 'permissive'` | +| `severities.latency` | `'warn' \| 'error'` | `'warn'` | Whether going over the time budget throws or only reports, on a client created with `mode: 'permissive'` | + +### Report instead of throwing [#report-instead-of-throwing] + +On a client created with the default `mode`, `'strict'`, both budgets always throw, whatever `severities` says. When you create the client with `mode: 'permissive'`, a check whose severity is `'warn'` passes the problem to `log.warn`, where `log` is the [`log` option](/orm/reference/transactions-and-runtime#postgresoptions) you pass to `postgres(...)`, such as `console`, and the query runs. [`mode`](/orm/reference/transactions-and-runtime#mode) in the client reference has the details. A check whose severity is `'error'` still throws. Without a `log` option, the report goes nowhere. -`severities` has no effect on a client you build with `postgres(...)`, where both budgets always throw. You can leave it out. +```ts +export const db = postgres({ + contract: contractFromJson(contractJson), + url: process.env['DATABASE_URL']!, + middleware: [budgets({ severities: { rowCount: 'warn' } })], + mode: 'permissive', + log: console, +}); +``` ## How it enforces the budget diff --git a/apps/docs/content/docs/orm/middleware/built-in-lints.mdx b/apps/docs/content/docs/orm/middleware/built-in-lints.mdx index 2f93bbdaf7b..a596466f907 100644 --- a/apps/docs/content/docs/orm/middleware/built-in-lints.mdx +++ b/apps/docs/content/docs/orm/middleware/built-in-lints.mdx @@ -45,7 +45,7 @@ Every rule has a severity, which is `warn` or `error` and nothing else. A rule s :::note -You cannot yet give the `postgres(...)` client a logger of your own, so a rule set to `warn` produces nothing you can see: nothing is printed, and there is no callback of yours for it to reach. That makes `LINT.DELETE_WITHOUT_WHERE` and `LINT.UPDATE_WITHOUT_WHERE` the two rules you can act on today, because a blocked query is something you notice, and it leaves the other two as rules to plan for until the client accepts a logger. +A rule set to `warn` sends its warning to the [`log` option](/orm/reference/transactions-and-runtime#postgresoptions) of `postgres(...)`. Pass `log: console` to print the warnings. If you create the client without a `log` option, a rule set to `warn` has no effect you can see. The client's `mode` option makes no difference to `lints`: a `warn` rule logs in either mode. ::: @@ -62,10 +62,10 @@ The rules are named after the SQL they are about, and you write your queries in So `await db.orm.public.User.limit(50).all()` sets off `LINT.SELECT_STAR`, while `await db.orm.public.User.select('id', 'email').limit(50).all()` does not. In `db.orm.public.User`, `public` is the PostgreSQL schema and `User` is your model name. The SQL query builder reaches the same table by its name in the database, as `db.sql.public.User`. These examples assume a `User` model with no `@@map`, so the table has the same name as the model. -Today a `warn` rule has no visible effect. `lints` builds a record like the one below, with the code of the rule, a message, and the table the query touched, and passes it to the client's logger, but the `postgres(...)` client has no logger you can set yet, as the note above says, so the record is thrown away. +When a `warn` rule fires, `lints` calls `log.warn` with a record that holds the code of the rule, a message, and the table the query touched. With `log: console`, Node.js prints it like this: ```text title="Lint warning" -warn { +{ code: 'LINT.NO_LIMIT', message: 'Unbounded SELECT may return large result sets', details: { table: 'User' } diff --git a/apps/docs/content/docs/orm/middleware/how-middleware-works.mdx b/apps/docs/content/docs/orm/middleware/how-middleware-works.mdx index fc12a5cd0f4..f4d2c793e66 100644 --- a/apps/docs/content/docs/orm/middleware/how-middleware-works.mdx +++ b/apps/docs/content/docs/orm/middleware/how-middleware-works.mdx @@ -82,11 +82,11 @@ You write only the hooks you care about, and Prisma ORM skips the ones you leave `beforeCompile` → `beforeQuery` → `interceptQuery` → `onRow` (once per row) → `afterQuery` → `afterTransaction` -**A non-returning write** is a statement that reports how many rows it affected rather than returning them. On the ORM API those are `createAndCount`, `updateAndCount`, and `deleteAndCount`, and with the SQL query builder it is whatever you hand to `db.execute(plan)`. These run a shorter sequence: +**A counting write** is a write that gives you back only the number of rows it changed, not the rows themselves. On the ORM API those are `createAndCount`, `updateAndCount`, and `deleteAndCount`. `create` also writes, but it returns the row it wrote, so it is a row query. With the SQL query builder, a counting write is whatever you pass to `db.execute(plan)`. These run a shorter sequence: `beforeCompile` → `beforeExecute` → `interceptExecute` → `afterExecute` → `afterTransaction` -`beforeQuery` never runs for a non-returning write, and `beforeExecute` never runs for a row query, so a middleware that has to cover reads and writes both needs a hook in each sequence. In practice that means writing the check once and calling it from `beforeQuery` and from `beforeExecute`: +`beforeQuery` never runs for a counting write, and `beforeExecute` never runs for a row query, so a middleware that has to cover reads and writes both needs a hook in each sequence. In practice that means writing the check once and calling it from `beforeQuery` and from `beforeExecute`: ```ts title="src/prisma/no-delete-all.ts" import type { SqlMiddleware } from '@prisma/orm-postgres/family-runtime'; @@ -170,7 +170,7 @@ Then `scopeUserSelects(() => onlyMia)` goes in the `middleware` array of your `p ### beforeQuery and beforeExecute: validate or block -These two hooks are where you inspect a query and refuse it. `beforeQuery` runs on a row query and `beforeExecute` on a non-returning write, both after the SQL text has been written out and before anything reaches the database. Your hook receives the plan, so you can read the SQL text in `plan.sql` and read the same query as objects in `plan.ast`, which is the tree `beforeCompile` works on. Throwing from the hook blocks the query, and returning normally lets it carry on to the database. The hook itself goes in the object your middleware returns, next to `name` and `familyId`: +These two hooks are where you inspect a query and refuse it. `beforeQuery` runs on a row query and `beforeExecute` on a counting write, both after the SQL text has been written out and before anything reaches the database. Your hook receives the plan, so you can read the SQL text in `plan.sql` and read the same query as objects in `plan.ast`, which is the tree `beforeCompile` works on. Throwing from the hook blocks the query, and returning normally lets it carry on to the database. The hook itself goes in the object your middleware returns, next to `name` and `familyId`: ```ts async beforeQuery(plan) { @@ -219,7 +219,7 @@ Before-hooks still run on a query that an intercept hook answers, because every ### onRow: watch rows arrive -`onRow(row, plan, ctx)` lets you see each row of a result on its way to your code, and `row` is one of those plain row objects. Prisma ORM calls it once for every row as it reads the result, before your `await` on `.all()` or `.first()` resolves, so you can count rows, sample them, or throw to stop the query part way through. If you throw, your `await` rejects with that error and you get no rows at all, not the rows read so far. That is what `budgets` does when a query returns more rows than its `maxRows` budget allows. `onRow` does not run on a non-returning write, and it does not run for rows that an `interceptQuery` hook answered. +`onRow(row, plan, ctx)` lets you see each row of a result on its way to your code, and `row` is one of those plain row objects. Prisma ORM calls it once for every row as it reads the result, before your `await` on `.all()` or `.first()` resolves, so you can count rows, sample them, or throw to stop the query part way through. If you throw, your `await` rejects with that error and you get no rows at all, not the rows read so far. That is what `budgets` does when a query returns more rows than its `maxRows` budget allows. `onRow` does not run on a counting write, and it does not run for rows that an `interceptQuery` hook answered. ### afterQuery and afterExecute: observe the finished operation @@ -227,7 +227,7 @@ These two hooks are for recording what happened once the work is over. Prisma OR After a row query, `afterQuery` receives `result.rowCount`, how many rows came back, and `result.latencyMs`, how long the query took. `result.completed` is `true` when the query finished and `false` when it failed, and `result.source` is `'driver'` for a query the database answered and `'middleware'` when an `interceptQuery` answered it, so you can tell where the rows came from. -After a non-returning write, `afterExecute` receives the same `result.completed`, `result.latencyMs`, and `result.source`, and on a run that completed it also gets the number of rows the statement changed, in `result.stats.affectedRows`. There is no `result.rowCount` on this one, and there is no `result.stats` on a write that failed, so read the count only when `result.completed` is `true`. +After a counting write, `afterExecute` receives the same `result.completed`, `result.latencyMs`, and `result.source`, and on a run that completed it also gets the number of rows the statement changed, in `result.stats.affectedRows`. There is no `result.rowCount` on this one, and there is no `result.stats` on a write that failed, so read the count only when `result.completed` is `true`. Timing, logging, and latency budgets all belong in these two hooks. @@ -281,7 +281,7 @@ A middleware of your own that leaves `familyId` out can still be typed as `SqlMi When your hook throws from `beforeCompile`, `beforeQuery`, `beforeExecute`, `interceptQuery`, or `interceptExecute`, the whole operation fails before anything reaches the database, and your caller gets the error you threw. A throw from `onRow` also fails the operation, but by then the query has run: your caller gets your error and none of the rows, including the ones already read. Your own middleware can throw whatever error you like, as `noDeleteAll` throws a plain `Error`. `lints` and `budgets` throw an ordinary `Error` with a `code` property on it, so you catch them like any other error and read `code` to tell them apart. `budgets` throws two codes, `BUDGET.ROWS_EXCEEDED` and `BUDGET.TIME_EXCEEDED`, and the [lints](/orm/middleware/built-in-lints) page lists the codes `lints` throws. -When the database driver is the thing that fails, Prisma ORM still calls `afterQuery` on every middleware, or `afterExecute` after a non-returning write, with `result.completed` set to `false`, so your logging sees failed queries as well as successful ones. A throw from `afterQuery` or `afterExecute` while it is handling a failure cannot hide the real error: your caller still gets the failure the driver reported. +When the database driver is the thing that fails, Prisma ORM still calls `afterQuery` on every middleware, or `afterExecute` after a counting write, with `result.completed` set to `false`, so your logging sees failed queries as well as successful ones. A throw from `afterQuery` or `afterExecute` while it is handling a failure cannot hide the real error: your caller still gets the failure the driver reported. ## Prompt your coding agent diff --git a/apps/docs/content/docs/orm/migrations/generating-a-migration.mdx b/apps/docs/content/docs/orm/migrations/generating-a-migration.mdx index 7d3a168df29..351b87244f1 100644 --- a/apps/docs/content/docs/orm/migrations/generating-a-migration.mdx +++ b/apps/docs/content/docs/orm/migrations/generating-a-migration.mdx @@ -224,7 +224,7 @@ npx prisma migration check :::note[What's early] -Planning covers tables, columns, indexes, constraints, and the backfill placeholder shown above. It cannot tell a rename from a drop and a create on its own. When a plan would drop a model or field that holds data, `migration plan` asks about it before it writes anything: pass `--rename :` to keep the data, or `--delete ` to let it go, as the [`migration plan` reference](/cli/migration-plan#options) shows. Where nobody can answer, the command fails with `CLI.CONSENT_REQUIRED`. `db migrate` runs destructive operations without asking, so read `migration show ` before you apply anything. +Planning covers tables, columns, indexes, constraints, and the backfill placeholder shown above. It cannot tell on its own whether you renamed a model or field, or dropped it and created a new one. So when a plan would drop a model or field that holds data, `migration plan` asks you about it before it writes anything. To keep the data, answer with `--rename :`, such as `--rename User.name:User.fullName`. To let it go, answer with `--delete` and the name of the model or field, such as `--delete User.legacy`. The [`migration plan` reference](/cli/migration-plan#options) has both flags. When the command cannot ask you, because it is not running in an interactive terminal, as in CI, it fails with `CLI.CONSENT_REQUIRED` and lists each question with the flags that answer it. `db migrate` runs destructive operations without asking, so read `migration show ` before you apply anything. ::: diff --git a/apps/docs/content/docs/orm/migrations/how-migrations-work.mdx b/apps/docs/content/docs/orm/migrations/how-migrations-work.mdx index 23371551fb7..8070c38b9fa 100644 --- a/apps/docs/content/docs/orm/migrations/how-migrations-work.mdx +++ b/apps/docs/content/docs/orm/migrations/how-migrations-work.mdx @@ -124,7 +124,7 @@ When `db migrate` reaches an operation, it runs the postcheck first. If the post Say `"user"` already has a `phone` column of another type. The postcheck does not pass, because the type differs, so `db migrate` runs the precheck. The precheck finds a `phone` column, and the run fails with an error whose `code` is `MIGRATION.PRECHECK_FAILED`. To see which columns differ, run [`npx prisma db verify --schema-only`](/cli/db-verify), which compares the tables with your contract and skips the marker check. Then change the column by hand to the type your contract declares, here `text`, with `ALTER TABLE "public"."user" ALTER COLUMN "phone" TYPE text;`, and run `db migrate` again. [Drift](/orm/migrations/rollbacks-and-recovery#drift-when-the-database-isnt-where-migrations-left-it) covers other changes made outside a migration. -The checks are written into `ops.json` when `migration.ts` runs, so an `ops.json` written before `8.0.0-rc.19` keeps its older checks, some of which only asked whether an object of that name exists. For each such migration that is not applied yet, run its `migration.ts` again (`node /migration.ts`) before you apply it. An applied migration needs nothing, because its operations do not run again. +The checks are written into `ops.json` when `migration.ts` runs. If you planned migrations with a Prisma ORM 8 release before `8.0.0-rc.19`, their `ops.json` files keep the older checks, some of which only asked whether an object with that name exists. Before you apply such a migration, run its `migration.ts` again with `node /migration.ts`. A migration you have already applied needs nothing, because its operations do not run again. Each operation also has an `operationClass`, which says what kind of change the operation makes to the database and which you choose yourself when you write a raw SQL operation: @@ -207,7 +207,11 @@ Here is the whole loop, from idea to typed code: The commands, the file layout, the migration history, and the checks work the same way on PostgreSQL and MongoDB. On PostgreSQL, operations run SQL statements such as `ALTER TABLE`. On MongoDB, they create collections, indexes, and JSON Schema validators. -Each MongoDB collection that Prisma ORM manages gets a closed `$jsonSchema` validator, whether you write the contract in PSL or in TypeScript. It refuses a field the model does not declare, a missing required field, and a value of the wrong BSON type. A collection that stores several models, without one being a variant of another, gets a validator that accepts the documents of each model. MongoDB does not check documents already stored when a validator is added, so fix any document that does not match its model first. Then apply the validator with `npx prisma db update`, or with `npx prisma migration plan` followed by `npx prisma db migrate`. To leave a collection without a validator in a TypeScript contract, give its model `controlPolicy: 'observed'`, and Prisma ORM creates and changes nothing in that collection. +On MongoDB, Prisma ORM gives each collection it manages a `$jsonSchema` validator, so that MongoDB itself refuses a document that does not match its model. The validator is closed: it refuses a field the model does not declare, as well as a missing required field and a value of the wrong BSON type. This is the same whether you write the contract in `contract.prisma` or in TypeScript with `defineContract`. One collection can store more than one model. When they are a base model and its [variants](/orm/contract-authoring/psl-syntax#base-models-and-variants), its validator accepts a document of the base model or of any variant. When they are separate models stored in the same collection, its validator accepts a document of any of those models. + +MongoDB does not check the documents already in a collection when a validator is added. So before you apply a validator, fix any stored document that does not match its model. Then apply it with `npx prisma db update`, or with `npx prisma migration plan` followed by `npx prisma db migrate`. + +In a contract written in TypeScript, you can leave a collection without a validator, for example because other applications write fields your contract does not declare. Give its model `controlPolicy: 'observed'`, which tells Prisma ORM to leave the collection as it is: Prisma ORM then creates and changes nothing in it, not even a validator. The [TypeScript schema builder](/orm/contract-authoring/typescript-schema-builder) page shows the option. Each database keeps its own [marker and ledger](/orm/migrations/the-migration-graph#terms-used-on-this-page), where the ledger is that database's list of every migration applied to it, and `migration log` is the command that reads it. diff --git a/apps/docs/content/docs/orm/reference/migration-api.mdx b/apps/docs/content/docs/orm/reference/migration-api.mdx index 12e9c641155..7ebcfd68202 100644 --- a/apps/docs/content/docs/orm/reference/migration-api.mdx +++ b/apps/docs/content/docs/orm/reference/migration-api.mdx @@ -132,7 +132,7 @@ Each operation is a method you call on the migration, `this.({ ... })`, wi `addColumn` and `setDefault` take a `col(...)` call as `column`. The other methods take the column's name as a string. -`alterColumnType` takes the new type in `type`, written as a migration writes it, such as `'int8'`, `'varchar(255)'`, or `'"Mood"'` for an enum. Prisma ORM works out how PostgreSQL prints that type through the data types of your configured target and extensions, and the postcheck compares the column's type with it. A type it cannot read fails with `MIGRATION.COLUMN_TYPE_UNREADABLE`. For a column of a type no data type declares, pass the type as PostgreSQL prints it and set `raw: true`, and it is checked as written. +`alterColumnType` takes the new type in `type`, written the way a migration writes it, such as `'int8'`, `'varchar(255)'`, or `'"Mood"'` for an enum. Prisma ORM recognizes the PostgreSQL types it supports and the types of the extension packages in your configuration, and after the change `db migrate` checks that the column has the type you wrote. A type it does not recognize fails with `MIGRATION.COLUMN_TYPE_UNREADABLE`. If that type comes from an extension package, add the package to your configuration. For any other type, set `raw: true` and pass the type exactly as PostgreSQL prints it, because `db migrate` then compares the column's type with that text. ```ts this.alterColumnType({ schema: 'public', table: 'post', column: 'views', type: 'int8' }), @@ -142,9 +142,13 @@ this.alterColumnType({ schema: 'public', table: 'post', column: 'views', type: ' When the column is an `autoincrement()` column, `alterColumnType` also changes the sequence the column owns to the new width, with `ALTER SEQUENCE ... AS bigint` for an `int8` column. It first refuses, before any statement runs, when the sequence has already handed out a value too large for the new width. A migration applied before `8.0.0-rc.19` left the sequence at its old width. Change it yourself, for example `ALTER SEQUENCE "public"."post_id_seq" AS bigint`; `pg_get_serial_sequence('"public"."post"', 'id')` names the sequence. -`migration.ts` refuses an `alterColumnType` with `using` to the type the migration's start contract already gives the column, with `MIGRATION.ALTER_COLUMN_TYPE_USING_WITHOUT_TYPE_CHANGE`. Its postcheck would already pass, so the conversion would never run. To change the values of a column whose type stays the same, write a [`dataTransform`](#data), which checks the rows themselves. +A `using` needs a type change. If the new `type` is the type the column already has in the contract the migration starts from, `migration.ts` refuses the `alterColumnType` with `MIGRATION.ALTER_COLUMN_TYPE_USING_WITHOUT_TYPE_CHANGE`, because `db migrate` would find that the column already has that type and skip the operation, so the conversion would never run. To change the values in a column and keep its type, write a [`dataTransform`](#data) instead, which checks the rows themselves. -`setDefault` takes the column with its new default, and Prisma ORM writes the `DEFAULT` clause: `column: col('name', 'text', { default: lit('anonymous'), codecRef: { codecId: 'pg/text@1' } })`, or `column: col('createdAt', 'timestamptz', { default: fn('now()') })`. With a `codecRef`, the column's codec checks a literal default first, and a value it refuses fails with [`CONTRACT.DEFAULT_INVALID`](/orm/reference/error-reference#CONTRACT.DEFAULT_INVALID). A `migration.ts` that still passes `defaultSql`, written before `8.0.0-rc.15`, stops with [`MIGRATION.OPERATION_OPTION_REMOVED`](/orm/reference/error-reference#MIGRATION.OPERATION_OPTION_REMOVED); rewrite the call this way. Its `ops.json` still applies. The operation is widening when the migration's start contract gives the column a default, and additive otherwise. `setDefault` takes no `operationClass`; a call that still passes one runs and ignores it. `setDefault` has no postcheck, so it always runs, and replacing an existing default works. After the last operation, `db migrate` checks that the default matches your contract. +`setDefault` takes the column with its new default, and Prisma ORM writes the `DEFAULT` clause: `column: col('name', 'text', { default: lit('anonymous'), codecRef: { codecId: 'pg/text@1' } })`, or `column: col('createdAt', 'timestamptz', { default: fn('now()') })`. When you pass a `codecRef`, Prisma ORM first checks that a literal default is a valid value of that type, and a value that is not fails with [`CONTRACT.DEFAULT_INVALID`](/orm/reference/error-reference#CONTRACT.DEFAULT_INVALID). + +`setDefault` has no postcheck, so it always runs, which means it can also replace a default the column already has. After the last operation, `db migrate` checks that the default matches your contract. The operation is widening when the column already has a default in the contract the migration starts from, and additive otherwise. + +`setDefault` takes no `operationClass`; a call that still passes one runs and ignores it. A `migration.ts` written before `8.0.0-rc.15` that still passes `defaultSql` stops with [`MIGRATION.OPERATION_OPTION_REMOVED`](/orm/reference/error-reference#MIGRATION.OPERATION_OPTION_REMOVED), so rewrite the call as shown above. Its existing `ops.json` still applies. `setNotNull` has an extra precheck that fails while the column still holds a `NULL`, so backfill the column with a `dataTransform` first. [Editing a migration](/orm/migrations/editing-a-migration#worked-example-making-a-column-required) shows the three steps. diff --git a/apps/docs/content/docs/orm/reference/orm-client.mdx b/apps/docs/content/docs/orm/reference/orm-client.mdx index fa9e3fd0a18..9ce9a7d3173 100644 --- a/apps/docs/content/docs/orm/reference/orm-client.mdx +++ b/apps/docs/content/docs/orm/reference/orm-client.mdx @@ -900,7 +900,7 @@ On PostgreSQL and SQLite you can subclass `Collection` to add your own methods t #### Remarks - PostgreSQL and SQLite. You cannot subclass a collection on MongoDB. -- `collections` maps a model name to your class. `postgres(...)`, `postgresServerless(...)`, `sqlite(...)`, and `supabase(...)` take it. The classes are used on `db.orm`, on every transaction's `tx.orm`, on each connection a serverless client opens, and on Supabase's role-bound clients. Models you leave out keep the built-in class. +- `collections` maps a model name to your class. You pass it to the function that creates your client: `postgres(...)`, `postgresServerless(...)` for serverless functions, `sqlite(...)` for SQLite, or `supabase(...)` for Supabase. Your class is then used wherever that client gives you the model: on `db.orm`, on every transaction's `tx.orm`, on each connection the serverless client opens, and on the Supabase clients that run queries as a database role, such as the one `asUser(jwt)` returns. Models you leave out keep the built-in class. - If the module that defines your classes needs the contract type, import only the type, `import type { Contract } from './contract.d'`, so that it does not import the module that creates the client. - `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. @@ -941,6 +941,8 @@ const recentBugs = await db.orm.public.Task.orderBy((t) => t.id.desc()).limit(10 const lockedBugs = await db.transaction((tx) => tx.orm.public.Task.bugs().forUpdate().all()); ``` +The last line runs inside a transaction, so your method works on `tx.orm` too. `forUpdate()` locks the rows it reads with `FOR UPDATE` until the transaction ends. + On SQLite, `db.orm` holds the models directly, so the same method is `db.orm.Task.bugs()`. ### Package methods with `using` [#package-methods-with-using] @@ -2035,7 +2037,7 @@ A plain object of equality matches, ANDed together. - Available for PostgreSQL and MongoDB. - 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 MongoDB, a list field takes a whole list, which matches a stored list that is equal to it, an empty list `[]`, or `null`. `null` matches a document whose list is null or missing, not a list that holds a `null` item. +- On MongoDB, what a list field matches depends on the value you give it. A whole list, such as `{ tags: ['a', 'b'] }`, matches a stored list equal to it. `{ tags: [] }` matches an empty list. `{ tags: null }` matches a document whose list is null or missing, not a list that holds a `null` item. - 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 diff --git a/apps/docs/content/docs/orm/reference/raw-queries.mdx b/apps/docs/content/docs/orm/reference/raw-queries.mdx index 7e429bb5be0..98c7adbe779 100644 --- a/apps/docs/content/docs/orm/reference/raw-queries.mdx +++ b/apps/docs/content/docs/orm/reference/raw-queries.mdx @@ -16,7 +16,7 @@ Raw SQL comes in two forms. A **raw fragment** is a piece of SQL you put inside a builder query. Write it as a tagged template with `fns.raw` and pass it to a `select()`, `where()`, `orderBy()`, or `update()` call. The rest of the query stays typed. -A **whole raw statement** is a complete SQL statement written with `db.raw.sql`. If it returns rows, end it with `.returnsRow(spec).build()`, where the spec is a column list [defined below](#the-client-level-dbrawsql-tag), and pass the result to `db.query(...)`. If it does not, end it with `.affectedCount().build()` and pass the result to `db.execute(...)`. To see the SQL a built query would send, without running it, pass it to [`db.toSql(...)`](/orm/reference/transactions-and-runtime#dbtosqlplan). +A **whole raw statement** is a complete SQL statement written with `db.raw.sql`. It takes the place of `$queryRaw` and `$executeRaw` from Prisma ORM 7. If it returns rows, as a `$queryRaw` query did, end it with `.returnsRow(spec).build()`, where the spec is a column list [defined below](#the-client-level-dbrawsql-tag), and pass the result to `db.query(...)`. If it does not, as with `$executeRaw`, end it with `.affectedCount().build()` and pass the result to `db.execute(...)`. To see the SQL a built query would send, without running it, pass it to [`db.toSql(...)`](/orm/reference/transactions-and-runtime#dbtosqlplan). :::warning[Only `.returnsRow()` converts values for you] A type id such as `pg/text@1` tells Prisma ORM what JavaScript type a column should become, and the property is named `codecId`. `.returnsRow(spec)` names a type id for every column of a whole `db.raw.sql` statement, so you get real JavaScript values back. A fragment's `.returns(...)` does not convert anything: it only tells TypeScript what type to expect, so the value is whatever the `pg` package, the PostgreSQL driver underneath, gives you. For a `text` or an `integer` column that is already a JavaScript string or number, while a `numeric` column arrives as a string. A date column gets no conversion by Prisma ORM, so you get the `pg` package's own value for that column. @@ -204,7 +204,7 @@ To bind any value whose type is not in the list, wrap it in [`param(value, { cod `db.raw.collection(collectionName)` returns an object with nine methods for running MongoDB commands directly against a collection. The collection name is the one your contract declares (`'users'`, `'posts'`), the same name the ORM client uses (`db.orm.users`). MongoDB has no schemas, so there is no `public` segment here and none on `db.orm`. There is no raw `find()`, so use `aggregate()` with a `$match` stage. MongoDB has no transactions in Prisma ORM, so a raw command cannot run inside one. See [Transactions (MongoDB)](/orm/reference/transactions-and-runtime#transactions-mongodb). -Each of these nine methods builds a command: call `.build()` on it, then run every one of them with `db.query(...)`, including the writes. The raw collection methods all run through `query()`. A write returns a one-element array, so destructure it with `const [result] = ...`. +Each of these nine methods builds a command: call `.build()` on it, then run it with `runtime.query(...)`, where `runtime` is what `await db.runtime()` returns. This is true of the writes too. A write returns a one-element array, so destructure it with `const [result] = ...`. An update or delete command can also run with `runtime.execute(...)`, which resolves to `{ affectedRows }`. You cannot run a command with `db.query(...)` as you would on PostgreSQL, because on MongoDB `db.query` is the [pipeline builder](/orm/reference/pipeline-builder), which builds aggregation pipelines. :::warning[MongoDB raw results are native BSON values] Nothing on the MongoDB side of this page is converted for you. Raw commands name fields as they are stored in the database, so a field the ORM calls `id` because it is declared `id ObjectId @id @map("_id")` is `_id` here, in filters and in the rows you get back. An `_id` comes back as it is stored: one that MongoDB generated is an `ObjectId` instance from the `mongodb` package, the MongoDB driver underneath, not a hex string. Compare one with `String(row._id) === aliceId`. Raw filters take native BSON values too, so build ids with that package's `ObjectId` class. The write methods give you the counts and ids under the `mongodb` package's own key names, `{ insertedId }`, `{ matchedCount, modifiedCount, upsertedCount, upsertedId }`, and `{ deletedCount }`, and you read those keys directly. diff --git a/apps/docs/content/docs/orm/reference/transactions-and-runtime.mdx b/apps/docs/content/docs/orm/reference/transactions-and-runtime.mdx index 7396e0059b2..35f2c1980cc 100644 --- a/apps/docs/content/docs/orm/reference/transactions-and-runtime.mdx +++ b/apps/docs/content/docs/orm/reference/transactions-and-runtime.mdx @@ -6,11 +6,11 @@ metaTitle: Prisma ORM transactions and runtime reference metaDescription: 'Reference for the Prisma ORM client lifecycle, transactions, prepared statements, and execution options.' --- -Prisma ORM 8 renames `schema.prisma` to `contract.prisma`. Run `npx prisma contract emit`, which writes two files next to it: `contract.json`, which your app imports, and `contract.d.ts`, which gives you the TypeScript types. A new project keeps all three files in `src/prisma/`. Every client you create needs the contract from `contract.json`, turned into a value with `contractFromJson`. The query you came for, `prisma.user.findMany({ where })`, is now `db.orm.public.User.where(...).all()`. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) lists the packages to install, the commands to run, and the rest of the renamed calls. +Create, connect, and close the client, run transactions, prepare statements, and set per-query execution options, on PostgreSQL and MongoDB. For a task-oriented walkthrough, see the Fundamentals guide to [Transactions](/orm/fundamentals/transactions). For the ways to query inside a transaction, see the [ORM client reference](/orm/reference/orm-client), the [SQL query builder reference](/orm/reference/sql-query-builder), and the [raw queries reference](/orm/reference/raw-queries). -`db` is the client that `postgres(...)` or `mongo(...)` returns. `db.runtime()` gives you the object that actually runs queries, and that object is called the runtime. +If you are coming from Prisma ORM 7, your `schema.prisma` is now called `contract.prisma`. Running `npx prisma contract emit` writes two files next to it: `contract.json`, which your app imports, and `contract.d.ts`, which gives you the TypeScript types. A new project keeps all three files in `src/prisma/`. Every client you create needs the contract from `contract.json`, turned into a value with `contractFromJson`. A query such as `prisma.user.findMany({ where })` is now `db.orm.public.User.where(...).all()`. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) lists the packages to install, the commands to run, and the rest of the renamed calls. -Create, connect, and close the client, run transactions, prepare statements, and set per-query execution options, on PostgreSQL and MongoDB. For a task-oriented walkthrough, see the Fundamentals guide to [Transactions](/orm/fundamentals/transactions). For the ways to query inside a transaction, see the [ORM client reference](/orm/reference/orm-client), the [SQL query builder reference](/orm/reference/sql-query-builder), and the [raw queries reference](/orm/reference/raw-queries). +`db` is the client that `postgres(...)` or `mongo(...)` returns. `db.runtime()` gives you the object that actually runs queries, and that object is called the runtime. Transactions are not available on MongoDB. To run one there, pass your own `MongoClient` with the `mongoClient` option and use the driver's `session.withTransaction(...)`, which [Transactions on MongoDB](/orm/fundamentals/transactions#transactions-on-mongodb) shows in code. @@ -24,7 +24,7 @@ Create a PostgreSQL client. #### Remarks -- Pass the contract as a value with the `contract` option. Turn `contract.json` into that value once with `contractFromJson(contractJson)` from `@prisma/orm-postgres/static`, as every example below does. `Contract` is the type that `contract.d.ts` exports; you name it here because TypeScript cannot read a type out of a JSON file. The client takes its types for `db.orm` and `db.sql` from the value, so `postgres(...)` takes no type argument. A contract written in TypeScript with `defineContract` is already a value: pass it as `contract` directly. +- Pass the contract as a value with the `contract` option. Turn `contract.json` into that value once with `contractFromJson(contractJson)` from `@prisma/orm-postgres/static`, as every example below does. `Contract` is the type that `contract.d.ts` exports; you name it here because TypeScript cannot read a type out of a JSON file. The client takes its types for `db.orm` and `db.sql` from the value, so `postgres(...)` takes no type argument. If you write your contract in TypeScript with [`defineContract`](/orm/contract-authoring/typescript-schema-builder) instead of in `contract.prisma`, what `defineContract` returns is already a contract value, so pass it as `contract` directly. - `contractFromJson` checks the contract once. Build every client from the same value, and export it from the module that creates the client so that tests and other modules can import it. - Tell the client which database to use in one of three ways: `url`, `pg`, or `binding`. A `binding` wraps any of the three choices in one object, with a `kind` field saying which it is: `{ kind: 'url', url }`, `{ kind: 'pgPool', pool }`, or `{ kind: 'pgClient', client }`. Use it when your code picks the source at run time. Pass exactly one of `url`, `pg`, and `binding`, or leave all three out and pass one to [`connect()`](#connect). - When you pass a `pg` pool or client, you own it: call `db.close()` first, then close it yourself with `pool.end()`. When you pass a `url`, the client creates the pool and closes it. Set that pool's timeouts with `poolOptions`, which has no effect on a pool you supply yourself. @@ -49,6 +49,7 @@ Pass exactly one of `url`, `pg`, and `binding`. | `extensions` | Array of extension runtimes, the default export of each package's `/runtime` subpath | No | The PostgreSQL extensions your contract's types need. Install each package, then import its `/runtime` subpath and put the default export in this array. | | `middleware` | Array of middleware | No | Code that runs around every query. See [How middleware works](/orm/middleware/how-middleware-works). | | `verifyMarker` | `'onFirstUse'` or `false` | No | Whether the first query checks that the database was set up for this contract. Defaults to `'onFirstUse'`. | +| `log` | An object with `info`, `warn`, and `error` methods, such as `console` | No | Where the client sends its warnings, such as a `'warn'` result from the lints or budgets middleware, or the `verifyMarker` warning. Without `log`, warnings are not printed. | | `cursor` | `PostgresCursorOptions`, which is `{ batchSize?: number }` | No | Read through a server-side cursor in batches of `batchSize` rows, 100 when you leave `batchSize` out. Without `cursor`, reads are not batched. | | `mode` | `'strict'` or `'permissive'` | No | How middleware treats a rule violation. Defaults to `'strict'`. See [`mode`](#mode). | | `collections` | Object mapping model name to a collection class | No | Your own collection classes, used on `db.orm` and on every transaction's `tx.orm`. See [Custom collections](/orm/reference/orm-client#custom-collection-subclass). | @@ -196,8 +197,9 @@ The `mode` option says how middleware treats a rule violation. #### Remarks -- `'strict'`, the default, makes the [budgets middleware](/orm/middleware/built-in-budgets) throw on every violation. -- With `'permissive'`, a budgets violation whose severity is `'warn'` is logged with the client's `log.warn`, and the query runs. Set the severity with the budgets `severities` option. A violation whose severity is `'error'` still throws. +- Of the built-in middleware, only [budgets](/orm/middleware/built-in-budgets) reads `mode`. The [lints middleware](/orm/middleware/built-in-lints) sends a rule set to `'warn'` to `log.warn` in either mode. +- `'strict'`, the default, makes the budgets middleware throw on every violation. +- With `'permissive'`, a budgets violation whose severity is `'warn'` is passed to `log.warn`, where `log` is the `log` option you pass to the client, such as `log: console`, and the query runs. Without a `log` option, the warning goes nowhere. Set the severity with the budgets `severities` option. A violation whose severity is `'error'` still throws. - `postgres(...)`, `postgresServerless(...)`, `sqlite(...)`, and `supabase(...)` take `mode`. Its type is `RuntimeMode`. #### Examples @@ -212,6 +214,7 @@ const db = postgres({ url: process.env.DATABASE_URL!, middleware: [budgets({ maxRows: 1000, severities: { rowCount: 'warn' } })], mode: 'permissive', + log: console, }); ``` @@ -341,7 +344,9 @@ Pass exactly one of `url`, `uri`, `mongoClient`, and `binding`. `dbName` is sepa | Return type | Example | Description | |---|---|---| -| `MongoClient` | `mongo({ contract, url, dbName })` | A client that has not connected yet. This is Prisma ORM's own type. The `mongodb` driver has a class of the same name, so when you import both, name this one `db`. | +| `MongoClient` | `mongo({ contract, url, dbName })` | A client that has not connected yet. | + +`MongoClient` here is Prisma ORM's own type, exported from `@prisma/orm-mongo/runtime`. The `mongodb` driver has a class with the same name, so in a file that imports both, rename one of them in its import: `import type { MongoClient as PrismaMongoClient } from '@prisma/orm-mongo/runtime'`. - `.orm` holds your models by collection name, with no `.public` in the path: `db.orm.users`. The collection name is the model's `@@map` value, or the model name exactly as written when the model sets no `@@map`. The [example schema](/orm/reference/orm-client#example-schema) sets `@@map("users")` on `User`, which is why the key here is `users`. See the [ORM client reference](/orm/reference/orm-client). - `.query` is the pipeline builder, which builds MongoDB aggregation pipelines. See the [pipeline builder reference](/orm/reference/pipeline-builder). `.raw` sends raw MongoDB commands. See the [raw queries reference](/orm/reference/raw-queries). From 086e266e545cd3255a1631ab464f42b10b9fc970 Mon Sep 17 00:00:00 2001 From: willbot Date: Wed, 21 Oct 2026 19:01:25 +0200 Subject: [PATCH 3/3] docs(docs): point the config at the printed contract before emitting Temporal types Signed-off-by: willbot Signed-off-by: Will Madden --- apps/docs/content/docs/cli/configuration.mdx | 2 +- pnpm-lock.yaml | 19 ------------------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/apps/docs/content/docs/cli/configuration.mdx b/apps/docs/content/docs/cli/configuration.mdx index 61d70c9c1f7..ff9ecb1ce8e 100644 --- a/apps/docs/content/docs/cli/configuration.mdx +++ b/apps/docs/content/docs/cli/configuration.mdx @@ -127,7 +127,7 @@ export default definePrismaConfig({ Prisma ORM 7 gave your application date and time values as JavaScript `Date` objects. With `prisma7Schema`, Prisma ORM 8 gives them as strings instead. This applies to a `DateTime` field and to a field with `@db.Timestamp`, `@db.Timestamptz`, `@db.Date`, or `@db.Time`: each value is the text PostgreSQL prints, such as `"2026-09-14 10:00:00.123"`. A `DateTime` value is in UTC, as Prisma ORM 7 writes it. -Prisma ORM 8 can also give these values as `Temporal` objects. `Temporal` is the newer JavaScript date and time API, and runtimes without it, such as Node.js 24, need the `temporal-polyfill` package. To get `Temporal` values, write a Prisma ORM 8 contract with `npx prisma contract print --output prisma/contract.prisma`, and in that file change `TimestampString(3)` to `Timestamp(3)`, `TimestamptzString(3)` to `Timestamptz(3)`, `DateString` to `Date`, and `TimeString(3)` to `Time(3)`. +Prisma ORM 8 can also give these values as `Temporal` objects. `Temporal` is the newer JavaScript date and time API, and runtimes without it, such as Node.js 24, need the `temporal-polyfill` package. To get `Temporal` values, write a Prisma ORM 8 contract with `npx prisma contract print --output prisma/contract.prisma`, and in that file change `TimestampString(3)` to `Timestamp(3)`, `TimestamptzString(3)` to `Timestamptz(3)`, `DateString` to `Date`, and `TimeString(3)` to `Time(3)`. Then change `contract` in `prisma.config.ts` from `prisma7Schema("prisma/schema.prisma")` to `"./prisma/contract.prisma"`, so that `contract emit` reads the edited file. When your code creates or updates a row, Prisma ORM fills each `@updatedAt` field with the time from your application's clock, as text in the same form. This also works on a `@db.Date`, `@db.Time`, or `@db.Timetz` field. A field can have both `@updatedAt` and `@default(now())`, and `@default(now())` stays the column's default in the database. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 04471862c76..b3ef335c7bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,9 +6,6 @@ importers: .: configDependencies: {} packageManagerDependencies: - '@pnpm/exe': - specifier: 12.3.4 - version: 12.3.4 pnpm: specifier: 12.3.4 version: 12.3.4 @@ -59,11 +56,6 @@ packages: cpu: [x64] os: [win32] - '@pnpm/exe@12.3.4': - resolution: {integrity: sha512-Rq7JokWAyYF9IFfn8zofB/q2AgnZlmoMuH5fMHB2+Ta61I9aD7sNm+woZpx6iH+5vb38chdSoYRvOtSqlH72YQ==} - engines: {node: '>=18.*'} - hasBin: true - pnpm@12.3.4: resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==} engines: {node: '>=18.*'} @@ -95,17 +87,6 @@ snapshots: '@pnpm/exe.win32-x64@12.3.4': optional: true - '@pnpm/exe@12.3.4': - optionalDependencies: - '@pnpm/exe.darwin-arm64': 12.3.4 - '@pnpm/exe.darwin-x64': 12.3.4 - '@pnpm/exe.linux-arm64': 12.3.4 - '@pnpm/exe.linux-arm64-musl': 12.3.4 - '@pnpm/exe.linux-x64': 12.3.4 - '@pnpm/exe.linux-x64-musl': 12.3.4 - '@pnpm/exe.win32-arm64': 12.3.4 - '@pnpm/exe.win32-x64': 12.3.4 - pnpm@12.3.4: optionalDependencies: '@pnpm/exe.darwin-arm64': 12.3.4