prisma: Second Prisma migrate run (with no interim schema changes) reports diff and throws "duplicate key" error

Bug description

After introspecting a MySQL database and running an initial migration, and then making no changes of any kind to the schema after that initial introspection, a second run of npx prisma migrate dev incorrectly generates another migration by adding an index already added during that first migration. The second migration then fails to run, reporting a “duplicate key.”

image

There is nothing special or noteworthy about this particular index: it’s just one of many indices in this DB, and that index name IDX_fileName is unique and exists nowhere else in the database. The syntax in schema.prisma uses exactly the same @@index syntax for this index which is used to create all other indices. The index is not a foreign key but just a regular string column called “fileName”:

fileName        String         @db.VarChar(1023)

To test this I rolled back the failed migration, deleted the entire migrations folder, removed the failing index from schema.prisma and attempted the same sequence above (npx prisma migrate dev, make no changes to schema, then run npx prisma migrate dev again).

On this second test I see the expected outcome: “Already in sync, no schema change or pending migration was found.” No second migration is generated at all because nothing has changed in the schema.

So I have no idea why this one index (out of hundreds in the DB) causes this behavior, but am curious if it has something to do with this bug fix of a few years ago on an earlier version of Prisma, which mentions oddities surrounding MySql indices.

How to reproduce

  1. Introspect a MySQL database using npx prisma db pull
  2. Run the first migration: npx prisma migrate dev --init
  3. Without making any changes to the schema, run a second migration immediately after the first: npx prisma migrate dev --second
  4. Verify that a new migration file reporting a diff is (incorrectly) generated, attempting to create an index which was already part of the first migration

Expected behavior

After introspecting the DB and running an initial migration, making no changes of any kind to the schema after that initial introspection, I expect a second run of npx prisma migrate dev not to output any second migration file. Instead the second run should simply log “Already in sync, no schema change or pending migration was found” (because no schema changes have occurred).

Prisma information

(No queries being made yet, this is just an attempt to set up the migrations)

I am not able to share the entire schema but will include the relevant tables below:

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model attachment {
  id              Int            @id @default(autoincrement())
  createdAt       DateTime       @default(now()) @db.DateTime(6)
  updatedAt       DateTime       @default(now()) @db.DateTime(6)
  deletedAt       DateTime?      @db.DateTime(6)
  lastUserId      String?        @db.VarChar(60)
  fileName        String         @db.VarChar(1023)
  note            String?        @db.Text
  path            String         @db.VarChar(1023)
  type            String         @db.VarChar(255)
  productItemId   Int
  product_item  product_item @relation(fields: [productItemId], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "FK_d5749de348b0f64483a3f020046")

  @@index([productItemId], map: "FK_d5749de348b0f64483a3f020046")
  @@index([deletedAt], map: "IDX_deletedAt")
  @@index([fileName], map: "IDX_fileName")
}

model product_item {
  id                                                                               Int                                        @id @default(autoincrement())
  uuid                                                                             String                                     @unique(map: "IDX_2fc7a6ec8c1e67f13f8cf9bffb") @db.VarChar(30)
  cost                                                                             Decimal                                    @default(0.00) @db.Decimal(15, 2)
  createdAt                                                                        DateTime                                   @default(now()) @db.DateTime(6)
  updatedAt                                                                        DateTime                                   @default(now()) @db.DateTime(6)
  deletedAt                                                                        DateTime?                                  @db.DateTime(6)
  lastUserId                                                                       String?                                    @db.VarChar(60)
  status                                                                           String                                     @default("") @db.VarChar(20)
  statusNoteId                                                                     Int?                                       @unique(map: "IDX_ccdae40f3258a45d4883d22f6c")
  sequenceNumber                                                                   Int?
  scheduledDate                                                                    DateTime?                                  @db.Date
  componentId                                                                      Int
  component                                                                        component                                  @relation(fields: [componentId], references: [id], onUpdate: NoAction, map: "FK_eb69e557981ad9e0d5bcb7a78b7")
  attachment                                                                       attachment[]

  @@unique([componentId, sequenceNumber], map: "componentId")
  @@index([scheduledDate], map: "IDX_SCHEDULEDDATE")
  @@index([status], map: "product-item-status-idx")
  @@index([uuid], map: "product-item-uuid-idx")
  @@index([lastUserId], map: "last-user-id-idx")
}

Environment & setup

  • OS: MacOS (Big Sur)
  • Database: MySQL
  • Node.js version: 12.19.1

Prisma Version

3.5.0

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Comments: 24 (16 by maintainers)

Most upvoted comments

I was able to recreate this problem with PostgreSQL 14.1 and prisma 3.8.1 (using extendedIndexes since I wanted to use the Hash index) . Removing the extendedIndexes fixed the problem. Just including the extendedIndexes in the schema file is causing it for some reason.

@feather-michael can you try with prisma@3.8.0-dev.47 (or 3.8.0 stable next week) if the issue still persists?

I’ve tried to reproduce this with a great effort today, with versions 3.5.0 and 3.7.0 and I can’t get the failure; it works as it should.

OS: Linux Db: MySQL versions 8.x and 5.7.x from Docker Prisma 3.5.0 and 3.7.0

Steps taken:

We might need to go this through next week together with @janpio to see if I missed a step or something…

CREATE TABLE `attachment` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `createdAt` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
  `updatedAt` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
  `deletedAt` datetime(6) DEFAULT NULL,
  `lastUserId` varchar(60) DEFAULT NULL,
  `fileName` varchar(1023) NOT NULL,
  `note` text,
  `path` varchar(1023) NOT NULL,
  `type` varchar(255) NOT NULL,
  `productItemId` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `FK_d5749de348b0f64483a3f020046` (`productItemId`),
  KEY `IDX_fileName` (`fileName`),
  KEY `IDX_deletedAt` (`deletedAt`),
  CONSTRAINT `FK_d5749de348b0f64483a3f020046` FOREIGN KEY (`productItemId`) REFERENCES `product_item` (`id`)
)

CREATE TABLE `product_item` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `uuid` varchar(30) NOT NULL,
  `cost` decimal(15,2) NOT NULL DEFAULT '0.00',
  `createdAt` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
  `updatedAt` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
  `deletedAt` datetime(6) DEFAULT NULL,
  `lastUserId` varchar(60) DEFAULT NULL,
  `status` varchar(20) NOT NULL DEFAULT '',
  `statusNoteId` int(11) DEFAULT NULL,
  `sequenceNumber` int(11) DEFAULT NULL,
  `scheduledDate` date DEFAULT NULL,
  `componentId` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `IDX_2fc7a6ec8c1e67f13f8cf9bffb` (`uuid`),
  UNIQUE KEY `REL_ccdae40f3258a45d4883d22f6c` (`statusNoteId`),
  UNIQUE KEY `IDX_ccdae40f3258a45d4883d22f6c` (`statusNoteId`),
  UNIQUE KEY `componentId` (`componentId`,`sequenceNumber`),
  KEY `product-item-uuid-idx` (`uuid`),
  KEY `product-item-status-idx` (`status`),
  KEY `last-user-id-idx` (`lastUserId`),
  KEY `IDX_SCHEDULEDDATE` (`scheduledDate`),
  CONSTRAINT `FK_ccdae40f3258a45d4883d22f6c5` FOREIGN KEY (`statusNoteId`) REFERENCES `product_item_note` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `FK_eb69e557981ad9e0d5bcb7a78b7` FOREIGN KEY (`componentId`) REFERENCES `component` (`id`) ON DELETE RESTRICT
) 



@janpio Thanks very much for the follow-up, I will try to test out your suggested fix later today. I believe I may have initially introspected using version 3.4.2 but I reset everything and re-ran the procedure using 3.5.0 everywhere. I was also prompted to reset the DB on the initial run (the docs seemed to provide a procedure for baselining specifically in production environments, but I wasn’t aware there was an alternative to resetting/losing data when it comes to local development).

I will test your fix and update today.