payload
payload copied to clipboard
feat: expose `session` / `db` in migrations to use the active transaction with the database directly
This PR adds a feature which fixes another issue with migrations in Postgres and does few refactors that significantly reduce code duplication.
Previously, if you needed to use the underlying database directly in migrations with the active transaction (for example to execute raw SQL), created from payload create:migration
, as req
doesn't work there you had to do something like this:
// Postgres
export async function up({ payload, req }: MigrateUpArgs): Promise<void> {
const db = payload.db.sessions?.[await req.transactionID!].db ?? payload.db
const { rows: posts } = await db.execute(sql`SELECT * from posts`)
}
// MongoDB
export async function up({ payload, req }: MigrateUpArgs): Promise<void> {
const session = payload.db.sessions?.[await req.transactionID!]
const posts = await payload.db.collections.posts.collection.find({ session }).toArray()
}
Which was:
- Awkward to write
- Not documented anywhere
Now, we expose session
and db
to up
and down
functions for you:
MongoDB:
import { type MigrateUpArgs } from '@payloadcms/db-mongodb'
export async function up({ session, payload, req }: MigrateUpArgs): Promise<void> {
const posts = await payload.db.collections.posts.collection.find({ session }).toArray()
}
Postgres:
import { type MigrateUpArgs, sql } from '@payloadcms/db-postgres'
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
const { rows: posts } = await db.execute(sql`SELECT * from posts`)
}
SQLite:
import { type MigrateUpArgs, sql } from '@payloadcms/db-sqlite'
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
const { rows: posts } = await db.run(sql`SELECT * from posts`)
}
This actually was a thing with Postgres migrations, we already were passing db
, but:
- Only for
up
and when runningpayload migrate
, not for example withpayload migrate:fresh
- Not documented neither in TypeScript or docs.
By ensuring we use db
, this also fixes an issue that affects all Postgres/SQLite migrations:
Currently, if we run payload migration:create
with the postgres adapter we get a file like this:
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
export async function up({ payload, req }: MigrateUpArgs): Promise<void> {
await payload.db.drizzle.execute(sql`
CREATE TABLE IF NOT EXISTS "users" (
"id" serial PRIMARY KEY NOT NULL,
);
Looks good?
Not exactly!
payload.db.drizzle.execute()
doesn't really use the current transaction which can lead to some problems.
Instead, it should use the db
from payload.db.sessions?.[await req.transactionID!].db
because that's where we store our Drizzle instance with the transaction.
But now, if we generate payload migrate:create
we get:
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "users" (
"id" serial PRIMARY KEY NOT NULL,
);
Which is what we want, as the db
is passed correctly here:
https://github.com/payloadcms/payload/blob/76428373e4ad94ec29dd58ee293a68b0b67429dc/packages/drizzle/src/migrate.ts#L88-L90
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
const dbWithTransaction = payload.db.sessions?.[await req.transactionID!].db
payload.logger.info({ one: db === dbWithTransaction })
payload.logger.info({ two: db === payload.db.drizzle })
Additionally, this PR refactors:
-
createMigration
with Drizzle - now we have sharablebuildCreateMigration
in@payloadcms/drizzle
to reduce copy-pasting of the same logic. - the
v2-v3
relationships migration for Postgres is now shared betweendb-postgres
anddb-vercel-postgres
, again to reduce copy-paste.