{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Database.Persist.Migration.Operation
( Version
, OperationPath
, (~>)
, Migration
, MigrationPath(..)
, opPath
, Operation(..)
, validateOperation
) where
import Control.Monad (when)
import Data.List (nub)
import Data.Maybe (isNothing, mapMaybe)
import Data.Text (Text)
import Database.Persist.Migration.Operation.Types
import Database.Persist.Migration.Utils.Data (isConstr)
import Database.Persist.Migration.Utils.Sql (MigrateSql)
import Database.Persist.Sql (PersistValue, SqlPersistT)
type Version = Int
type OperationPath = (Version, Version)
(~>) :: Version -> Version -> OperationPath
(~>) = (,)
type Migration = [MigrationPath]
data MigrationPath = OperationPath := [Operation]
deriving (Show)
opPath :: MigrationPath -> OperationPath
opPath (path := _) = path
data Operation
= CreateTable
{ name :: Text
, schema :: [Column]
, constraints :: [TableConstraint]
}
| DropTable
{ table :: Text
}
| RenameTable
{ from :: Text
, to :: Text
}
| AddConstraint
{ table :: Text
, constraint :: TableConstraint
}
| DropConstraint
{ table :: Text
, constraintName :: Text
}
| AddColumn
{ table :: Text
, column :: Column
, colDefault :: Maybe PersistValue
}
| RenameColumn
{ table :: Text
, from :: Text
, to :: Text
}
| DropColumn
{ columnId :: ColumnIdentifier
}
| RawOperation
{ message :: Text
, rawOp :: SqlPersistT IO [MigrateSql]
}
deriving (Show)
instance Show (SqlPersistT m a) where
show _ = "<SqlPersistT>"
validateOperation :: Operation -> Either String ()
validateOperation ct@CreateTable{..} = do
when (null schema) $
fail' "No columns specified in the schema"
mapM_ validateColumn schema
case length . filter (isConstr "PrimaryKey") $ constraints of
0 -> fail' "No primary key specified"
1 -> return ()
_ -> fail' "Multiple primary keys specified"
let getUniqueName (Unique n _) = Just n
getUniqueName _ = Nothing
uniqueNames = mapMaybe getUniqueName constraints
when (length (nub uniqueNames) /= length uniqueNames) $
fail' "Multiple unique constraints with the same name detected"
let constraintCols = concatMap getConstraintColumns constraints
schemaCols = map colName schema
when (any (`notElem` schemaCols) constraintCols) $
fail' "Table constraint references non-existent column"
where
fail' = Left . (++ ": " ++ show ct)
validateOperation ac@AddColumn{..} = do
validateColumn column
when (NotNull `elem` colProps column && isNothing colDefault) $
fail' "Adding a non-nullable column requires a default"
where
fail' = Left . (++ ": " ++ show ac)
validateOperation _ = return ()