diff --git a/apps/docs/content/docs/cli/configuration.mdx b/apps/docs/content/docs/cli/configuration.mdx index eb366b6f70..3054513686 100644 --- a/apps/docs/content/docs/cli/configuration.mdx +++ b/apps/docs/content/docs/cli/configuration.mdx @@ -6,7 +6,7 @@ metaTitle: Prisma ORM CLI configuration metaDescription: 'Learn how Prisma ORM CLI commands find config, read database URLs, and format output.' --- -Prisma ORM CLI commands read `prisma.config.ts`, starting in the directory you run them from. The file has one section per part of the CLI. The `contract`, `db`, and `migration` commands read the `orm` section, and the [agent skills commands](/cli/skills) read the `skills` section. +Prisma ORM CLI commands read `prisma.config.ts`, starting in the directory you run them from. The file has one section per part of the CLI. The `orm` section holds your schema, database, and migration settings, which the `contract`, `db`, and `migration` commands read. The `skills` section holds the settings for the [agent skills commands](/cli/skills), which copy Prisma's instructions for AI coding agents into your project. ## Config file @@ -29,7 +29,7 @@ export default definePrismaConfig({ For MongoDB projects, import the section helper from `@prisma/orm-mongo/config` instead. Both packages export this helper as `defineConfig`, and the examples on this page rename it to `ormConfig` when they import it. -If you set up Prisma ORM 8 with a release candidate before `8.0.0-rc.12`, your config may import `defineConfig` from `@prisma/cli-engine`, the package the `prisma` CLI is built on. `8.0.0-rc.12` requires `@prisma/cli-engine` 0.6.1, which removed that export, so the config fails to load. Rename that import and its call to `definePrismaConfig`, which is the same function under its new name. You can import it from `prisma/config` or from `@prisma/cli-engine`, because `prisma/config` re-exports it. Leave the `defineConfig` import from `@prisma/orm-postgres/config` or `@prisma/orm-mongo/config` as it is: that is the helper for the `orm` section, and its name has not changed. +This paragraph is only for a config written for a Prisma ORM 8 release candidate before `8.0.0-rc.12`. Such a config may import `defineConfig` from `@prisma/cli-engine`. That import no longer exists, so the config fails to load. Replace it with `definePrismaConfig` from `prisma/config`, and rename the call to match: it is the same function under its new name. Leave the `defineConfig` import from `@prisma/orm-postgres/config` or `@prisma/orm-mongo/config` as it is. That one is the helper for the `orm` section, and its name has not changed. [`orm init`](/cli/orm-init) writes this file for you. Without `--config`, the CLI looks for `prisma.config.ts` in the directory you run the command from and in the directories above it, as [How the CLI finds the config](#how-the-cli-finds-the-config) explains. Pass `--config` to use a config file in another place, such as a subdirectory: @@ -39,15 +39,15 @@ npx prisma contract emit --config ./config/prisma.config.ts ## How the CLI finds the config -The CLI looks for `prisma.config.ts` in the current directory, or starts from the file you pass to `--config`. It then looks in each parent directory, up to the root of the repository, which is the first directory that has a `.git` entry. If neither the starting directory nor any directory above it has a `.git` entry, the CLI reads only the first file. +The CLI starts with `prisma.config.ts` in the current directory, or with the file you pass to `--config`. It then also reads the `prisma.config.ts` in each parent directory that has one, up to the root of the repository, which is the first directory that has a `.git` entry. If neither the starting directory nor any directory above it has a `.git` entry, the CLI reads only the file it started with. -The CLI combines the files it finds one section at a time. Inside a section such as `orm`, each top-level key, such as `contract`, `db`, or `migrations`, comes whole from the nearest file that sets it, and the keys nested inside it are not combined. So if a config higher up sets `db.connection` and your project's config sets only `contract`, the CLI uses your `contract` and the other file's `db`. Extensions are the exception: a project config that calls `ormConfig` does not inherit them from a config higher up, even when it leaves `extensions` out. List the extensions your contract uses in each project's own config. +The CLI combines the files it finds one section at a time. Inside a section such as `orm`, it takes each top-level key, such as `contract`, `db`, or `migrations`, from the nearest file that sets it, and it takes the whole value of that key from that one file. So if a config higher up sets `db.connection` and your project's config sets only `contract`, the CLI uses your `contract` and the other file's `db`. -A project inside a larger repository therefore inherits any setting its own config leaves out from a `prisma.config.ts` higher up. +Extensions are the exception: if your project config has its own `orm` section, it never inherits `extensions` from a config higher up, even when it lists none. List the extensions your contract uses, as [Extension packs](#extension-packs) shows, in each project's own config. :::warning[More than one project in a repository] -If a `prisma.config.ts` higher up sets `db` or `migrations`, a project config that leaves them out uses that file's database and migrations folder. `db update`, `db migrate` and `db verify` then connect to the other project's database, and `migration plan` writes into the other project's migrations folder, without a warning. Set `db` and `migrations` in every project's own config, or add `parent: false` to it. +If a `prisma.config.ts` higher up sets `db` or `migrations`, a project config that leaves them out uses that file's database and migrations folder. `db update`, `db migrate` and `db verify` then connect to the other project's database, and `migration plan` writes into the other project's migrations folder, without a warning. Set `db` and `migrations`, such as `migrations: { dir: "./migrations" }`, in every project's own config, or add `parent: false` to it. ::: @@ -65,7 +65,7 @@ export default definePrismaConfig({ }); ``` -`parent` can also be a path to another config file, which the CLI reads next in place of the one in the parent directory. The path is relative to the directory of the config file that sets it, and it may point outside the repository. After reading that file, the CLI goes on to search the directories above it in the usual way, unless that file sets `parent` too: +`parent` can also be a path to another config file, which the CLI reads next in place of the one in the parent directory. The path is relative to the directory of the config file that sets it, and it may point outside the repository. After reading that file, the CLI goes on to search the directories above that file in the usual way, unless that file sets `parent` too: ```typescript title="prisma.config.ts" import { definePrismaConfig } from "prisma/config"; @@ -96,7 +96,9 @@ export default definePrismaConfig({ }); ``` -`contract emit` builds one contract from every matched file whose first line is `// use prisma-8`. It skips a matched file without that line and does not warn, so the line is what makes a file part of the schema. The Prisma editor extension uses the same rule, so the editor and `contract emit` agree on which files make up the schema. If the glob matches files but none of them starts with the line, `contract emit` fails with `CONTRACT.SOURCE_LOAD_FAILED`, and the error's finding is `PSL_NO_OPTED_IN_SCHEMA_FILES`. If the glob matches no file at all, for example because of a typo in the pattern, the finding is `PSL_NO_SCHEMA_FILES_MATCHED` instead. +`contract emit` builds one contract from every matched file whose first line is `// use prisma-8`. The same rule applies when `contract` names a single file: that file must start with the line too. `contract emit` skips a matched file without that line and does not warn, so the line is what makes a file part of the schema. The Prisma editor extension uses the same rule, so the editor and `contract emit` agree on which files make up the schema. + +If the glob matches files but none of them starts with the line, `contract emit` fails with `CONTRACT.SOURCE_LOAD_FAILED`, and the error's details name the problem `PSL_NO_OPTED_IN_SCHEMA_FILES`. If the glob matches no file at all, for example because of a typo in the pattern, they name it `PSL_NO_SCHEMA_FILES_MATCHED` instead. [What `contract emit` creates](/cli/contract-emit#what-it-creates) says where these details appear in the output. A new file joins the schema the next time you run `contract emit`, with no change to the config. The emitted files go in the folder before the first wildcard, here `./prisma/contract.json` and `./prisma/contract.d.ts`. diff --git a/apps/docs/content/docs/cli/migration-plan.mdx b/apps/docs/content/docs/cli/migration-plan.mdx index 9a95702243..ac181bd963 100644 --- a/apps/docs/content/docs/cli/migration-plan.mdx +++ b/apps/docs/content/docs/cli/migration-plan.mdx @@ -19,11 +19,13 @@ Keep the `db` ref current and you never see the refusal. [`db init`](/cli/db-ini ### The automatic baseline -When `migrations/app/` is empty and the `db` ref points at a contract whose snapshot is stored under `migrations/snapshots/`, one `migration plan` run writes a baseline package from nothing to the ref's contract and, when your emitted contract differs from the ref's, a second package with the delta. Expect one or two new directories in `git status`. The baseline is never applied to a database that already has a marker, because `db migrate` starts from the marker and applies only the migrations after it. +Say `migrations/app/` has no migrations yet, the `db` ref is set, and `migrations/snapshots/` holds a copy of the contract the ref points at. Then `migration plan` writes a baseline: a migration from an empty database to the ref's contract. If your emitted contract has changed since then, the same run also writes a second migration with those changes. Expect one or two new directories in `git status`. -The baseline only creates things, because it is planned from an empty database, so it never removes data. The second package can: `migration plan` writes it without asking, and its output marks each destructive operation in it and warns that it may cause data loss. Review that package before you apply it. +`db migrate` never applies the baseline to a database that already records which contract it matches, such as one you ran `db sign` on. It applies only the migrations after the contract that database records. -When the `db` ref points at a contract state that already has a migration starting from it, `migration plan` writes a second migration that starts from the same state, so the [migration history](/orm/migrations/the-migration-graph) splits into two branches. This happens, for example, after `db migrate` without `--advance-ref db`, which applies migrations but does not update the `db` ref. `migration plan` still writes the migration and prints a warning. A database that has already run the existing migration then has no migration leading to your new contract, so `db migrate` on that database fails with `MIGRATION.PATH_UNREACHABLE`. To build on the newest migration instead, pass `--from` with that migration's directory name. +The baseline only creates things, because it is planned from an empty database, so it never removes data. The second migration can remove data. `migration plan` writes it without asking, and the command's output marks each destructive operation in it and warns that it may cause data loss. Review that migration before you apply it. + +If a migration already starts from the contract the `db` ref points at, `migration plan` writes a second migration that starts from the same contract, so the [migration history](/orm/migrations/the-migration-graph) splits into two branches. This happens, for example, after `db migrate` without `--advance-ref db`, which applies migrations but leaves the `db` ref where it was. `migration plan` still writes the migration and prints a warning. A database that has already run the existing migration then has no migration leading to your new contract, so `db migrate` on that database fails with `MIGRATION.PATH_UNREACHABLE`. To build on the newest migration instead, delete the directory this run wrote and plan again with `--from` set to the newest migration's directory name. The command is offline. It does not need a database connection. diff --git a/apps/docs/content/docs/guides/database/data-migration.mdx b/apps/docs/content/docs/guides/database/data-migration.mdx index 2cb3020314..9e29140489 100644 --- a/apps/docs/content/docs/guides/database/data-migration.mdx +++ b/apps/docs/content/docs/guides/database/data-migration.mdx @@ -292,7 +292,7 @@ npx prisma migration show 20260910T1552_add_status } ``` -Commit `migration.ts`, `ops.json`, and `migration.json` together. If `ops.json` or `migration.json` no longer matches the hash stored for the migration, `db migrate` refuses to run it, and `npx prisma migration check` reports the same problem before you commit. +Commit `migration.ts`, `ops.json`, and `migration.json` together. Recompiling stores a hash of `ops.json` and `migration.json` inside `migration.json`. If either file changes after that, `db migrate` refuses to run the migration, and `npx prisma migration check` reports the same problem before you commit. ## 4. Apply the expand migration @@ -444,7 +444,7 @@ Production runs the same migrations from the same files; only the connection str npx prisma db migrate --db "$PRODUCTION_DATABASE_URL" ``` -It refuses to run a migration whose files no longer match the hash stored for it. To see the route before it runs, add `--show`, which previews it without changing anything. Here is that preview against a production database that shipped the `init` migration and holds live rows, followed by the apply: +It refuses to run a migration whose `ops.json` or `migration.json` changed after it was compiled. If your pipeline deploys each environment to a ref, as [Applying a migration](/orm/migrations/applying-a-migration) shows, add `--to ` to that command. To see the route before it runs, add `--show`, which previews it without changing anything. Here is that preview against a production database that shipped the `init` migration and holds live rows, followed by the apply: ```text no-copy ℹ The following 2 migrations will run: @@ -482,7 +482,7 @@ If `migration plan` stops with `MIGRATION.PLAN_ORIGIN_UNKNOWN`, the `db` ref is :::note -If `db migrate` reports `MIGRATION.HASH_MISMATCH`, you edited `migration.ts` without recompiling. Run `node /migration.ts` and apply again. Never edit `ops.json` by hand. +If `db migrate` reports `MIGRATION.HASH_MISMATCH`, the migration's `ops.json` or `migration.json` changed after it was compiled, for example by a hand edit. Restore both files from Git, or move the edit into `migration.ts` and recompile with `node /migration.ts`, then apply again. Never edit `ops.json` by hand. ::: 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 24dbf16caa..e5eddf0f5a 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` at `8.0.0-rc.17` and `@prisma/orm-postgres` at `8.0.0-rc.12`. The CLI is released separately from the ORM packages, so its version number will not match theirs. Prisma ORM 7 stays at `7.10.0`: the example app in step 1.1 starts on it, and step 1.2 installs `@prisma/prisma7@7.10.0`. +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` at `8.0.0-rc.17` and `@prisma/orm-postgres` at `8.0.0-rc.12`: the CLI is released separately, so the two numbers differ. Prisma ORM 7 stays at `7.10.0`: the example app in step 1.1 starts on it, and step 1.2 installs `@prisma/prisma7@7.10.0`. ::: @@ -281,7 +281,7 @@ export default definePrismaConfig({ }); ``` -After step 2.5, run `npx prisma db sign`. [`db sign`](/cli/db-sign) checks that the database matches the contract Prisma ORM 8 read from `schema.prisma` and records in the database that it does. On this path it does not hand migrations to Prisma ORM 8: Prisma ORM 7 keeps planning and applying them, so after each `prisma7 migrate dev`, run `npx prisma contract emit` and then `npx prisma db sign` again. If you emit without signing, the Prisma ORM 8 client still runs queries, but `npx prisma db verify` reports that the contract no longer matches the one recorded in the database. [Use a Prisma ORM 7 schema](/cli/configuration#use-a-prisma-orm-7-schema) lists the parts of a Prisma ORM 7 schema that Prisma ORM 8 cannot read. +After step 2.5, run `npx prisma db sign`. [`db sign`](/cli/db-sign) checks that the database matches the contract Prisma ORM 8 read from `schema.prisma` and records in the database that it does. Signing does not move your migrations to Prisma ORM 8. Prisma ORM 7 keeps planning and applying them, so after each Prisma ORM 7 migration, whether you ran `prisma7 migrate dev` or `prisma7 migrate deploy`, run `npx prisma contract emit` and then `npx prisma db sign` again. If you emit without signing, the Prisma ORM 8 client still runs queries, but `npx prisma db verify` reports that the contract no longer matches the one recorded in the database. [Use a Prisma ORM 7 schema](/cli/configuration#use-a-prisma-orm-7-schema) lists the parts of a Prisma ORM 7 schema that Prisma ORM 8 cannot read. If you have not started phase 1 yet, one command does phase 1 and this setup for you: @@ -291,7 +291,7 @@ npx prisma@latest orm init --from-prisma7-schema prisma/schema.prisma Write `prisma@latest`, because in a Prisma ORM 7 project `npx prisma` runs version 7. The command moves Prisma ORM 7 to the `prisma7` command and `prisma7.config.ts`, installs the Prisma ORM 8 packages, writes `prisma.config.ts` and a Prisma ORM 8 client in `src/prisma/db.ts`, and emits the contract. [`orm init` on a Prisma ORM 7 project](/cli/orm-init#on-a-prisma-orm-7-project) lists what it changes. Then run `npx prisma db sign` and continue with phase 3, using the `db` client from `src/prisma/db.ts` instead of adding one to `src/db.ts` in step 3.1. -Prisma ORM 7 keeps owning migrations on this path until you are ready for [phase 4](#4-transfer-migration-ownership), where Prisma ORM 8 takes them over. Phase 4 works from a Prisma ORM 8 contract file, so switch to one first: run steps 2.3 to 2.5, and change `contract:` in `prisma.config.ts` back to `"prisma8/contract.prisma"`. Then follow phase 4 as written. Its first step, `db sign`, checks the new contract against the database before Prisma ORM 8 takes over. +Prisma ORM 7 keeps owning migrations on this path until you are ready for [phase 4](#4-transfer-migration-ownership), where Prisma ORM 8 takes them over. Phase 4 needs a contract file written for Prisma ORM 8, so switch to one first: run steps 2.3 to 2.5, and set `contract` in `prisma.config.ts` to `"prisma8/contract.prisma"`, the file those steps create. Then follow phase 4 as written. Its first step, `db sign`, checks the new contract against the database before Prisma ORM 8 takes over. ::: 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 4553ef6fce..d91fcd90ac 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 @@ -22,9 +22,9 @@ On PostgreSQL, the following command moves Prisma ORM 7 to the `prisma7` command npx prisma@latest orm init --from-prisma7-schema prisma/schema.prisma ``` -It does not change `prisma/` or the database. [`orm init` on a Prisma ORM 7 project](/cli/orm-init#on-a-prisma-orm-7-project) lists what it changes. Then run `npx prisma db sign` and continue with [phase 3 of the guide](/guides/upgrade-prisma-orm/postgresql#3-migrate-one-route), moving routes one at a time to the client in `src/prisma/db.ts`. Prisma ORM 7 keeps owning your migrations until the guide's [phase 4](/guides/upgrade-prisma-orm/postgresql#4-transfer-migration-ownership), where Prisma ORM 8 takes them over. Phase 4 works from a Prisma ORM 8 contract file, so before it, generate one with the guide's steps 2.3 to 2.5 and point `contract:` at it. +It does not change `prisma/` or the database. [`orm init` on a Prisma ORM 7 project](/cli/orm-init#on-a-prisma-orm-7-project) lists what it changes. Then run `npx prisma db sign` and continue with [phase 3 of the guide](/guides/upgrade-prisma-orm/postgresql#3-migrate-one-route), moving routes one at a time to the client in `src/prisma/db.ts`. Prisma ORM 7 keeps owning your migrations until the guide's [phase 4](/guides/upgrade-prisma-orm/postgresql#4-transfer-migration-ownership), where Prisma ORM 8 takes them over. Phase 4 needs a contract file written for Prisma ORM 8. Before you start it, create one with the guide's steps 2.3 to 2.5, and set `contract` in `prisma.config.ts` to that file's path. -In a new project, three commands set up Prisma ORM 8: +To set up Prisma ORM 8 in a new project, install `prisma` and `@prisma/orm-postgres`, then run `orm init`: ```npm title="Terminal" npm install --save-dev prisma @@ -156,7 +156,7 @@ export default definePrismaConfig({ The `orm` section takes these keys: -- `contract`: the path to your contract file, or a glob for a [contract in several files](/orm/contract-authoring/psl-syntax#split-the-contract-across-several-files). On PostgreSQL it can also be `prisma7Schema("prisma/schema.prisma")`, with `prisma7Schema` imported from `@prisma/orm-postgres/config` next to `defineConfig`. The schema stays in Prisma ORM 7 syntax, so the changes in the table above do not apply to it, and it needs no `// use prisma-8` line. `contract emit` fails on any part of it that Prisma ORM 8 cannot read, such as a `view` block, and the error names the line and the Prisma ORM 7 edit that removes it; see [Use a Prisma ORM 7 schema](/cli/configuration#use-a-prisma-orm-7-schema). +- `contract`: the path to your contract file, or a glob for a [contract in several files](/orm/contract-authoring/psl-syntax#split-the-contract-across-several-files). On PostgreSQL it can also be `prisma7Schema("prisma/schema.prisma")`, with `prisma7Schema` imported from `@prisma/orm-postgres/config` next to `defineConfig`. The schema stays in Prisma ORM 7 syntax, so the changes in the table above do not apply to it, and it needs no `// use prisma-8` line. `contract emit` fails on any part of it that Prisma ORM 8 cannot read, such as a `view` block, and the error names the line and suggests a change to the Prisma ORM 7 schema; see [Use a Prisma ORM 7 schema](/cli/configuration#use-a-prisma-orm-7-schema). - `db`: the connection. - `output` (optional): where `contract.json` and `contract.d.ts` are written. Default: next to the contract. - `extensions` (optional): database extensions, for example `extensions: [pgvector]` with `import pgvector from "@prisma/orm-extension-pgvector/control"`. See [Using extensions](/orm/extensions/using-extensions). @@ -321,7 +321,7 @@ await db.runtime().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 `db.runtime().execute()` for a count. On PostgreSQL, `db.runtime()` returns the connection directly, so it needs no `await`; the MongoDB client's `db.runtime()` is the one you `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.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`. ### Transaction @@ -358,7 +358,7 @@ Add `@@fullTextIndex([title], name: "post_title_search")` to the `Post` model so ## Not available [#not-available-yet] -Prisma ORM 7 features that have no direct form in Prisma ORM 8, with what to do instead. The status column says which: not available today, available in a different form, coming in the next release, or (for `$extends`) not coming. +Prisma ORM 7 features that have no direct form in Prisma ORM 8, with what to do instead. The status column says whether each feature is not available, available in a different form, or, for `$extends`, replaced by middleware. | Prisma ORM 7 feature | Status | What to do instead | | --- | --- | --- | diff --git a/apps/docs/content/docs/orm/core-concepts.mdx b/apps/docs/content/docs/orm/core-concepts.mdx index f2adc0441e..b566305595 100644 --- a/apps/docs/content/docs/orm/core-concepts.mdx +++ b/apps/docs/content/docs/orm/core-concepts.mdx @@ -163,7 +163,7 @@ Three middleware ship with Prisma ORM at present, and they are in the early stag A **migration** is a recorded change to your database. Most migrations change the database schema from what one contract describes to what another describes, so each one records which contract hash it starts `from` and which it ends at, `to`. A migration that only changes rows, such as a backfill, starts and ends at the same contract state. On disk, a migration is a directory in your repository that holds the change as editable TypeScript (`migration.ts`), the compiled operations that Prisma ORM runs (`ops.json`), and that `from` and `to` metadata (`migration.json`). -Only one of these commands changes a database: [`db migrate`](/cli/db-migrate) applies the recorded migrations to it. The other `migration ...` commands create and inspect the migration files in your repository, and never connect to a database. +Of the commands that work with migrations, only [`db migrate`](/cli/db-migrate) changes a database: it applies the recorded migrations to it. The `migration ...` commands create and inspect the migration files in your repository. Of those, only `migration status` and `migration log` connect to a database, and they only read it. Because every migration records its `from` and `to` hashes, the migrations in a repository form a **graph**: contracts are the nodes, migrations are the edges. When two branches each add a migration and both merge, the graph has a fork and a join, and `db migrate` works out which migrations to run, given the contract state the database matches and the one you name. No renumbering, no rebasing migration files. @@ -192,9 +192,11 @@ The commands compose into four everyday workflows. ```npm npx prisma contract emit npx prisma migration plan --name add_user_phone -npx prisma db migrate +npx prisma db migrate --advance-ref db ``` +`--advance-ref db` records what you just applied, so the next `migration plan` starts from there. [The db ref](/orm/migrations/generating-a-migration#the-db-ref-skipping---from) explains it. + **Prototyping without migration files.** While a schema is still in flux, skip the migration directory and reconcile the database directly. [`db update`](/cli/db-update) diffs the live schema against the emitted contract and applies the difference; `--dry-run` previews it first: ```npm @@ -213,7 +215,7 @@ npx prisma contract emit npx prisma db init --db "$DATABASE_URL" ``` -**Deploying.** A deploy pipeline pins each environment with a ref and migrates to it by name, and that one command is all it needs. `db migrate` refuses to run a migration whose files no longer match its hash, so the pipeline needs no separate check: +**Deploying.** Give each environment a ref, such as `production`, that names the contract state the environment should reach. When a change is ready to ship, point the ref at the new state with [`migration ref set`](/cli/migration-ref), such as `npx prisma migration ref set production `, and commit the ref file it writes under `migrations/app/refs/`. The deploy pipeline then migrates the environment to its ref by name, and that one command is all it needs. `db migrate` refuses to run a migration whose files no longer match its hash, so the pipeline needs no separate check: ```npm npx prisma db migrate --db "$DATABASE_URL" --to production 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 aeadc174b9..7163e43bbf 100644 --- a/apps/docs/content/docs/orm/middleware/authoring-custom-middleware.mdx +++ b/apps/docs/content/docs/orm/middleware/authoring-custom-middleware.mdx @@ -220,7 +220,7 @@ A hook you implement for one kind of call is never called for the other kind, so `beforeCompile` is where you change a query before it runs. It gives you the query as an AST, the abstract syntax tree, which is the query as a tree of typed objects rather than as text. A `SELECT` is one object whose `kind` is `'select'`, whose `from` is the table it reads, and whose `where` is its condition. The hook runs before Prisma ORM turns that tree into SQL text, so a changed tree changes the SQL that reaches the database. -Its argument is called a draft because the SQL text does not exist yet. A draft has `ast`, the tree you change, and `meta`, which you pass through unchanged. `meta.lane` says which API built the query: `'orm-client'` for the ORM API and `'dsl'` for the SQL query builder. You return a copy of the draft with a new `ast`, or `undefined` when there is nothing to change. +Its argument, the draft, is the query before Prisma ORM writes its SQL. A draft has `ast`, the tree you change, and `meta`, which you pass through unchanged. `meta.lane` says which API built the query: `'orm-client'` for the ORM API, and `'dsl'` for the SQL query builder. You return a copy of the draft with a new `ast`, or `undefined` when there is nothing to change. The middleware below adds a condition to every `SELECT` whose own `FROM` clause names the `User` table: @@ -248,11 +248,11 @@ export function scopeUserSelects(condition: () => BinaryExpr | undefined): SqlMi The `kind` check on `from` keeps the middleware to plain tables: a `SELECT` that reads from a subquery has a `from` whose `kind` is `'derived-table-source'`, and one that reads from a function has `'function-source'`. The name it checks, `'User'`, is the table name in your database rather than the model name in your contract, because by this point the query is expressed in tables and columns. The two are the same unless the model sets `@@map`. `draft.ast.withWhere(where)` gives back a new tree with that `WHERE` and leaves the original alone. -These checks read only the table in the query's own `FROM` clause, so `User` rows that a query reaches any other way do not get the condition. When you load related users with `.include(...)` from another model, such as the author of each post, the ORM API reads them inside that other model's query, whose `FROM` is the other table. A SQL query builder query that joins `User` onto another table is not filtered either, for the same reason. +These checks read only the table in the query's own `FROM` clause, so `User` rows that a query reaches any other way do not get the condition. Users you load with `.include(...)` from another model, such as the author of each post, are not filtered, and neither is a SQL query builder query that joins `User` onto another table. The middleware calls `condition` for each `SELECT` whose `FROM` clause names `User`, after the checks above, so the condition can change from one request to the next. You build it from the same classes. Put values through `ParamRef.of` rather than `LiteralExpr.of`, because a `ParamRef` becomes a bound parameter such as `$1` while a literal is pasted into the SQL text itself, so a value that came from one of your users could inject SQL of its own. A `ParamRef` you build by hand has to name the PostgreSQL type of the value with `codec: { codecId: ... }`, and the ids start with `pg/` and end with `@1`, so the `text` column `name` takes `pg/text@1`. Leave that out and the query fails with an error whose `code` is `RUNTIME.PARAM_REF_MISSING_CODEC`. -A request has no tenant of its own, so the middleware reads the value from something in its own scope. The example below keeps it in an `AsyncLocalStorage` from Node.js, which holds one value per request while that request's code runs: +Prisma ORM does not know which request a query belongs to, so the value has to come from a place your condition function can read. The example below keeps it in an `AsyncLocalStorage` from Node.js, which holds one value per request while that request's code runs: ```ts title="src/prisma/db.ts" import { AsyncLocalStorage } from 'node:async_hooks'; @@ -280,7 +280,9 @@ export const db = postgres({ }); ``` -Run each request's queries inside `currentName.run(...)`, and `await` them inside its callback: +This file imports `BinaryExpr` without `type`, because it calls `BinaryExpr.eq(...)`. Keep the `type` on the `BinaryExpr` import in `scope-user-selects.ts`. + +Run each request's queries inside `currentName.run(...)`, and `await` them inside its callback. In a web server, that means wrapping the code of each request handler: ```ts title="src/routes/users.ts" import { currentName, db } from '../prisma/db'; @@ -298,8 +300,6 @@ Do not rely on this middleware alone to keep one tenant's rows away from another ::: -Copy each import as it is written, including the one that says `type` and the one that does not. - The `codecId` depends on the field's type in your contract: | Type in your contract | `codecId` | @@ -314,7 +314,7 @@ The `codecId` depends on the field's type in your contract: The `temporal` in the `DateTime` id means the value reaches your code as a JavaScript `Temporal.Instant` rather than a `Date`. -For a field whose type is not in the table, read the `codecId` off your own client: `db.sql.public.User.columns.name.codecId` is the id for the `name` column of the `User` table, so print it from any script that imports `db`. [`fns.raw` and `.returns()`](/orm/reference/sql-query-builder#fnsraw-and-returns) lists the ids in one place. +For a field whose type is not in the table, read the `codecId` off your own client: `db.sql.public.User.columns.name.codecId` is the id for the `name` column of the `User` table, so print it from any script that imports `db`. The SQL query builder reference lists every id, in its section on [`fns.raw` and `.returns()`](/orm/reference/sql-query-builder#fnsraw-and-returns). `BinaryExpr` has one method per comparison (`eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `like`, `in`, `notIn`), and `AndExpr.of` and `OrExpr.of` combine conditions. They all come from `@prisma/orm-postgres/relational-core/ast`, so your editor lists them once you import from there. @@ -353,7 +353,9 @@ export function userFixture(): SqlMiddleware { Prisma ORM decodes the rows you return the same way it decodes rows that came from the database, so your caller gets the types the contract gives those columns, even though you handed back plain driver values. -The last three checks make the fixture answer only a `SELECT` that reads the `User` table directly, in the same way the `beforeCompile` example picks its queries. Matching on the SQL text instead, such as `plan.sql.includes('FROM "public"."User"')`, also catches `DELETE FROM "public"."User"`, so an ORM `.delete()` would get a made-up row back and delete nothing. On an `UPDATE` or a `DELETE` the table name is at `plan.ast.table.name`. `plan.meta.lane` tells you which API built a query, but nothing on the plan says which model or which ORM call it came from, so the table name in the tree is the only thing you have to match on. +The last three checks make the fixture answer only a `SELECT` that reads the `User` table directly, in the same way the `beforeCompile` example picks its queries. Matching on the SQL text instead, such as `plan.sql.includes('FROM "public"."User"')`, also catches `DELETE FROM "public"."User"`, so an ORM `.deleteAll()` would get a made-up row back and delete nothing. On an `UPDATE` or a `DELETE` the table name is at `plan.ast.table.name`. + +`plan.meta.lane` tells you which API built a query, but nothing on the plan says which model or which ORM call it came from, so the table name in the tree is the only thing you have to match on. The first check, on `ctx.scope`, lets every query inside a transaction, or on a connection you took from the pool, go on to the database, so the fixture answers only queries sent on `db` directly. Keep it, because an ORM `.update()` or `.delete()` reads its row inside a transaction before it writes, and a made-up row there would send the write to the wrong row. 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 f8f68402b4..bcf3ab50a6 100644 --- a/apps/docs/content/docs/orm/middleware/how-middleware-works.mdx +++ b/apps/docs/content/docs/orm/middleware/how-middleware-works.mdx @@ -123,7 +123,7 @@ Prisma ORM calls your middleware in the order you listed them in the `middleware ### beforeCompile: rewrite the query -Reach for `beforeCompile` when you want to change what a query asks for rather than only look at it. It runs on both kinds of query, and it hands you a draft with two fields: `meta`, the metadata Prisma ORM keeps about the query, and `ast`, the query as a tree of typed objects. The tree is the part you can change, because the SQL text has not been written yet. Return a copy of the draft with a changed `ast` and Prisma ORM runs your version instead, which is how you add a condition to queries without editing a single call site. Return `undefined` and the query goes through as written. +Reach for `beforeCompile` when you want to change what a query asks for rather than only look at it. It runs on both kinds of query, and it hands you a draft with two fields: `meta`, facts about the query that you read but do not change, such as which API built it, and `ast`, the query as a tree of typed objects. The tree is the part you can change, because the SQL text has not been written yet. Return a copy of the draft with a changed `ast` and Prisma ORM runs your version instead, which is how you add a condition to queries without editing a single call site. Return `undefined` and the query goes through as written. The middleware below adds a condition to every `SELECT` whose own `FROM` clause names the `User` table: @@ -153,7 +153,7 @@ The three checks above are how you say "only `SELECT`s, and only ones over the ` These checks read only the table in the query's own `FROM` clause, so `User` rows that a query loads with `.include(...)` or through a join are not filtered. Do not rely on a middleware like this alone to keep one tenant's rows away from another. -The function you pass in returns the comparison to add, or `undefined` to add none, and the middleware calls it for each `SELECT` whose `FROM` clause names `User`, after the three checks. You build the comparison yourself, and the types for building one come from `@prisma/orm-postgres/relational-core/ast`. A value you compare against needs its `codecId`, which says how the value is stored: `pg/` plus the PostgreSQL type name plus `@1`, so text is `pg/text@1` and a 32-bit integer is `pg/int4@1`: +The function you pass in returns the comparison to add, or `undefined` to add none, and the middleware calls it for each `SELECT` whose `FROM` clause names `User`, after the three checks. You build the comparison yourself, and the types for building one come from `@prisma/orm-postgres/relational-core/ast`. A value you compare against needs its `codecId`, an id that says how the value is stored, such as `pg/text@1` for text and `pg/int4@1` for a 32-bit integer. [Authoring custom middleware](/orm/middleware/authoring-custom-middleware#rewrite-queries-with-beforecompile) lists the id for each field type: ```ts title="src/prisma/db.ts (excerpt)" import { BinaryExpr, ColumnRef, ParamRef } from '@prisma/orm-postgres/relational-core/ast'; @@ -212,7 +212,7 @@ The rows you return are plain objects, keyed by the column aliases in the SQL, h { id: 3, email: 'mia+1@prisma.io', createdAt: '2026-09-17 08:00:08.775+00' } ``` -So for a query whose SQL is `SELECT "User"."id" AS "id", "User"."email" AS "email" FROM "public"."User" LIMIT 5`, `return { rows: [{ id: 1, email: 'mia@prisma.io' }] }` is a complete answer to it. Prisma ORM converts the rows you return the same way it converts rows from the database, so a `createdAt` you return as a string reaches a caller on the ORM API as a `Temporal.Instant`, the type a `DateTime` field has, not as a string. This is how the cache serves repeated reads. +So for a query whose SQL is `SELECT "User"."id" AS "id", "User"."email" AS "email" FROM "public"."User" LIMIT 5`, `return { rows: [{ id: 1, email: 'mia@prisma.io' }] }` is a complete answer to it. Prisma ORM converts the rows you return the same way it converts rows from the database. So if you return a `createdAt` as a string, a caller on the ORM API gets a `Temporal.Instant`, which is the type of a `DateTime` field. Before-hooks still run on a query that an intercept hook answers, because every middleware's before-hooks run before any intercept hook, whatever order you list the middleware in. So in the setup at the top of this page, where the cache comes first in the array, `lints` still checks every query before the cache answers it. 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 7864cbcf7c..59fa49246c 100644 --- a/apps/docs/content/docs/orm/migrations/applying-a-migration.mdx +++ b/apps/docs/content/docs/orm/migrations/applying-a-migration.mdx @@ -47,7 +47,7 @@ npx prisma db migrate --show --db "$PRODUCTION_DATABASE_URL" npx prisma db migrate --db "$PRODUCTION_DATABASE_URL" ``` -The output in the rest of this section comes from an earlier point in the same project than the run at the top of this page: the newest migration is `add_user_phone`, and the production database has applied only `init`, so `add_user_phone` is pending. +The examples in the rest of this section use a project with two migrations, `init` and `add_user_phone`, where the production database has applied only `init`. `npx prisma migration status --db "$PRODUCTION_DATABASE_URL"` never changes the database, so you can run it against production whenever you want to know which contract state that database matches. It draws your migration history, marks each migration as applied or pending, and shows which contract state the database matches: @@ -145,13 +145,15 @@ In development, add `--advance-ref db` so the next `migration plan` starts from - each migration's directory under `migrations/app/` - the refs in `migrations/app/refs/`, such as `prod.json`, which `db migrate --to prod` reads -- the directories `migration plan` added under `migrations/snapshots/`, one per contract state +- the copies of your contract that `migration plan` saved under `migrations/snapshots/`, one directory per contract state - one directory per [extension package](#extension-spaces) that ships migrations, such as `migrations/pgvector/` - `contract.prisma`, `contract.json`, and `contract.d.ts` -When you deploy by hand, run the three commands in [Check before, preview, then apply](#check-before-preview-then-apply). The point of the `migration check` step is to tell you that what you are about to apply is still what was reviewed. In a pipeline, the deploy job runs only the last of them, `db migrate` with `--to `, which that section explains. That one command is all the pipeline needs, because `db migrate` refuses to run when a migration's files no longer match its hash. +When you deploy by hand, run the three commands in [Check before, preview, then apply](#check-before-preview-then-apply). The point of the `migration check` step is to tell you that what you are about to apply is still what was reviewed. In a pipeline, the deploy job runs only the last of them, `db migrate` with `--to `, which that section explains. That one command is all the pipeline needs, because `db migrate` refuses to run when a migration's `ops.json` or `migration.json` no longer matches the hash recorded for it. -When a migration's `ops.json` or `migration.json` no longer matches the `migrationHash` in its `migration.json`, `migration check` fails with an error whose `code` is `MIGRATION.CHECK_HASH_MISMATCH`, as [Editing a migration](/orm/migrations/editing-a-migration) explains. To fix it, either restore the migration's files from Git, or move the hand edit into `migration.ts` and recompile. If the error comes back right after you recompile, and the migration's backfill updates a model with a `temporal.updatedAtString()` column, recompiling cannot fix it, and [Editing a migration](/orm/migrations/editing-a-migration#worked-example-making-a-column-required) shows how to write that backfill instead. +Each migration's `migration.json` records a hash, `migrationHash`, computed from the rest of `migration.json` and from `ops.json`. If either file changes after the hash was recorded, for example because you edited it by hand, `migration check` fails with an error whose `code` is `MIGRATION.CHECK_HASH_MISMATCH`, as [Editing a migration](/orm/migrations/editing-a-migration) explains. To fix it, either restore the migration's files from Git, or move the hand edit into `migration.ts` and recompile with `node migrations/app//migration.ts`. + +One case needs a different fix. If the error comes back right after you recompile, and the migration's backfill updates a model with a `temporal.updatedAtString()` column, recompiling cannot fix it. [Editing a migration](/orm/migrations/editing-a-migration#worked-example-making-a-column-required) shows how to write that backfill instead. When a contract snapshot under `migrations/snapshots/` no longer matches the hash in its directory name, `migration check` fails with an error whose `code` is `MIGRATION.CHECK_SNAPSHOT_CONTENT_MISMATCH`. Other commands that read that snapshot, such as `migration plan --from ` or `migration ref set prod `, fail with a different code, `MIGRATION.CONTRACT_SNAPSHOT_CONTENT_MISMATCH`. Recompiling `migration.ts` fixes neither error, so restore the edited snapshot from Git. MongoDB snapshots are not checked yet. diff --git a/apps/docs/content/docs/orm/migrations/editing-a-migration.mdx b/apps/docs/content/docs/orm/migrations/editing-a-migration.mdx index 10de472d99..2cf31254b8 100644 --- a/apps/docs/content/docs/orm/migrations/editing-a-migration.mdx +++ b/apps/docs/content/docs/orm/migrations/editing-a-migration.mdx @@ -40,7 +40,7 @@ to: 866ab885a8a4bd8d9f5adc47784c068be7895a7182c55cc29f9f9967d3019484 app space: migrations/app/20260707T1008_add_display_name ``` -In that output, `self-emit` is what the CLI calls recompiling, and `app space` is the directory the new migration was written to. `migration plan` writes the change as three steps rather than one, because rows that already exist need a value for the new required column: `addColumn` adds the column as nullable, `dataTransform` holds the backfill you write, and `setNotNull` makes the column required: +In that output, `self-emit` is what the CLI calls recompiling, and `app space` is the directory the new migration was written to. The output says `node migration.ts`, but run the file from your project root, as `node migrations/app//migration.ts`, for the reason given above. `migration plan` writes the change as three steps rather than one, because rows that already exist need a value for the new required column: `addColumn` adds the column as nullable, `dataTransform` holds the backfill you write, and `setNotNull` makes the column required: ```ts title="migration.ts (as planned)" override get operations() { @@ -172,7 +172,7 @@ model User { Keep `// use prisma-8` as the first line of the copy. `contract emit` skips any `.prisma` file that does not start with it, and because `intermediate.prisma` is the only file in the intermediate contract, emitting it without that line fails with an error whose `code` is `CONTRACT.SOURCE_LOAD_FAILED`. -In the same migration directory, add `intermediate.config.ts`, a second config file whose `contract` is `intermediate.prisma`. It needs no `db.connection`, because emitting a contract never connects to a database. If your contract uses an extension pack, such as pgvector, list the same `extensions` here as in your project's `prisma.config.ts`, because this file does not take them from it: +In the same migration directory, add `intermediate.config.ts`, a second config file whose `contract` is `intermediate.prisma`. It needs no `db.connection`, because emitting a contract never connects to a database. If your contract uses an extension package, such as pgvector, add the same `extensions` array here as in your project's `prisma.config.ts`, such as `extensions: [pgvector]` inside `ormConfig({ ... })`, because this file does not take it from there: ```ts title="intermediate.config.ts" import { definePrismaConfig } from 'prisma/config'; @@ -198,7 +198,11 @@ The command writes `intermediate.json` and `intermediate.d.ts` next to `intermed └── ops.json ``` -Now fill in this migration's own planned `migration.ts`, rather than copying the `displayName` file, whose snapshot imports point at other contracts. In the listing, `` and `` stand for the hashes your planned file already imports, so keep those imports as they are. The listing below builds `db` from the intermediate contract and passes that same contract to `dataTransform`, and those two have to be the same contract: if you pass a different one, `node migration.ts` fails with an error whose `code` is `MIGRATION.DATA_TRANSFORM_CONTRACT_MISMATCH`. The listing also moves the planned `dropColumn` for `isAdmin` to after the backfill, so `isAdmin` is still there when the backfill reads it, and its two `run` callbacks run in the order you list them, so the second one sets `member` only on the rows the first one left empty. +Now fill in this migration's own planned `migration.ts`, rather than copying the `displayName` file, whose snapshot imports point at other contracts. In the listing, `` and `` stand for the hashes your planned file already imports, so keep those imports as they are. + +The listing builds `db` from the intermediate contract and passes that same contract to `dataTransform`. Those two have to be the same contract: if you pass a different one, `node migration.ts` fails with an error whose `code` is `MIGRATION.DATA_TRANSFORM_CONTRACT_MISMATCH`. + +The listing also moves the planned `dropColumn` for `isAdmin` to after the backfill, so `isAdmin` is still there when the backfill reads it. The backfill's `dataTransform` has two `run` callbacks, which run in the order you list them, so the second one sets `member` only on the rows the first one left empty. ```ts title="migration.ts (filled in)" #!/usr/bin/env -S node @@ -304,7 +308,7 @@ To find your current contract's hash, run `npx prisma migration graph` and look npx prisma migration new --name backfill_scores --from ``` -If you leave `--from` off, `migration new` starts from the `db` ref, and after `npx prisma db migrate --advance-ref db` that ref names your current contract, so the migration would start and end at the same state. `migration new` refuses that case only when you leave `--from` off: it stops with an error whose `code` is `MIGRATION.NO_CHANGES`, which tells you to change your contract first, or to pass `--from` if you want a data-only migration. Passing the same hash with `--from` is how you tell it that starting and ending at the same state is what you want. +For a data-only migration, always pass `--from`. Without it, `migration new` starts from the `db` ref, which names your current contract after `db migrate --advance-ref db`, so the migration would start and end at the same state. Without `--from`, `migration new` takes that as a mistake and stops with an error whose `code` is `MIGRATION.NO_CHANGES`, which tells you to change your contract first, or to pass `--from` if you want a data-only migration. Write your operations in the new `migration.ts` and recompile it. Until you do, the migration is still the empty one the command wrote, so `npx prisma migration check` and `npx prisma migration plan` both fail. 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 ac85ee4746..905e1c2643 100644 --- a/apps/docs/content/docs/orm/migrations/generating-a-migration.mdx +++ b/apps/docs/content/docs/orm/migrations/generating-a-migration.mdx @@ -26,7 +26,7 @@ model User { } ``` -Keep `// use prisma-8` as the first line of `contract.prisma`, and of every other `.prisma` file if you [split your contract across several files](/orm/contract-authoring/psl-syntax#split-the-contract-across-several-files). `contract emit` and the Prisma editor extension read only the `.prisma` files that start with it and skip the others without a warning, and when no file starts with it, `contract emit` fails with an error whose `code` is `CONTRACT.SOURCE_LOAD_FAILED`. +Keep `// use prisma-8` as the first line of `contract.prisma`. If you [split your contract across several files](/orm/contract-authoring/psl-syntax#split-the-contract-across-several-files), every one of them needs that line too. `contract emit` and the Prisma editor extension read only the `.prisma` files that start with it, and skip the others without a warning. If no file starts with it, `contract emit` fails with an error whose `code` is `CONTRACT.SOURCE_LOAD_FAILED`. Run both commands, passing `--name init` so that the new migration directory is named `_init`: @@ -150,7 +150,7 @@ Once your first migration is applied, every change you make after that follows t 2. Run `npx prisma contract emit`. If you skip this, `migration plan` reads the `contract.json` from before your edit, so it either prints `No changes detected` or plans a migration that is missing your latest change. 3. Run `npx prisma migration plan --name `. 4. Review what it planned with `npx prisma migration show `, where `` is the new migration's directory name. If you edit `migration.ts`, [recompile it](/orm/migrations/editing-a-migration) before you go any further, because until you do, your edit is not part of what will run. -5. Apply it. In development that is `npx prisma db migrate --advance-ref db`, and in CI and production it is `npx prisma db migrate` without the flag. +5. Apply it. In development that is `npx prisma db migrate --advance-ref db`. In CI and production it is `npx prisma db migrate --to `, without `--advance-ref`, where the ref names the state that environment should reach. The step that is easy to get out of order is the last one, and the symptom is a migration that repeats work you already planned. If you plan a second migration before you have applied the first one with `--advance-ref db`, the `db` ref still names the older contract state, so Prisma ORM plans the first migration's changes a second time. To fix that, delete the directory of the repeated migration, run [`npx prisma migration ref set db `](/cli/migration-ref) with the name of the directory whose changes it repeated, and plan again. Deleting the directory is how you discard any migration that has not run on a database, and the matching directory under `migrations/snapshots/` can stay. @@ -220,7 +220,7 @@ npx prisma migration graph npx prisma migration check ``` -`migration check` also tells you the result in its exit code, if you run it from a script. `0` means every check passed, `4` means an integrity failure such as a missing file or an `ops.json` that someone edited by hand, and `2` means it could not find a migration you named. The one thing it cannot tell you is whether you remembered to [recompile](/orm/migrations/editing-a-migration) after your last edit. You can check that yourself by staging the migration with `git add` and then recompiling it, because if `git status` afterwards shows no unstaged change to `ops.json` or `migration.json`, they were already up to date. This does not work for a backfill on a model with an [`updatedAt` column](/orm/migrations/editing-a-migration#worked-example-making-a-column-required). +`migration check` also tells you the result in its exit code, if you run it from a script. `0` means every check passed, `4` means an integrity failure such as a missing file or an `ops.json` that someone edited by hand, and `2` means it could not find a migration you named. The one thing it cannot tell you is whether you remembered to [recompile](/orm/migrations/editing-a-migration) after your last edit. To check that yourself, stage the migration with `git add`, then recompile it. If `git status` then shows no unstaged change to `ops.json` or `migration.json`, they were already up to date. This does not work for a backfill on a model with an `updatedAt` column. [Editing a migration](/orm/migrations/editing-a-migration#worked-example-making-a-column-required) explains why, and how to write that backfill. :::note[What's early] 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 31df4a586f..249e53d378 100644 --- a/apps/docs/content/docs/orm/migrations/how-migrations-work.mdx +++ b/apps/docs/content/docs/orm/migrations/how-migrations-work.mdx @@ -19,7 +19,7 @@ You run this loop many times a day: 3. **Review it**: before anything touches the database, run `npx prisma migration show ` to see the operations and SQL that `db migrate` will run. If you want the migration to change rows as well, [edit `migration.ts` and recompile it](/orm/migrations/editing-a-migration) by running `node migrations/app//migration.ts` from your project root, which rewrites `ops.json`. Nothing extra is needed to run that file, because the Prisma ORM CLI already requires Node.js 22.18 or later, which runs `migration.ts` directly. 4. **Apply it**: `db migrate` starts by reading the database's marker, the record in the database of which contract state it matches, so it knows how much of your history the database has already seen, and then runs the migrations from that state to your current contract. In development, add [`--advance-ref db`](/orm/migrations/generating-a-migration#the-db-ref-skipping---from) so the next `migration plan` starts from what you just applied. -Say your contract is the one in [Generating a migration](/orm/migrations/generating-a-migration#your-first-migration), whose `User` model is stored in the table `user` because it sets `@@map("user")`, and you applied its first migration with `npx prisma db migrate --advance-ref db`. With a database connection set up, as in the [quickstart](/prisma-orm/quickstart/postgresql), add an optional `phone String?` field to `User`, then run: +This example uses the contract from [Generating a migration](/orm/migrations/generating-a-migration#your-first-migration), whose `User` model is stored in the table `user` because it sets `@@map("user")`. Its first migration is already applied, with `npx prisma db migrate --advance-ref db`, and a database connection is set up as in the [quickstart](/prisma-orm/quickstart/postgresql). Add an optional `phone String?` field to `User`, then run: ```npm npx prisma contract emit @@ -57,7 +57,7 @@ The `app space:` line tells you which migration history the new directory was wr The first time you run `migration plan`, there are no migrations and no `db` ref on disk yet, so there is no earlier state to compare against and Prisma ORM plans as if the database were empty. If you want it to start somewhere else, pass `--from`, such as `--from 20260707T1006_add_user_phone` for the state after that migration. The [`migration plan` reference](/cli/migration-plan#options) lists the other forms `--from` accepts, and [The db ref](/orm/migrations/generating-a-migration#the-db-ref-skipping---from) covers what `migration plan` starts from when you leave `--from` off. -`db migrate` needs a database to talk to, and it takes that from `db.connection` in [`prisma.config.ts`](/orm/contract-authoring/the-data-contract), such as `db: { connection: process.env["DATABASE_URL"]! }`, unless you pass a URL with the `--db` flag. In CI and production, run plain `npx prisma db migrate`. +`db migrate` needs a database to talk to, and it takes that from `db.connection` in [`prisma.config.ts`](/orm/contract-authoring/the-data-contract), such as `db: { connection: process.env["DATABASE_URL"]! }`, unless you pass a URL with the `--db` flag. In CI and production, run `npx prisma db migrate --to `, where the ref names the state that environment should reach, as [Applying a migration](/orm/migrations/applying-a-migration) explains. :::note[If you used Prisma ORM 8 before 8.0.0-rc.12] diff --git a/apps/docs/content/docs/orm/migrations/the-migration-graph.mdx b/apps/docs/content/docs/orm/migrations/the-migration-graph.mdx index e6fccaa09f..22dbaf065c 100644 --- a/apps/docs/content/docs/orm/migrations/the-migration-graph.mdx +++ b/apps/docs/content/docs/orm/migrations/the-migration-graph.mdx @@ -109,7 +109,7 @@ When no chain of migrations leads from the marker to the target, the run fails w | Which contract state does my database match, and what would `db migrate` run next? | `npx prisma migration status` | Yes | | What has actually been applied, and when? | `npx prisma migration log` | Yes | -`migration log` reads the **ledger**, so it tells you what actually ran rather than what should have run, and a rollback appears there as one more applied migration instead of erasing anything. Run `migration check` after you edit a migration by hand or move a ref, because it catches a hand-edited migration file or a ref naming a state that does not exist, and it needs no database connection. A deploy pipeline does not need it: `db migrate --to ` refuses a hand-edited migration file on its own. See [Reviewing what you planned](/orm/migrations/generating-a-migration#reviewing-what-you-planned) for its exit codes. +`migration log` reads the **ledger**, so it tells you what actually ran rather than what should have run, and a rollback appears there as one more applied migration instead of erasing anything. Run `migration check` before you commit a migration you edited, and after you move a ref. It catches an `ops.json` or `migration.json` that was changed by hand, and a ref naming a state that does not exist, and it needs no database connection. A deploy pipeline does not need it: `db migrate --to ` refuses a hand-edited migration file on its own. See [Reviewing what you planned](/orm/migrations/generating-a-migration#reviewing-what-you-planned) for its exit codes. ### Try it on real fixtures @@ -176,7 +176,9 @@ Nothing above changes with your database, because the graph and the commands are ## Adding a migration you write yourself -`npx prisma migration new` writes an empty migration for a change you write yourself, such as a data update. It always ends at the contract state in your current `contract.json`, and without `--from` it starts where `migration plan` would: at the `db` ref, or from an empty database when there are no migrations and no `db` ref yet. When there are migrations but no `db` ref, it refuses and asks for `--from`. [`migration new`](/cli/migration-new) lists every case. Its `--from` accepts less than the one on `migration plan`: it takes only the contract hash in the `to` field of an existing migration's `migration.json`, or any start of that hash that matches only one migration's `to` hash, such as the 7 characters `migration graph` shows. It does not take a ref name, a migration directory name, or one of the `@` tokens. So to start from `e377d00` in the drawing above, run `npx prisma migration new --name backfill --from e377d00`. Because `e377d00` is also the `@contract` state, that migration starts and ends at the same state, which is what a data-only migration does. +`npx prisma migration new` writes an empty migration for a change you write yourself, such as a data update. The migration always ends at the contract state in your current `contract.json`. Without `--from`, it starts where `migration plan` would: at the `db` ref, or at an empty database when there are no migrations and no `db` ref yet. When there are migrations but no `db` ref, it stops and asks for `--from`. [`migration new`](/cli/migration-new) lists every case. + +Unlike the `--from` of `migration plan`, the `--from` of `migration new` takes only a contract hash that an existing migration ends at, which is the `to` hash in that migration's `migration.json`. You can shorten the hash to its first characters, such as the 7 that `migration graph` shows, as long as they match only one migration's `to` hash. It does not take a ref name, a migration directory name, or an `@` name such as `@db`. So to start from `e377d00` in the drawing above, run `npx prisma migration new --name backfill --from e377d00`. Because `e377d00` is also the `@contract` state, that migration starts and ends at the same state, which is what a data-only migration does. ## Release-candidate limitations