Specify DB migrations in terms of your persistent model
[[ 🗃
^0rd3E persistent-migration
]] ::
[📥 Inbox]
[📤 Outbox]
[🐤 Followers]
[🤝 Collaborators]
[🛠 Changes]
Clone
HTTPS:
darcs clone https://vervis.peers.community/repos/0rd3E
SSH:
darcs clone USERNAME@vervis.peers.community:0rd3E
Tags
TODO
Schema.hs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | {- This file is part of persistent-migration.
-
- Written in 2016, 2018, 2019, 2020 by fr33domlover <fr33domlover@riseup.net>.
-
- ♡ Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
{-# LANGUAGE TypeFamilies #-}
module Database.Persist.Schema
( SchemaT
, Migration
, PersistSchema (..)
)
where
import Control.Monad.IO.Class (MonadIO)
import Control.Monad.Trans.Reader (ReaderT)
import Data.List.NonEmpty (NonEmpty)
import Data.Text (Text)
import Database.Persist.Class (PersistField, BackendKey)
import Database.Persist.BackendDataType
import Database.Persist.Schema.Types
import Database.Persist.Schema.Validate (SchemaAction)
type SchemaT backend m = ReaderT (SchemaBackend backend, Text) (ReaderT backend m)
-- | A migration action specifies 2 things:
--
-- (1) A function which takes a schema, and checks whether the migration action
-- is valid for it. For example, if you want to remove an entity, check in
-- the schema that an entity by the same name already exists, otherwise
-- this migration action is invalid, something went out of sync with the
-- migration list. If the action is invalid, an error message is returned.
-- Otherwise, the action is applied to the schema, and an updated schema is
-- returned. For example, the entity with the given name is removed.
--
-- This validity check and schema state tracking isn't strictly necessary,
-- because the database transaction will raise an error, but it does give
-- a few benefits:
--
-- * Ability to validate a set of migrations without or before doing
-- any database access
-- * Clear error messages phrased in high-level terms of entities, not
-- in lower level terms of the persistent backend specifics
-- * Ability to compare the actual schema of the running database with
-- the structure we track on the Haskell side, and detect errors and
-- mismatches early, avoiding breaking the migration process or the
-- database schema in case of errors or bugs
--
-- (2) Database transaction, the actual IO action that updates the running
-- database
type Migration b m = (SchemaAction b, SchemaT b m ())
-- | 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
data SchemaBackend backend
hasEntities
:: MonadIO m => SchemaT backend m Bool
hasSchemaEntity
:: MonadIO m => SchemaT backend m Bool
getEntityNames
:: MonadIO m => SchemaT backend m [EntityName]
getFieldNames
:: MonadIO m => EntityName -> SchemaT backend m [FieldName]
addEntity
:: MonadIO m => Entity backend -> SchemaT backend m ()
addEntities
:: MonadIO m => NonEmpty (Entity backend) -> SchemaT backend m ()
renameEntity
:: MonadIO m => EntityName -> EntityName -> SchemaT backend m ()
removeEntity
:: MonadIO m => EntityName -> SchemaT backend m ()
--addField
-- :: MonadIO m
-- => EntityName -> Maybe Text -> Field backend -> SchemaT backend m ()
addFieldPrimOptional
:: (MonadIO m, PersistField a, PersistFieldBackend a backend)
=> EntityName -> Maybe a -> FieldName -> SchemaT backend m ()
addFieldPrimRequired
:: (MonadIO m, PersistField a, PersistFieldBackend a backend)
=> EntityName -> a -> FieldName -> SchemaT backend m ()
addFieldRefOptional
:: MonadIO m
=> EntityName
-> Maybe (BackendKey backend)
-> FieldName
-> EntityName
-> SchemaT backend m ()
addFieldRefRequired
:: MonadIO m
=> EntityName
-> BackendKey backend
-> FieldName
-> EntityName
-> SchemaT backend m ()
addFieldRefRequiredEmpty
:: MonadIO m
=> EntityName
-> FieldName
-> EntityName
-> SchemaT backend m ()
renameField
:: MonadIO m
=> EntityName -> FieldName -> FieldName -> SchemaT backend m ()
removeField
:: MonadIO m => EntityName -> FieldName -> SchemaT backend m ()
addUnique
:: MonadIO m => EntityName -> Unique -> SchemaT backend m ()
renameUnique
:: MonadIO m
=> EntityName -> UniqueName -> UniqueName -> SchemaT backend m ()
removeUnique
:: MonadIO m => EntityName -> UniqueName -> SchemaT backend m ()
setFieldMaybe
:: MonadIO m => EntityName -> FieldName -> SchemaT backend m ()
--unsetFieldMaybe
-- :: MonadIO m => EntityName -> FieldName -> Text -> SchemaT backend m ()
unsetFieldPrimMaybe
:: (MonadIO m, PersistField a, PersistFieldBackend a backend)
=> EntityName -> FieldName -> a -> SchemaT backend m ()
unsetFieldRefMaybe
:: MonadIO m
=> EntityName
-> FieldName
-> BackendKey backend
-> SchemaT backend m ()
--setFieldDefault
-- :: MonadIO m => EntityName -> FieldName -> Text -> SchemaT backend m ()
setFieldPrimDefault
:: (MonadIO m, PersistField a, PersistFieldBackend a backend)
=> EntityName -> FieldName -> a -> SchemaT backend m ()
setFieldRefDefault
:: MonadIO m
=> EntityName
-> FieldName
-> BackendKey backend
-> SchemaT backend m ()
unsetFieldDefault
:: MonadIO m => EntityName -> FieldName -> SchemaT backend m ()
-- | NOTE this relies on DB doing type cast implicitly
-- (technically e.g. in PostgreSQL you can also define your own type casts
-- and then I guess maybe this works in more cases?)
changeFieldTypeImplicit
:: MonadIO m
=> EntityName
-> FieldName
-> BackendDataType backend
-> SchemaT backend m ()
changeFieldTypePrimRequiredHs
:: (MonadIO m, PersistField a, PersistFieldBackend a backend, PersistFieldBackend b backend, PersistDefault b)
=> EntityName
-> FieldName
-> (a -> b)
-> SchemaT backend m ()
-- should retyping take some detail about the conversion? hmmm there are 3
-- options:
--
-- (1) don't specify anything, let postgresql do implicit conversion
-- (2) specify USING in SQL i.e. specify the type cast in SQL
-- (3) provide Haskell function, and then the migration code reads the
-- whole table, changes column type, applies conversion in Haskell and
-- commits back into the DB - but how do we take the Haskell function, how
-- to link the types in Haskell to the raw SQL? Maybe using PersistentValue
-- the way rawSql function can have values inserted at '?'s and also parsed
-- the returned values?
|