Skip to content

Commit fa0d8ef

Browse files
committed
postgresql: Add foreign key constraints the first time when migrating
1 parent 9d88bf4 commit fa0d8ef

File tree

2 files changed

+79
-22
lines changed

2 files changed

+79
-22
lines changed

persistent-postgresql/Database/Persist/Postgresql/Internal/Migration.hs

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,42 +1097,33 @@ findAlters
10971097
-> EntityDef
10981098
-- ^ The entity definition for the entity that we're working on.
10991099
-> Column
1100-
-- ^ The column that we're searching for potential alterations for.
1100+
-- ^ The column that we're searching for potential alterations for, derived
1101+
-- from the Persistent EntityDef. That is: this is how we _want_ the column
1102+
-- to look, and not necessarily how it actually looks in the database right
1103+
-- now.
11011104
-> [Column]
1105+
-- ^ The columns for this table, as they currently exist in the database.
11021106
-> ([AlterColumn], [Column])
11031107
findAlters defs edef col@(Column name isNull sqltype def _gen _defConstraintName _maxLen ref) cols =
11041108
case List.find (\c -> cName c == name) cols of
11051109
Nothing ->
1106-
([AddColumn col], cols)
1110+
([AddColumn col] ++ refAdd ref, cols)
11071111
Just
1108-
(Column _oldName isNull' sqltype' def' _gen' _defConstraintName' _maxLen' ref') ->
1112+
(Column oldName isNull' sqltype' def' _gen' _defConstraintName' _maxLen' ref') ->
11091113
let
11101114
refDrop Nothing = []
11111115
refDrop (Just ColumnReference{crConstraintName = cname}) =
11121116
[DropReference cname]
11131117

1114-
refAdd Nothing = []
1115-
refAdd (Just colRef) =
1116-
case find ((== crTableName colRef) . getEntityDBName) defs of
1117-
Just refdef
1118-
| Just _oldName /= fmap fieldDB (getEntityIdField edef) ->
1119-
[ AddReference
1120-
(crTableName colRef)
1121-
(crConstraintName colRef)
1122-
(name NEL.:| [])
1123-
(NEL.toList $ Util.dbIdColumnsEsc escapeF refdef)
1124-
(crFieldCascade colRef)
1125-
]
1126-
Just _ -> []
1127-
Nothing ->
1128-
error $
1129-
"could not find the entityDef for reftable["
1130-
++ show (crTableName colRef)
1131-
++ "]"
11321118
modRef =
11331119
if equivalentRef ref ref'
11341120
then []
1135-
else refDrop ref' ++ refAdd ref
1121+
else
1122+
refDrop ref'
1123+
++ ( do
1124+
guard $ Just oldName /= fmap fieldDB (getEntityIdField edef)
1125+
refAdd ref
1126+
)
11361127
modNull = case (isNull, isNull') of
11371128
(True, False) -> do
11381129
guard $ Just name /= fmap fieldDB (getEntityIdField edef)
@@ -1174,3 +1165,20 @@ findAlters defs edef col@(Column name isNull sqltype def _gen _defConstraintName
11741165
( modRef ++ modDef ++ modNull ++ modType ++ dropSafe
11751166
, filter (\c -> cName c /= name) cols
11761167
)
1168+
where
1169+
refAdd Nothing = []
1170+
refAdd (Just colRef) =
1171+
case find ((== crTableName colRef) . getEntityDBName) defs of
1172+
Just refdef ->
1173+
[ AddReference
1174+
(crTableName colRef)
1175+
(crConstraintName colRef)
1176+
(name NEL.:| [])
1177+
(NEL.toList $ Util.dbIdColumnsEsc escapeF refdef)
1178+
(crFieldCascade colRef)
1179+
]
1180+
Nothing ->
1181+
error $
1182+
"could not find the entityDef for reftable["
1183+
++ show (crTableName colRef)
1184+
++ "]"

persistent-postgresql/test/MigrationSpec.hs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ AdminUser sql=admin_users
6060

6161
promotedByUserId UserId
6262
UniquePromotedByUserId promotedByUserId
63+
64+
FKParent sql=migration_fk_parent
65+
66+
FKChildV1 sql=migration_fk_child
67+
68+
-- Simulate creating a new FK field on an existing table
69+
FKChildV2 sql=migration_fk_child
70+
parentId FKParentId
6371
|]
6472

6573
userEntityDef :: EntityDef
@@ -77,15 +85,28 @@ password2EntityDef = entityDef (Proxy :: Proxy Password2)
7785
adminUserEntityDef :: EntityDef
7886
adminUserEntityDef = entityDef (Proxy :: Proxy AdminUser)
7987

88+
fkParentEntityDef :: EntityDef
89+
fkParentEntityDef = entityDef (Proxy :: Proxy FKParent)
90+
91+
fkChildV1EntityDef :: EntityDef
92+
fkChildV1EntityDef = entityDef (Proxy :: Proxy FKChildV1)
93+
94+
fkChildV2EntityDef :: EntityDef
95+
fkChildV2EntityDef = entityDef (Proxy :: Proxy FKChildV2)
96+
97+
-- Note that FKChild is deliberately omitted here because we have two
98+
-- versions of it
8099
allEntityDefs :: [EntityDef]
81100
allEntityDefs =
82101
[ userEntityDef
83102
, userFriendshipEntityDef
84103
, passwordEntityDef
85104
, password2EntityDef
86105
, adminUserEntityDef
106+
, fkParentEntityDef
87107
]
88108

109+
-- Note that this function migrates to the schema expected by FKChildV1
89110
migrateManually :: (HasCallStack, MonadIO m) => SqlPersistT m ()
90111
migrateManually = do
91112
cleanDB
@@ -150,6 +171,8 @@ migrateManually = do
150171
, " ADD CONSTRAINT unique_promoted_by_user_id"
151172
, " UNIQUE(promoted_by_user_id);"
152173
]
174+
rawEx "CREATE TABLE migration_fk_parent(id int8 primary key);"
175+
rawEx "CREATE TABLE migration_fk_child(id int8 primary key);"
153176
rawEx "CREATE TABLE ignored(id int8 primary key);"
154177

155178
cleanDB :: (HasCallStack, MonadIO m) => SqlPersistT m ()
@@ -162,6 +185,8 @@ cleanDB = do
162185
rawEx "DROP TABLE IF EXISTS ignored;"
163186
rawEx "DROP TABLE IF EXISTS admin_users;"
164187
rawEx "DROP TABLE IF EXISTS users;"
188+
rawEx "DROP TABLE IF EXISTS migration_fk_child;"
189+
rawEx "DROP TABLE IF EXISTS migration_fk_parent;"
165190

166191
spec :: Spec
167192
spec = describe "MigrationSpec" $ do
@@ -582,3 +607,27 @@ spec = describe "MigrationSpec" $ do
582607
result2 <-
583608
liftIO $ migrateEntitiesStructured getter allEntityDefs allEntityDefs
584609
result2 `shouldBe` Right []
610+
611+
it "suggests FK constraints for new fields first time" $ runConnAssert $ do
612+
migrateManually
613+
614+
getter <- getStmtGetter
615+
result <-
616+
liftIO $
617+
migrateEntitiesStructured
618+
getter
619+
(fkChildV2EntityDef : allEntityDefs)
620+
[fkChildV2EntityDef]
621+
622+
cleanDB
623+
624+
case result of
625+
Right [] ->
626+
pure ()
627+
Left err ->
628+
expectationFailure $ show err
629+
Right alters ->
630+
map (snd . showAlterDb) alters
631+
`shouldBe` [ "ALTER TABLE \"migration_fk_child\" ADD COLUMN \"parent_id\" INT8 NOT NULL"
632+
, "ALTER TABLE \"migration_fk_child\" ADD CONSTRAINT \"migration_fk_child_parent_id_fkey\" FOREIGN KEY(\"parent_id\") REFERENCES \"migration_fk_parent\"(\"id\") ON DELETE RESTRICT ON UPDATE RESTRICT"
633+
]

0 commit comments

Comments
 (0)