question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Validation fails to detect invalid `SetNull` referential action referencing non-optional fields

See original GitHub issue

This missed validation triggers a migration error when using MySQL, SQL Server, SQLite, and CockroachDB, but not on Postgres.

Example with MySQL:

// schema.prisma

generator client {
  provider = "prisma-client-js"
  previewFeatures = ["referentialIntegrity"]
}

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

model User {
  id      String @id
  profile Profile?
}

model Profile {
  id       String @id
  user     User @relation(fields: [userId], references: [id], onUpdate: SetNull, onDelete: SetNull)
  
  // notice that this field should become optional in order to support `SetNull`
  userId   String @unique
}

We can see that the schema is wrongfully considered valid:

❯  prisma validate
Prisma schema loaded from prisma/schema.prisma
The schema at /.../reprod/prisma/schema.prisma is valid 🚀

If we attempt a push, we get the following error:

❯ prisma db push --skip-generate
Prisma schema loaded from prisma/schema.prisma
Datasource "db": MySQL database "PRISMA_DB_NAME" at "localhost:3306"

MySQL database PRISMA_DB_NAME created at localhost:3306
Error: Column 'userId' cannot be NOT NULL: needed in a foreign key constraint 'Profile_userId_fkey' SET NULL
   0: sql_migration_connector::apply_migration::migration_step
           with step=AddForeignKey { foreign_key_id: ForeignKeyId(0) }
             at migration-engine/connectors/sql-migration-connector/src/apply_migration.rs:21
   1: sql_migration_connector::apply_migration::apply_migration
             at migration-engine/connectors/sql-migration-connector/src/apply_migration.rs:10
   2: migration_core::state::SchemaPush
             at migration-engine/core/src/state.rs:384

If we try to create/update the Profile model via the Prisma client, we get the following migration error:

await prisma.$transaction([
  prisma.user.create({
    data: {
      id: '1'.
      profile: {
        create: { id }
      }
    }
  })
])
    Column 'userId' cannot be NOT NULL: needed in a foreign key constraint 'Profile_userId_fkey' SET NULL
       0: sql_migration_connector::apply_migration::apply_migration
                 at migration-engine/connectors/sql-migration-connector/src/apply_migration.rs:10
       1: migration_core::state::SchemaPush
                 at migration-engine/core/src/state.rs:384

If we exclude the onUpdate/onDelete referential actions, the schema above generates the following SQL statements (we can check that via prisma migrate dev):

-- CreateTable
CREATE TABLE `User` (
    `id` VARCHAR(191) NOT NULL,

    PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- CreateTable
CREATE TABLE `Profile` (
    `id` VARCHAR(191) NOT NULL,
    `userId` VARCHAR(191) NOT NULL,

    UNIQUE INDEX `Profile_userId_key`(`userId`),
    PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- AddForeignKey
ALTER TABLE `Profile` ADD CONSTRAINT `Profile_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`);

On Postgres, oddly, prisma db push it doesn’t fail, so migration error is thrown:

❯ prisma db push --skip-generate
Prisma schema loaded from prisma/schema.prisma
Datasource "db": PostgreSQL database "PRISMA_DB_NAME", schema "public" at "localhost:5432"

PostgreSQL database PRISMA_DB_NAME created at localhost:5432

🚀  Your database is now in sync with your Prisma schema. Done in 64ms

SetNull on Valid Schema

If we want prisma db push to work on other databases like mysql:8 when using the SetNull referential action, we’d need to change the schema above as:

generator client {
  provider = "prisma-client-js"
  previewFeatures = ["referentialIntegrity"] 
}

datasource db {
  provider = "mysql"
  url = env("TEST_MYSQL_URI_MIGRATE")
  referentialIntegrity = "foreignKeys"
}

model User {
  id      String @id
  profile Profile?
}

model Profile {
  id       String @id
  user     User? @relation(fields: [userId], references: [id], onUpdate: SetNull, onDelete: SetNull)
  userId   String? @unique
}

which generates the following SQL statements:

-- CreateTable
CREATE TABLE `User` (
    `id` VARCHAR(191) NOT NULL,

    PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- CreateTable
CREATE TABLE `Profile` (
    `id` VARCHAR(191) NOT NULL,
    `userId` VARCHAR(191) NULL,

    UNIQUE INDEX `Profile_userId_key`(`userId`),
    PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- AddForeignKey
ALTER TABLE `Profile` ADD CONSTRAINT `Profile_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE SET NULL ON UPDATE SET NULL;

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:7 (7 by maintainers)

github_iconTop GitHub Comments

1reaction
tomhoulecommented, Oct 6, 2022

but I think Prisma should, in order to avoid panics at runtimes.

Makes sense. One thing to keep in mind that could prevent us from going into that direction is the principle that we should be able to introspect a valid database schema to a valid prisma schema in as many cases as possible, so if it’s valid at the database level, we may want to accept it. Maybe warn.

0reactions
jkomynocommented, Oct 11, 2022

To summarise the output of the tests done in https://github.com/prisma/prisma/pull/15728 relevant to this issue:

  • For both 1:1 and 1:n relations:
    • postgres doesn’t validate DDL statements that set a SET NULL referential action on a foreign key constraint whose referenced column (or one of the referenced columns) is NOT NULL, all other database throw a validation error in this case.
    • postgres fails at runtime with a not-null constraint violation error when UPDATE / DELETE statements trigger the SET NULL referential action on a column defined as NOT NULL

Optional but nice DX: There’s also an odd behavior that may be useful to document and/or validate against:

  • sqlserver fails at runtime on 1:1 NULL relations because NULL conflicts with UNIQUE indexes in that particular database
Read more comments on GitHub >

github_iconTop Results From Across the Web

`referentialIntegrity` GA · Issue #11441 · prisma/prisma - GitHub
Schema team. Validation fails to detect invalid SetNull referential action referencing non-optional fields #14673
Read more >
Prisma 4.6.0 Release - GitClear
Re-Introspection: referentialIntegrity = prisma not respected when using @@map() · Validation fails to detect invalid SetNull referential action ...
Read more >
Referential actions - Prisma
Referential actions let you define the update and delete behavior of related models on the database level.
Read more >
error: error validating model cart: the id definition refers to the ...
Error validating model "ThirdParty": The id definition refers to the optional fields orgId. Id definitions must reference only required fields. at Object.
Read more >
2 Server Error Message Reference - MySQL :: Developer Zone
MySQL 8.0 Error Reference / Server Error Message Reference ... Message: Field separator argument is not what is expected; check the manual. Error...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found