yesodweb / yesodweb/persistent

SQLite: FOREIGN KEY constraint failed when migrating

Open
#1,125 7 comments 5 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

good first issue
Dominant language
Haskell
Stars
486
Forks
306
PR merge metrics
No merged PRs in 30d

Description

{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

import Database.Persist.Sqlite
import Database.Persist.TH

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person
    name String
    -- Uncomment this line in the second run
    -- age Int Maybe
    deriving Show
BlogPost
    title String
    authorId PersonId
    deriving Show
|]

main :: IO ()
main = runSqlite "db" $ do
    runMigration migrateAll
    -- Comment this line in the second run
    johnId <- insert $ Person "John Doe"
    -- Uncomment this line in the second run
    -- johnId <- insert $ Person "John Doe" $ Just 35
    insert $ BlogPost "post" johnId
    pure ()
  1. stack ghc the script above and run.
  2. Edit the script, uncomment 2 lines and comment 1 line.
  3. stack ghc the script and run again.
  4. You will see:
Migrating: CREATE TEMP TABLE "person_backup"("id" INTEGER PRIMARY KEY,"name" VARCHAR NOT NULL,"age" INTEGER NULL)
Migrating: INSERT INTO "person_backup"("id","name") SELECT "id","name" FROM "person"
Migrating: DROP TABLE "person"
main: SQLite3 returned ErrorConstraint while attempting to perform step: FOREIGN KEY constraint failed

The correct way to do migrations in SQLite is documented here: https://sqlite.org/lang_altertable.html#making_other_kinds_of_table_schema_changes

In short, you need to disable foreign keys temporarily using PRAGMA commands when migrating. But this seems impossible because I can only send raw commands inside an automatically started transaction. And inside a transaction, PRAGMA foreign_keys=OFF has no effect.

Could you please change the logic of runMigration so that it follows the 12 steps in SQLite document precisely?

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the supplied Haskell reproducer and inspect runMigration, focusing on how SQLite schema changes are wrapped in transactions. Compare that flow with SQLite’s documented 12-step schema-change procedure and verify the second run completes without the FOREIGN KEY constraint failure while adding the nullable age column.

Written by the indexing model from the issue text.

Assessment

Tech stack
haskell, sqlite
Domain
databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.