diff --git a/packages/drizzle/src/migrateDown.ts b/packages/drizzle/src/migrateDown.ts index 20eb8714d7..09169b125a 100644 --- a/packages/drizzle/src/migrateDown.ts +++ b/packages/drizzle/src/migrateDown.ts @@ -50,7 +50,8 @@ export async function migrateDown(this: DrizzleAdapter): Promise { msg: `Migrated down: ${migrationFile.name} (${Date.now() - start}ms)`, }) - const tableExists = await migrationTableExists(this) + const tableExists = await migrationTableExists(this, db) + if (tableExists) { await payload.delete({ id: migration.id, diff --git a/packages/drizzle/src/migrateRefresh.ts b/packages/drizzle/src/migrateRefresh.ts index 1a56c825a1..e95133a0d6 100644 --- a/packages/drizzle/src/migrateRefresh.ts +++ b/packages/drizzle/src/migrateRefresh.ts @@ -54,7 +54,7 @@ export async function migrateRefresh(this: DrizzleAdapter) { msg: `Migrated down: ${migration.name} (${Date.now() - start}ms)`, }) - const tableExists = await migrationTableExists(this) + const tableExists = await migrationTableExists(this, db) if (tableExists) { await payload.delete({ collection: 'payload-migrations', diff --git a/packages/drizzle/src/migrateReset.ts b/packages/drizzle/src/migrateReset.ts index ce0d7a3546..99d977a716 100644 --- a/packages/drizzle/src/migrateReset.ts +++ b/packages/drizzle/src/migrateReset.ts @@ -45,7 +45,7 @@ export async function migrateReset(this: DrizzleAdapter): Promise { msg: `Migrated down: ${migrationFile.name} (${Date.now() - start}ms)`, }) - const tableExists = await migrationTableExists(this) + const tableExists = await migrationTableExists(this, db) if (tableExists) { await payload.delete({ id: migration.id, diff --git a/packages/drizzle/src/utilities/migrationTableExists.ts b/packages/drizzle/src/utilities/migrationTableExists.ts index 970de625a5..bbc397a637 100644 --- a/packages/drizzle/src/utilities/migrationTableExists.ts +++ b/packages/drizzle/src/utilities/migrationTableExists.ts @@ -1,6 +1,11 @@ -import type { DrizzleAdapter } from '../types.js' +import type { LibSQLDatabase } from 'drizzle-orm/libsql' -export const migrationTableExists = async (adapter: DrizzleAdapter): Promise => { +import type { DrizzleAdapter, PostgresDB } from '../types.js' + +export const migrationTableExists = async ( + adapter: DrizzleAdapter, + db?: LibSQLDatabase | PostgresDB, +): Promise => { let statement if (adapter.name === 'postgres') { @@ -20,7 +25,7 @@ export const migrationTableExists = async (adapter: DrizzleAdapter): Promise { + if (existsSync(path.resolve(dirname, 'migrations'))) { + rmSync(path.resolve(dirname, 'migrations'), { force: true, recursive: true }) + } +} + +describe('SQL migrations', () => { + // If something fails - an error will be thrown. + // eslint-disable-next-line jest/expect-expect + it('should up and down migration successfully', async () => { + clearMigrations() + + const { databaseAdapter } = await import(path.resolve(dirname, '../../databaseAdapter.js')) + + const init = databaseAdapter.init + + // set options + databaseAdapter.init = ({ payload }) => { + const adapter = init({ payload }) + adapter.migrationDir = path.resolve(dirname, 'migrations') + adapter.push = false + return adapter + } + + const config = await buildConfig({ + db: databaseAdapter, + secret: 'secret', + collections: [ + { + slug: 'users', + auth: true, + fields: [], + }, + ], + }) + + const payload = await getPayload({ config }) + + await payload.db.createMigration({ payload }) + await payload.db.migrate() + await payload.db.migrateDown() + + await payload.db.dropDatabase({ adapter: payload.db as any }) + await payload.db.destroy?.() + }) +}) diff --git a/test/database/up-down-migration/migrations/20250328_185055.json b/test/database/up-down-migration/migrations/20250328_185055.json new file mode 100644 index 0000000000..7ef134f6aa --- /dev/null +++ b/test/database/up-down-migration/migrations/20250328_185055.json @@ -0,0 +1,463 @@ +{ + "version": "6", + "dialect": "sqlite", + "tables": { + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reset_password_token": { + "name": "reset_password_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reset_password_expiration": { + "name": "reset_password_expiration", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "salt": { + "name": "salt", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "login_attempts": { + "name": "login_attempts", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "lock_until": { + "name": "lock_until", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "users_updated_at_idx": { + "name": "users_updated_at_idx", + "columns": ["updated_at"], + "isUnique": false + }, + "users_created_at_idx": { + "name": "users_created_at_idx", + "columns": ["created_at"], + "isUnique": false + }, + "users_email_idx": { + "name": "users_email_idx", + "columns": ["email"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "payload_locked_documents": { + "name": "payload_locked_documents", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "global_slug": { + "name": "global_slug", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + } + }, + "indexes": { + "payload_locked_documents_global_slug_idx": { + "name": "payload_locked_documents_global_slug_idx", + "columns": ["global_slug"], + "isUnique": false + }, + "payload_locked_documents_updated_at_idx": { + "name": "payload_locked_documents_updated_at_idx", + "columns": ["updated_at"], + "isUnique": false + }, + "payload_locked_documents_created_at_idx": { + "name": "payload_locked_documents_created_at_idx", + "columns": ["created_at"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "payload_locked_documents_rels": { + "name": "payload_locked_documents_rels", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_id": { + "name": "parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "users_id": { + "name": "users_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "payload_locked_documents_rels_order_idx": { + "name": "payload_locked_documents_rels_order_idx", + "columns": ["order"], + "isUnique": false + }, + "payload_locked_documents_rels_parent_idx": { + "name": "payload_locked_documents_rels_parent_idx", + "columns": ["parent_id"], + "isUnique": false + }, + "payload_locked_documents_rels_path_idx": { + "name": "payload_locked_documents_rels_path_idx", + "columns": ["path"], + "isUnique": false + }, + "payload_locked_documents_rels_users_id_idx": { + "name": "payload_locked_documents_rels_users_id_idx", + "columns": ["users_id"], + "isUnique": false + } + }, + "foreignKeys": { + "payload_locked_documents_rels_parent_fk": { + "name": "payload_locked_documents_rels_parent_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "payload_locked_documents", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_locked_documents_rels_users_fk": { + "name": "payload_locked_documents_rels_users_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "users", + "columnsFrom": ["users_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "payload_preferences": { + "name": "payload_preferences", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + } + }, + "indexes": { + "payload_preferences_key_idx": { + "name": "payload_preferences_key_idx", + "columns": ["key"], + "isUnique": false + }, + "payload_preferences_updated_at_idx": { + "name": "payload_preferences_updated_at_idx", + "columns": ["updated_at"], + "isUnique": false + }, + "payload_preferences_created_at_idx": { + "name": "payload_preferences_created_at_idx", + "columns": ["created_at"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "payload_preferences_rels": { + "name": "payload_preferences_rels", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_id": { + "name": "parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "users_id": { + "name": "users_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "payload_preferences_rels_order_idx": { + "name": "payload_preferences_rels_order_idx", + "columns": ["order"], + "isUnique": false + }, + "payload_preferences_rels_parent_idx": { + "name": "payload_preferences_rels_parent_idx", + "columns": ["parent_id"], + "isUnique": false + }, + "payload_preferences_rels_path_idx": { + "name": "payload_preferences_rels_path_idx", + "columns": ["path"], + "isUnique": false + }, + "payload_preferences_rels_users_id_idx": { + "name": "payload_preferences_rels_users_id_idx", + "columns": ["users_id"], + "isUnique": false + } + }, + "foreignKeys": { + "payload_preferences_rels_parent_fk": { + "name": "payload_preferences_rels_parent_fk", + "tableFrom": "payload_preferences_rels", + "tableTo": "payload_preferences", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_preferences_rels_users_fk": { + "name": "payload_preferences_rels_users_fk", + "tableFrom": "payload_preferences_rels", + "tableTo": "users", + "columnsFrom": ["users_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "payload_migrations": { + "name": "payload_migrations", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "batch": { + "name": "batch", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + } + }, + "indexes": { + "payload_migrations_updated_at_idx": { + "name": "payload_migrations_updated_at_idx", + "columns": ["updated_at"], + "isUnique": false + }, + "payload_migrations_created_at_idx": { + "name": "payload_migrations_created_at_idx", + "columns": ["created_at"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + }, + "id": "2b2f5008-c761-40d0-a858-67d6b4233615", + "prevId": "00000000-0000-0000-0000-000000000000" +} diff --git a/test/database/up-down-migration/migrations/20250328_185055.ts b/test/database/up-down-migration/migrations/20250328_185055.ts new file mode 100644 index 0000000000..02bc61092a --- /dev/null +++ b/test/database/up-down-migration/migrations/20250328_185055.ts @@ -0,0 +1,122 @@ +import type { MigrateDownArgs, MigrateUpArgs} from '@payloadcms/db-sqlite'; + +import { sql } from '@payloadcms/db-sqlite' + +export async function up({ db, payload, req }: MigrateUpArgs): Promise { + await db.run(sql`CREATE TABLE \`users\` ( + \`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + \`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL, + \`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL, + \`email\` text NOT NULL, + \`reset_password_token\` text, + \`reset_password_expiration\` text, + \`salt\` text, + \`hash\` text, + \`login_attempts\` numeric DEFAULT 0, + \`lock_until\` text + ); + `) + await db.run(sql`CREATE INDEX \`users_updated_at_idx\` ON \`users\` (\`updated_at\`);`) + await db.run(sql`CREATE INDEX \`users_created_at_idx\` ON \`users\` (\`created_at\`);`) + await db.run(sql`CREATE UNIQUE INDEX \`users_email_idx\` ON \`users\` (\`email\`);`) + await db.run(sql`CREATE TABLE \`payload_locked_documents\` ( + \`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + \`global_slug\` text, + \`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL, + \`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL + ); + `) + await db.run( + sql`CREATE INDEX \`payload_locked_documents_global_slug_idx\` ON \`payload_locked_documents\` (\`global_slug\`);`, + ) + await db.run( + sql`CREATE INDEX \`payload_locked_documents_updated_at_idx\` ON \`payload_locked_documents\` (\`updated_at\`);`, + ) + await db.run( + sql`CREATE INDEX \`payload_locked_documents_created_at_idx\` ON \`payload_locked_documents\` (\`created_at\`);`, + ) + await db.run(sql`CREATE TABLE \`payload_locked_documents_rels\` ( + \`id\` integer PRIMARY KEY NOT NULL, + \`order\` integer, + \`parent_id\` integer NOT NULL, + \`path\` text NOT NULL, + \`users_id\` integer, + FOREIGN KEY (\`parent_id\`) REFERENCES \`payload_locked_documents\`(\`id\`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (\`users_id\`) REFERENCES \`users\`(\`id\`) ON UPDATE no action ON DELETE cascade + ); + `) + await db.run( + sql`CREATE INDEX \`payload_locked_documents_rels_order_idx\` ON \`payload_locked_documents_rels\` (\`order\`);`, + ) + await db.run( + sql`CREATE INDEX \`payload_locked_documents_rels_parent_idx\` ON \`payload_locked_documents_rels\` (\`parent_id\`);`, + ) + await db.run( + sql`CREATE INDEX \`payload_locked_documents_rels_path_idx\` ON \`payload_locked_documents_rels\` (\`path\`);`, + ) + await db.run( + sql`CREATE INDEX \`payload_locked_documents_rels_users_id_idx\` ON \`payload_locked_documents_rels\` (\`users_id\`);`, + ) + await db.run(sql`CREATE TABLE \`payload_preferences\` ( + \`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + \`key\` text, + \`value\` text, + \`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL, + \`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL + ); + `) + await db.run( + sql`CREATE INDEX \`payload_preferences_key_idx\` ON \`payload_preferences\` (\`key\`);`, + ) + await db.run( + sql`CREATE INDEX \`payload_preferences_updated_at_idx\` ON \`payload_preferences\` (\`updated_at\`);`, + ) + await db.run( + sql`CREATE INDEX \`payload_preferences_created_at_idx\` ON \`payload_preferences\` (\`created_at\`);`, + ) + await db.run(sql`CREATE TABLE \`payload_preferences_rels\` ( + \`id\` integer PRIMARY KEY NOT NULL, + \`order\` integer, + \`parent_id\` integer NOT NULL, + \`path\` text NOT NULL, + \`users_id\` integer, + FOREIGN KEY (\`parent_id\`) REFERENCES \`payload_preferences\`(\`id\`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (\`users_id\`) REFERENCES \`users\`(\`id\`) ON UPDATE no action ON DELETE cascade + ); + `) + await db.run( + sql`CREATE INDEX \`payload_preferences_rels_order_idx\` ON \`payload_preferences_rels\` (\`order\`);`, + ) + await db.run( + sql`CREATE INDEX \`payload_preferences_rels_parent_idx\` ON \`payload_preferences_rels\` (\`parent_id\`);`, + ) + await db.run( + sql`CREATE INDEX \`payload_preferences_rels_path_idx\` ON \`payload_preferences_rels\` (\`path\`);`, + ) + await db.run( + sql`CREATE INDEX \`payload_preferences_rels_users_id_idx\` ON \`payload_preferences_rels\` (\`users_id\`);`, + ) + await db.run(sql`CREATE TABLE \`payload_migrations\` ( + \`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + \`name\` text, + \`batch\` numeric, + \`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL, + \`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL + ); + `) + await db.run( + sql`CREATE INDEX \`payload_migrations_updated_at_idx\` ON \`payload_migrations\` (\`updated_at\`);`, + ) + await db.run( + sql`CREATE INDEX \`payload_migrations_created_at_idx\` ON \`payload_migrations\` (\`created_at\`);`, + ) +} + +export async function down({ db, payload, req }: MigrateDownArgs): Promise { + await db.run(sql`DROP TABLE \`users\`;`) + await db.run(sql`DROP TABLE \`payload_locked_documents\`;`) + await db.run(sql`DROP TABLE \`payload_locked_documents_rels\`;`) + await db.run(sql`DROP TABLE \`payload_preferences\`;`) + await db.run(sql`DROP TABLE \`payload_preferences_rels\`;`) + await db.run(sql`DROP TABLE \`payload_migrations\`;`) +} diff --git a/test/database/up-down-migration/migrations/index.ts b/test/database/up-down-migration/migrations/index.ts new file mode 100644 index 0000000000..c7646c19ea --- /dev/null +++ b/test/database/up-down-migration/migrations/index.ts @@ -0,0 +1,9 @@ +import * as migration_20250328_185055 from './20250328_185055.js' + +export const migrations = [ + { + up: migration_20250328_185055.up, + down: migration_20250328_185055.down, + name: '20250328_185055', + }, +]