Schema backend becomes associated datatype
This commit is contained in:
parent
400c29289d
commit
2640ecb8d1
3 changed files with 147 additions and 30 deletions
|
@ -22,6 +22,7 @@ module Database.Persist.Schema
|
||||||
, Field (..)
|
, Field (..)
|
||||||
, Entity (..)
|
, Entity (..)
|
||||||
, Unique (..)
|
, Unique (..)
|
||||||
|
, SchemaT
|
||||||
, PersistSchema (..)
|
, PersistSchema (..)
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
@ -56,27 +57,38 @@ data Entity = Entity
|
||||||
}
|
}
|
||||||
|
|
||||||
data Unique = Unique
|
data Unique = Unique
|
||||||
{ uniqueEntity :: EntityName
|
{ uniqueName :: UniqueName
|
||||||
, uniqueName :: UniqueName
|
|
||||||
, uniqueFields :: [FieldName]
|
, uniqueFields :: [FieldName]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SchemaT b m a = ReaderT (SchemaBackend b) (ReaderT b m) a
|
||||||
|
|
||||||
|
-- | Ideally we'd make the @backend@ provide schema related specifics. The
|
||||||
|
-- problem is that e.g. @SqlBackend@ is already defined in @persistent@ and
|
||||||
|
-- I'll need a patch to get it updated. A patch that will take time to get
|
||||||
|
-- accpted, if the maintainer likes it at all. So instead, I'm letting these
|
||||||
|
-- specifics be specified in a separate, associated data type.
|
||||||
|
--
|
||||||
|
-- The only benefit I see for this approach is schema changes are separate from
|
||||||
|
-- data manipulations. You can't mix them in a single transaction without
|
||||||
|
-- explicitly specifying the schema backend and using 'lift' for data manip.
|
||||||
class PersistSchema backend where
|
class PersistSchema backend where
|
||||||
|
data SchemaBackend backend -- :: *
|
||||||
addEntity
|
addEntity
|
||||||
:: MonadIO m => Entity -> ReaderT backend m ()
|
:: MonadIO m => Entity -> SchemaT backend m ()
|
||||||
removeEntity
|
removeEntity
|
||||||
:: MonadIO m => EntityName -> ReaderT backend m ()
|
:: MonadIO m => EntityName -> SchemaT backend m ()
|
||||||
addField
|
addField
|
||||||
:: MonadIO m => Field -> ReaderT backend m ()
|
:: MonadIO m => EntityName -> Field -> SchemaT backend m ()
|
||||||
renameField
|
renameField
|
||||||
:: MonadIO m
|
:: MonadIO m
|
||||||
=> EntityName -> FieldName -> FieldName -> ReaderT backend m ()
|
=> EntityName -> FieldName -> FieldName -> SchemaT backend m ()
|
||||||
removeField
|
removeField
|
||||||
:: MonadIO m => EntityName -> FieldName -> ReaderT backend m ()
|
:: MonadIO m => EntityName -> FieldName -> SchemaT backend m ()
|
||||||
addUnique
|
addUnique
|
||||||
:: MonadIO m => Unique -> ReaderT backend m ()
|
:: MonadIO m => EntityName -> Unique -> SchemaT backend m ()
|
||||||
renameUnique
|
renameUnique
|
||||||
:: MonadIO m
|
:: MonadIO m
|
||||||
=> EntityName -> UniqueName -> UniqueName -> ReaderT backend m ()
|
=> EntityName -> UniqueName -> UniqueName -> SchemaT backend m ()
|
||||||
removeUnique
|
removeUnique
|
||||||
:: MonadIO m => EntityName -> UniqueName -> ReaderT backend m ()
|
:: MonadIO m => EntityName -> UniqueName -> SchemaT backend m ()
|
||||||
|
|
|
@ -14,13 +14,14 @@
|
||||||
-}
|
-}
|
||||||
|
|
||||||
module Database.Persist.Schema.PostgreSQL
|
module Database.Persist.Schema.PostgreSQL
|
||||||
(
|
( schemaBackend
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Prelude
|
import Prelude
|
||||||
|
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
|
import Database.Persist.Sql (SqlBackend)
|
||||||
import Database.Persist.Types (SqlType (..))
|
import Database.Persist.Types (SqlType (..))
|
||||||
import Formatting
|
import Formatting
|
||||||
|
|
||||||
|
@ -74,9 +75,10 @@ idCol = ColumnName "id"
|
||||||
idSql :: Text
|
idSql :: Text
|
||||||
idSql = "id SERIAL8 PRIMARY KEY UNIQUE"
|
idSql = "id SERIAL8 PRIMARY KEY UNIQUE"
|
||||||
|
|
||||||
ssb :: SqlSchemaBackend
|
schemaBackend :: SchemaBackend SqlBackend
|
||||||
ssb = SqlSchemaBackend
|
schemaBackend = SqlSchemaBackend
|
||||||
{ ssbCreateTable = \ table columns -> mconcat
|
{ ssbRefType = SqlInt64
|
||||||
|
, ssbCreateTable = \ table columns -> mconcat
|
||||||
[ "CREATE TABLE ", table2sql table, " ("
|
[ "CREATE TABLE ", table2sql table, " ("
|
||||||
, idSql
|
, idSql
|
||||||
, if null columns then T.empty else ", "
|
, if null columns then T.empty else ", "
|
||||||
|
@ -133,6 +135,10 @@ ssb = SqlSchemaBackend
|
||||||
, " FOREIGN KEY(", column2sql column
|
, " FOREIGN KEY(", column2sql column
|
||||||
, ") REFERENCES ", table2sql target, "(", column2sql idCol, ")"
|
, ") REFERENCES ", table2sql target, "(", column2sql idCol, ")"
|
||||||
]
|
]
|
||||||
|
, ssbRenameConstraint = \ _table old new -> mconcat
|
||||||
|
[ "ALTER INDEX ", constraint2sql old
|
||||||
|
, " RENAME TO ", constraint2sql new
|
||||||
|
]
|
||||||
, ssbDropConstraint = \ table constraint -> mconcat
|
, ssbDropConstraint = \ table constraint -> mconcat
|
||||||
[ "ALTER TABLE ", table2sql table
|
[ "ALTER TABLE ", table2sql table
|
||||||
, " DROP CONSTRAINT ", constraint2sql constraint
|
, " DROP CONSTRAINT ", constraint2sql constraint
|
||||||
|
|
|
@ -20,16 +20,23 @@ module Database.Persist.Schema.Sql
|
||||||
, ColumnName (..)
|
, ColumnName (..)
|
||||||
, ConstraintName (..)
|
, ConstraintName (..)
|
||||||
, Column (..)
|
, Column (..)
|
||||||
, SqlSchemaBackend (..)
|
, SchemaBackend (..)
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Prelude
|
import Prelude
|
||||||
|
|
||||||
|
import Control.Monad.IO.Class (MonadIO)
|
||||||
|
import Control.Monad.Trans.Class (lift)
|
||||||
|
import Control.Monad.Trans.Reader (ask)
|
||||||
|
import Data.Char (isUpper, toLower)
|
||||||
|
import Data.Foldable (traverse_)
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import Database.Persist.Sql (Sql)
|
import Database.Persist.Sql (Sql, SqlBackend, rawExecute)
|
||||||
import Database.Persist.Types (SqlType)
|
import Database.Persist.Types (SqlType)
|
||||||
|
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
import Database.Persist.Schema
|
import Database.Persist.Schema
|
||||||
|
|
||||||
newtype TableName = TableName { unTableName :: Text }
|
newtype TableName = TableName { unTableName :: Text }
|
||||||
|
@ -44,8 +51,54 @@ data Column = Column
|
||||||
, colNull :: MaybeNull
|
, colNull :: MaybeNull
|
||||||
}
|
}
|
||||||
|
|
||||||
data SqlSchemaBackend = SqlSchemaBackend
|
exec :: MonadIO m => Sql -> SchemaT SqlBackend m ()
|
||||||
{ ssbCreateTable :: TableName -> [Column] -> Sql
|
exec t = lift $ rawExecute t []
|
||||||
|
|
||||||
|
camelWords :: Text -> [Text]
|
||||||
|
camelWords ident =
|
||||||
|
let low = toLower
|
||||||
|
slow = T.singleton . toLower
|
||||||
|
go c t l =
|
||||||
|
let (x, y) = T.break isUpper t
|
||||||
|
in case (T.null x, T.uncons y) of
|
||||||
|
(True, Nothing) -> slow c : l
|
||||||
|
(True, Just (d, r)) -> go d r $ slow c : l
|
||||||
|
(False, Nothing) -> (low c `T.cons` x) : l
|
||||||
|
(False, Just (d, r)) -> go d r $ (low c `T.cons` x) : l
|
||||||
|
(a, b) = T.break isUpper ident
|
||||||
|
in reverse $ case (T.null a, T.uncons b) of
|
||||||
|
(True, Nothing) -> []
|
||||||
|
(True, Just (c, r)) -> go c r []
|
||||||
|
(False, Nothing) -> [a]
|
||||||
|
(False, Just (c, r)) -> go c r [a]
|
||||||
|
|
||||||
|
dbname :: Text -> Text
|
||||||
|
dbname = T.intercalate (T.singleton '_') . camelWords
|
||||||
|
|
||||||
|
entity2table :: EntityName -> TableName
|
||||||
|
entity2table (EntityName t) = TableName $ dbname t
|
||||||
|
|
||||||
|
field2column :: FieldName -> ColumnName
|
||||||
|
field2column (FieldName t) = ColumnName $ dbname t
|
||||||
|
|
||||||
|
unique2constraint :: UniqueName -> ConstraintName
|
||||||
|
unique2constraint (UniqueName t) = ConstraintName $ dbname t
|
||||||
|
|
||||||
|
type2sql :: SchemaBackend SqlBackend -> FieldType -> SqlType
|
||||||
|
type2sql _ (FTPrim t) = t
|
||||||
|
type2sql ssb FTRef = ssbRefType ssb
|
||||||
|
|
||||||
|
mkcolumn :: SchemaBackend SqlBackend -> Field -> Column
|
||||||
|
mkcolumn ssb (Field name typ mnull) = Column
|
||||||
|
{ colName = field2column name
|
||||||
|
, colType = type2sql ssb typ
|
||||||
|
, colNull = mnull
|
||||||
|
}
|
||||||
|
|
||||||
|
instance PersistSchema SqlBackend where
|
||||||
|
data SchemaBackend SqlBackend = SqlSchemaBackend
|
||||||
|
{ ssbRefType :: SqlType
|
||||||
|
, ssbCreateTable :: TableName -> [Column] -> Sql
|
||||||
, ssbRenameTable :: TableName -> TableName -> Sql
|
, ssbRenameTable :: TableName -> TableName -> Sql
|
||||||
, ssbDropTable :: TableName -> Sql
|
, ssbDropTable :: TableName -> Sql
|
||||||
, ssbAddColumn :: TableName -> Column -> Sql
|
, ssbAddColumn :: TableName -> Column -> Sql
|
||||||
|
@ -54,8 +107,54 @@ data SqlSchemaBackend = SqlSchemaBackend
|
||||||
, ssbRenullColumn :: TableName -> ColumnName -> MaybeNull -> Sql
|
, ssbRenullColumn :: TableName -> ColumnName -> MaybeNull -> Sql
|
||||||
, ssbUnnullColumn :: TableName -> ColumnName -> Text -> Sql
|
, ssbUnnullColumn :: TableName -> ColumnName -> Text -> Sql
|
||||||
, ssbDropColumn :: TableName -> ColumnName -> Sql
|
, ssbDropColumn :: TableName -> ColumnName -> Sql
|
||||||
, ssbAddUnique :: TableName -> ConstraintName -> [ColumnName] -> Sql
|
, ssbAddUnique
|
||||||
|
:: TableName -> ConstraintName -> [ColumnName] -> Sql
|
||||||
, ssbAddForeignKey
|
, ssbAddForeignKey
|
||||||
:: TableName -> ConstraintName -> ColumnName -> TableName -> Sql
|
:: TableName -> ConstraintName -> ColumnName -> TableName -> Sql
|
||||||
|
, ssbRenameConstraint
|
||||||
|
:: TableName -> ConstraintName -> ConstraintName -> Sql
|
||||||
, ssbDropConstraint :: TableName -> ConstraintName -> Sql
|
, ssbDropConstraint :: TableName -> ConstraintName -> Sql
|
||||||
}
|
}
|
||||||
|
addEntity (Entity name fields uniques) = do
|
||||||
|
ssb <- ask
|
||||||
|
exec $
|
||||||
|
ssbCreateTable ssb (entity2table name) (map (mkcolumn ssb) fields)
|
||||||
|
traverse_ (addUnique name) uniques
|
||||||
|
removeEntity name = do
|
||||||
|
ssb <- ask
|
||||||
|
exec $ ssbDropTable ssb $ entity2table name
|
||||||
|
addField ent (Field name typ mnull) = do
|
||||||
|
ssb <- ask
|
||||||
|
exec $
|
||||||
|
ssbAddColumn ssb (entity2table ent) $
|
||||||
|
Column (field2column name) (type2sql ssb typ) mnull
|
||||||
|
renameField entity old new = do
|
||||||
|
ssb <- ask
|
||||||
|
exec $
|
||||||
|
ssbRenameColumn ssb
|
||||||
|
(entity2table entity)
|
||||||
|
(field2column old)
|
||||||
|
(field2column new)
|
||||||
|
removeField entity field = do
|
||||||
|
ssb <- ask
|
||||||
|
exec $ ssbDropColumn ssb (entity2table entity) (field2column field)
|
||||||
|
addUnique entity (Unique name fields) = do
|
||||||
|
ssb <- ask
|
||||||
|
exec $
|
||||||
|
ssbAddUnique ssb
|
||||||
|
(entity2table entity)
|
||||||
|
(unique2constraint name)
|
||||||
|
(map field2column fields)
|
||||||
|
renameUnique entity old new = do
|
||||||
|
ssb <- ask
|
||||||
|
exec $
|
||||||
|
ssbRenameConstraint ssb
|
||||||
|
(entity2table entity)
|
||||||
|
(unique2constraint old)
|
||||||
|
(unique2constraint new)
|
||||||
|
removeUnique entity name = do
|
||||||
|
ssb <- ask
|
||||||
|
exec $
|
||||||
|
ssbDropConstraint ssb
|
||||||
|
(entity2table entity)
|
||||||
|
(unique2constraint name)
|
||||||
|
|
Loading…
Reference in a new issue