{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Database.Beam.Migrate.SQL.Tables
(
createTable, dropTable
, preserve
, TableMigration(..)
, ColumnMigration(..)
, alterTable
, renameTableTo, renameColumnTo
, addColumn, dropColumn
, DefaultValue, Constraint(..)
, field
, defaultTo_, notNull, unique
, int, smallint, bigint
, char, varchar, double
, characterLargeObject, binaryLargeObject, array
, boolean, numeric, date, time
, timestamp, timestamptz
, binary, varbinary
, maybeType
, FieldReturnType(..)
) where
import Database.Beam
import Database.Beam.Schema.Tables
import Database.Beam.Backend.SQL
import Database.Beam.Migrate.Types
import Database.Beam.Migrate.Checks
import Database.Beam.Migrate.SQL.Types
import Database.Beam.Migrate.SQL.SQL92
import Control.Applicative
import Control.Monad.Identity
import Control.Monad.Writer.Strict
import Control.Monad.State
import Data.Text (Text)
import Data.Vector (Vector)
import Data.ByteString (ByteString)
import Data.Typeable
import Data.Time (LocalTime, TimeOfDay)
import Data.Scientific (Scientific)
import qualified Data.Kind as Kind (Constraint)
import GHC.TypeLits
createTable :: ( Beamable table, Table table
, IsSql92DdlCommandSyntax syntax ) =>
Text -> TableSchema (Sql92CreateTableColumnSchemaSyntax (Sql92DdlCommandCreateTableSyntax syntax)) table
-> Migration syntax (CheckedDatabaseEntity be db (TableEntity table))
createTable newTblName tblSettings =
do let createTableCommand =
createTableSyntax Nothing newTblName
(allBeamValues (\(Columnar' (TableFieldSchema name (FieldSchema schema) _)) -> (name, schema)) tblSettings)
[ primaryKeyConstraintSyntax (allBeamValues (\(Columnar' (TableFieldSchema name _ _)) -> name) (primaryKey tblSettings)) ]
command = createTableCmd createTableCommand
tbl' = changeBeamRep (\(Columnar' (TableFieldSchema name _ _)) -> Columnar' (TableField name)) tblSettings
fieldChecks = changeBeamRep (\(Columnar' (TableFieldSchema _ _ cs)) -> Columnar' (Const cs)) tblSettings
tblChecks = [ TableCheck (\tblName _ -> SomeDatabasePredicate (TableExistsPredicate tblName)) ] ++
primaryKeyCheck
primaryKeyCheck =
case allBeamValues (\(Columnar' (TableFieldSchema name _ _)) -> name) (primaryKey tblSettings) of
[] -> []
cols -> [ TableCheck (\tblName _ -> SomeDatabasePredicate (TableHasPrimaryKey tblName cols)) ]
upDown command Nothing
pure (CheckedDatabaseEntity (CheckedDatabaseTable (DatabaseTable newTblName tbl') tblChecks fieldChecks) [])
dropTable :: IsSql92DdlCommandSyntax syntax
=> CheckedDatabaseEntity be db (TableEntity table)
-> Migration syntax ()
dropTable (CheckedDatabaseEntity (CheckedDatabaseTable (DatabaseTable tblNm _) _ _) _) =
let command = dropTableCmd (dropTableSyntax tblNm)
in upDown command Nothing
preserve :: CheckedDatabaseEntity be db e
-> Migration syntax (CheckedDatabaseEntity be db' e)
preserve (CheckedDatabaseEntity desc checks) = pure (CheckedDatabaseEntity desc checks)
data ColumnMigration a
= ColumnMigration
{ columnMigrationFieldName :: Text
, columnMigrationFieldChecks :: [FieldCheck] }
newtype TableMigration syntax a
= TableMigration (WriterT [Sql92DdlCommandAlterTableSyntax syntax] (State (Text, [TableCheck])) a)
deriving (Monad, Applicative, Functor)
renameTableTo :: Sql92SaneDdlCommandSyntax syntax
=> Text -> table ColumnMigration
-> TableMigration syntax (table ColumnMigration)
renameTableTo newName oldTbl = TableMigration $ do
(curNm, chks) <- get
tell [ alterTableSyntax curNm (renameTableToSyntax newName) ]
put (newName, chks)
return oldTbl
renameColumnTo :: Sql92SaneDdlCommandSyntax syntax
=> Text -> ColumnMigration a
-> TableMigration syntax (ColumnMigration a)
renameColumnTo newName column = TableMigration $ do
(curTblNm, _) <- get
tell [ alterTableSyntax curTblNm
(renameColumnToSyntax (columnMigrationFieldName column) newName) ]
pure column { columnMigrationFieldName = newName }
dropColumn :: Sql92SaneDdlCommandSyntax syntax
=> ColumnMigration a -> TableMigration syntax ()
dropColumn column = TableMigration $ do
(curTblNm, _)<- get
tell [ alterTableSyntax curTblNm (dropColumnSyntax (columnMigrationFieldName column)) ]
addColumn :: Sql92SaneDdlCommandSyntax syntax
=> TableFieldSchema (Sql92DdlCommandColumnSchemaSyntax syntax) a
-> TableMigration syntax (ColumnMigration a)
addColumn (TableFieldSchema nm (FieldSchema fieldSchemaSyntax) checks) =
TableMigration $
do (curTblNm, _) <- get
tell [ alterTableSyntax curTblNm (addColumnSyntax nm fieldSchemaSyntax) ]
pure (ColumnMigration nm checks)
alterTable :: forall be db db' table table' syntax
. (Table table', IsSql92DdlCommandSyntax syntax)
=> CheckedDatabaseEntity be db (TableEntity table)
-> (table ColumnMigration -> TableMigration syntax (table' ColumnMigration))
-> Migration syntax (CheckedDatabaseEntity be db' (TableEntity table'))
alterTable (CheckedDatabaseEntity (CheckedDatabaseTable (DatabaseTable tblNm tbl) tblChecks tblFieldChecks) entityChecks) alterColumns =
let initialTbl = runIdentity $
zipBeamFieldsM
(\(Columnar' (TableField nm) :: Columnar' (TableField table) x)
(Columnar' (Const checks) :: Columnar' (Const [FieldCheck]) x) ->
pure (Columnar' (ColumnMigration nm checks)
:: Columnar' ColumnMigration x))
tbl tblFieldChecks
TableMigration alterColumns' = alterColumns initialTbl
((newTbl, cmds), (tblNm', tblChecks')) = runState (runWriterT alterColumns') (tblNm, tblChecks)
fieldChecks' = changeBeamRep (\(Columnar' (ColumnMigration _ checks) :: Columnar' ColumnMigration a) ->
Columnar' (Const checks) :: Columnar' (Const [FieldCheck]) a)
newTbl
tbl' = changeBeamRep (\(Columnar' (ColumnMigration nm _) :: Columnar' ColumnMigration a) ->
Columnar' (TableField nm) :: Columnar' (TableField table') a)
newTbl
in forM_ cmds (\cmd -> upDown (alterTableCmd cmd) Nothing) >>
pure (CheckedDatabaseEntity (CheckedDatabaseTable (DatabaseTable tblNm' tbl') tblChecks' fieldChecks') entityChecks)
field :: ( IsSql92ColumnSchemaSyntax syntax ) =>
FieldReturnType 'False 'False syntax resTy a => Text -> DataType (Sql92ColumnSchemaColumnTypeSyntax syntax) resTy -> a
field name (DataType ty) = field' (Proxy @'False) (Proxy @'False) name ty Nothing Nothing []
newtype DefaultValue syntax a = DefaultValue (Sql92ColumnSchemaExpressionSyntax syntax)
defaultTo_ :: IsSql92ExpressionSyntax (Sql92ColumnSchemaExpressionSyntax syntax) =>
(forall s. QExpr (Sql92ColumnSchemaExpressionSyntax syntax) s a)
-> DefaultValue syntax a
defaultTo_ (QExpr e) =
DefaultValue (e "t")
newtype Constraint syntax
= Constraint (Sql92ColumnConstraintDefinitionConstraintSyntax
(Sql92ColumnSchemaColumnConstraintDefinitionSyntax syntax))
newtype NotNullConstraint syntax
= NotNullConstraint (Constraint syntax)
notNull :: IsSql92ColumnSchemaSyntax syntax => NotNullConstraint syntax
notNull = NotNullConstraint (Constraint notNullConstraintSyntax)
unique :: IsSql92ColumnSchemaSyntax syntax => Constraint syntax
unique = Constraint uniqueColumnConstraintSyntax
int :: (IsSql92DataTypeSyntax syntax, Integral a) => DataType syntax a
int = DataType intType
smallint :: (IsSql92DataTypeSyntax syntax, Integral a) => DataType syntax a
smallint = DataType smallIntType
bigint :: (IsSql2008BigIntDataTypeSyntax syntax, Integral a) => DataType syntax a
bigint = DataType bigIntType
binary :: IsSql2003BinaryAndVarBinaryDataTypeSyntax syntax
=> Maybe Word -> DataType syntax Integer
binary prec = DataType (binaryType prec)
varbinary :: IsSql2003BinaryAndVarBinaryDataTypeSyntax syntax
=> Maybe Word -> DataType syntax Integer
varbinary prec = DataType (varBinaryType prec)
date :: IsSql92DataTypeSyntax syntax => DataType syntax LocalTime
date = DataType dateType
char :: IsSql92DataTypeSyntax syntax => Maybe Word -> DataType syntax Text
char prec = DataType (charType prec Nothing)
varchar :: IsSql92DataTypeSyntax syntax => Maybe Word -> DataType syntax Text
varchar prec = DataType (varCharType prec Nothing)
double :: IsSql92DataTypeSyntax syntax => DataType syntax Double
double = DataType doubleType
numeric :: IsSql92DataTypeSyntax syntax => Maybe (Word, Maybe Word) -> DataType syntax Scientific
numeric x = DataType (numericType x)
timestamptz :: IsSql92DataTypeSyntax syntax => DataType syntax LocalTime
timestamptz = DataType (timestampType Nothing True)
timestamp :: IsSql92DataTypeSyntax syntax => DataType syntax LocalTime
timestamp = DataType (timestampType Nothing False)
time :: IsSql92DataTypeSyntax syntax => Maybe Word -> DataType syntax TimeOfDay
time prec = DataType (timeType prec False)
boolean :: IsSql99DataTypeSyntax syntax => DataType syntax Bool
boolean = DataType booleanType
characterLargeObject :: IsSql99DataTypeSyntax syntax => DataType syntax Text
characterLargeObject = DataType characterLargeObjectType
binaryLargeObject :: IsSql99DataTypeSyntax syntax => DataType syntax ByteString
binaryLargeObject = DataType binaryLargeObjectType
array :: (Typeable a, IsSql99DataTypeSyntax syntax)
=> DataType syntax a -> Int
-> DataType syntax (Vector a)
array (DataType ty) sz = DataType (arrayType ty sz)
maybeType :: DataType syntax a -> DataType syntax (Maybe a)
maybeType (DataType sqlTy) = DataType sqlTy
class FieldReturnType (defaultGiven :: Bool) (collationGiven :: Bool) syntax resTy a | a -> syntax resTy where
field' :: IsSql92ColumnSchemaSyntax syntax =>
Proxy defaultGiven -> Proxy collationGiven
-> Text
-> Sql92ColumnSchemaColumnTypeSyntax syntax
-> Maybe (Sql92ColumnSchemaExpressionSyntax syntax)
-> Maybe Text -> [ Sql92ColumnSchemaColumnConstraintDefinitionSyntax syntax ]
-> a
instance FieldReturnType 'True collationGiven syntax resTy a =>
FieldReturnType 'False collationGiven syntax resTy (DefaultValue syntax resTy -> a) where
field' _ collationGiven nm ty _ collation constraints (DefaultValue e) =
field' (Proxy @'True) collationGiven nm ty (Just e) collation constraints
instance FieldReturnType defaultGiven collationGiven syntax resTy a =>
FieldReturnType defaultGiven collationGiven syntax resTy (Constraint syntax -> a) where
field' defaultGiven collationGiven nm ty default_' collation constraints (Constraint e) =
field' defaultGiven collationGiven nm ty default_' collation (constraints ++ [ constraintDefinitionSyntax Nothing e Nothing ])
instance ( FieldReturnType defaultGiven collationGiven syntax resTy (Constraint syntax -> a)
, IsNotNull resTy ) =>
FieldReturnType defaultGiven collationGiven syntax resTy (NotNullConstraint syntax -> a) where
field' defaultGiven collationGiven nm ty default_' collation constraints (NotNullConstraint c) =
field' defaultGiven collationGiven nm ty default_' collation constraints c
instance ( FieldReturnType 'True collationGiven syntax resTy a
, TypeError ('Text "Only one DEFAULT clause can be given per 'field' invocation") ) =>
FieldReturnType 'True collationGiven syntax resTy (DefaultValue syntax resTy -> a) where
field' = error "Unreachable because of GHC Custom Type Errors"
instance ( FieldReturnType defaultGiven collationGiven syntax resTy a
, TypeError ('Text "Only one type declaration allowed per 'field' invocation")) =>
FieldReturnType defaultGiven collationGiven syntax resTy (DataType syntax' x -> a) where
field' = error "Unreachable because of GHC Custom Type Errors"
instance ( Typeable syntax, Typeable (Sql92ColumnSchemaColumnTypeSyntax syntax)
, Sql92DisplaySyntax (Sql92ColumnSchemaColumnTypeSyntax syntax), Eq (Sql92ColumnSchemaColumnTypeSyntax syntax)
, Sql92DisplaySyntax (Sql92ColumnSchemaColumnConstraintDefinitionSyntax syntax), Eq (Sql92ColumnSchemaColumnConstraintDefinitionSyntax syntax)
, IsSql92ColumnSchemaSyntax syntax
, Sql92SerializableConstraintDefinitionSyntax (Sql92ColumnSchemaColumnConstraintDefinitionSyntax syntax)
, Sql92SerializableDataTypeSyntax (Sql92ColumnSchemaColumnTypeSyntax syntax) ) =>
FieldReturnType defaultGiven collationGiven syntax resTy (TableFieldSchema syntax resTy) where
field' _ _ nm ty default_' collation constraints =
TableFieldSchema nm (FieldSchema (columnSchemaSyntax ty default_' constraints collation)) checks
where checks = [ FieldCheck (\tbl field'' -> SomeDatabasePredicate (TableHasColumn tbl field'' ty :: TableHasColumn syntax)) ] ++
map (\cns -> FieldCheck (\tbl field'' -> SomeDatabasePredicate (TableColumnHasConstraint tbl field'' cns :: TableColumnHasConstraint syntax))) constraints
type family IsNotNull (x :: *) :: Kind.Constraint where
IsNotNull (Maybe x) = TypeError ('Text "You used Database.Beam.Migrate.notNull on a column with type" ':$$:
'ShowType (Maybe x) ':$$:
'Text "Either remove 'notNull' from your migration or 'Maybe' from your table")
IsNotNull x = ()