|
1 | 1 | {-# OPTIONS_GHC -fno-warn-orphans #-} |
| 2 | +{-# LANGUAGE GADTs #-} |
2 | 3 | {-# LANGUAGE TupleSections #-} |
3 | 4 | {-# LANGUAGE BangPatterns #-} |
4 | 5 | {-# LANGUAGE ScopedTypeVariables #-} |
@@ -78,6 +79,7 @@ module Database.Beam.Migrate.Actions |
78 | 79 | , ensuringNot_ |
79 | 80 | , justOne_ |
80 | 81 |
|
| 82 | + , ForeignKeySupport(..) |
81 | 83 | , createSchemaActionProvider |
82 | 84 | , createTableActionProvider |
83 | 85 | , dropTableActionProvider |
@@ -107,6 +109,7 @@ import Control.Monad |
107 | 109 | import Control.Parallel.Strategies |
108 | 110 |
|
109 | 111 | import Data.Foldable |
| 112 | +import qualified Data.List.NonEmpty as NE |
110 | 113 |
|
111 | 114 | import qualified Data.HashMap.Strict as HM |
112 | 115 | import qualified Data.HashSet as HS |
@@ -279,6 +282,16 @@ createIndexWeight, dropIndexWeight :: Int |
279 | 282 | createIndexWeight = 200 |
280 | 283 | dropIndexWeight = 50 |
281 | 284 |
|
| 285 | +-- | Witness for whether a backend's table constraint syntax supports |
| 286 | +-- @FOREIGN KEY@ clauses in @CREATE TABLE@ statements. |
| 287 | +data ForeignKeySupport be where |
| 288 | + -- | The backend supports @FOREIGN KEY@ constraints in @CREATE TABLE@. |
| 289 | + ForeignKeySupported |
| 290 | + :: IsSql92ForeignKeyTableConstraintSyntax (BeamSqlBackendTableConstraintSyntax be) |
| 291 | + => ForeignKeySupport be |
| 292 | + -- | The backend does not support @FOREIGN KEY@ constraints. |
| 293 | + ForeignKeyUnsupported :: ForeignKeySupport be |
| 294 | + |
282 | 295 | -- | Proceeds only if no predicate matches the given pattern. See the |
283 | 296 | -- implementation of 'dropTableActionProvider' for an example of usage. |
284 | 297 | ensuringNot_ :: Alternative m => [ a ] -> m () |
@@ -342,10 +355,13 @@ dropSchemaActionProvider = |
342 | 355 | ("Drop schema " <> preSchemaNm) dropSchemaWeight) |
343 | 356 |
|
344 | 357 | -- | Action provider for SQL92 @CREATE TABLE@ actions. |
345 | | -createTableActionProvider :: forall be |
346 | | - . ( Typeable be, BeamMigrateOnlySqlBackend be ) |
347 | | - => ActionProvider be |
348 | | -createTableActionProvider = |
| 358 | +createTableActionProvider |
| 359 | + :: forall be |
| 360 | + . ( Typeable be, BeamMigrateOnlySqlBackend be ) |
| 361 | + => ForeignKeySupport be |
| 362 | + -- ^ a witness of whether the backend supports FOREIGN KEY constraints |
| 363 | + -> ActionProvider be |
| 364 | +createTableActionProvider fkSupport = |
349 | 365 | ActionProvider provider |
350 | 366 | where |
351 | 367 | provider :: ActionProviderFn be |
@@ -377,9 +393,43 @@ createTableActionProvider = |
377 | 393 | guard (tblNm == postTblNm) |
378 | 394 | pure (primaryKeyP, primaryKey) |
379 | 395 |
|
380 | | - let postConditions = [ p tblP, p primaryKeyP ] ++ concat columnsP |
| 396 | + let (fkPs, fkConstraints) = case fkSupport of |
| 397 | + ForeignKeySupported -> |
| 398 | + unzip |
| 399 | + [ ( p fkP |
| 400 | + , foreignKeyConstraintSyntax (NE.toList localCols) (qnameAsText refTbl) (NE.toList refCols) onUpd onDel ) |
| 401 | + | fkP@(TableHasForeignKey tblNm localCols refTbl refCols onUpd onDel) |
| 402 | + <- findPostConditions |
| 403 | + , tblNm == postTblNm |
| 404 | + ] |
| 405 | + ForeignKeyUnsupported -> ([], []) |
| 406 | + |
| 407 | + -- For each foreign key constraint, ensure the referenced table |
| 408 | + -- already exists in the current state. |
| 409 | + -- This enforces a topological ordering, so that the solver schedules |
| 410 | + -- CREATE TABLE for all referenced tables first. |
| 411 | + -- |
| 412 | + -- Note: this does not support cycles in the foreign key reference graph, |
| 413 | + -- for which we would need ALTER TABLE ADD CONSTRAINT, which we don't |
| 414 | + -- currently support. |
| 415 | + case fkSupport of |
| 416 | + ForeignKeyUnsupported -> pure () |
| 417 | + ForeignKeySupported -> do |
| 418 | + let refTbls = |
| 419 | + [ refTbl |
| 420 | + | TableHasForeignKey tblNm _ refTbl _ _ _ <- findPostConditions |
| 421 | + , tblNm == postTblNm |
| 422 | + , refTbl /= postTblNm |
| 423 | + ] |
| 424 | + for_ refTbls $ \refTbl -> do |
| 425 | + TableExistsPredicate nm <- findPreConditions |
| 426 | + guard (nm == refTbl) |
| 427 | + |
| 428 | + |
| 429 | + let postConditions = [ p tblP, p primaryKeyP ] ++ concat columnsP ++ fkPs |
381 | 430 | cmd = createTableCmd (createTableSyntax Nothing (qnameAsTableName postTblNm) colsSyntax tblConstraints) |
382 | | - tblConstraints = if null primaryKey then [] else [ primaryKeyConstraintSyntax primaryKey ] |
| 431 | + tblConstraints = (if null primaryKey then [] else [ primaryKeyConstraintSyntax primaryKey ]) |
| 432 | + ++ fkConstraints |
383 | 433 | colsSyntax = map (\(colNm, type_, cs) -> (colNm, columnSchemaSyntax type_ Nothing cs Nothing)) columns |
384 | 434 | pure (PotentialAction mempty (HS.fromList postConditions) |
385 | 435 | (Seq.singleton (MigrationCommand cmd MigrationKeepsData)) |
@@ -590,17 +640,18 @@ dropIndexActionProvider = ActionProvider provider |
590 | 640 | -- For default schema actions, see 'defaultSchemaActionProvider'. |
591 | 641 | defaultActionProvider :: ( Typeable be |
592 | 642 | , BeamMigrateOnlySqlBackend be ) |
593 | | - => ActionProvider be |
594 | | -defaultActionProvider = |
| 643 | + => ForeignKeySupport be |
| 644 | + -> ActionProvider be |
| 645 | +defaultActionProvider fkSupport = |
595 | 646 | mconcat |
596 | | - [ createTableActionProvider |
| 647 | + [ createTableActionProvider fkSupport |
597 | 648 | , dropTableActionProvider |
598 | 649 |
|
599 | 650 | , addColumnProvider |
600 | 651 | , dropColumnProvider |
601 | 652 |
|
602 | 653 | , addColumnNullProvider |
603 | | - , dropColumnNullProvider |
| 654 | + , dropColumnNullProvider |
604 | 655 | ] |
605 | 656 |
|
606 | 657 | -- | Default action providers for any syntax which supports schemas. |
|
0 commit comments