module Database.PostgreSQL.PQTypes.Model.Domain ( Domain(..) , mkChecks , sqlCreateDomain , sqlAlterDomain , sqlDropDomain ) where import Data.Monoid.Utils import Data.Set (Set, fromList) import Database.PostgreSQL.PQTypes import Prelude import Database.PostgreSQL.PQTypes.Model.Check import Database.PostgreSQL.PQTypes.Model.ColumnType -- Domains are global, i.e. not bound to any particular table. -- The first table that uses a new domain needs to create it -- by a migration. -- -- If a migration that alters the domain needs to be performed, -- there are three possible situations: -- -- 1) The modification doesn't require data change in any of the tables. -- 2) The modification requires data change, but only in one table. -- 3) The modification requires data change in more than one table. -- -- These situations should be handled as follows: -- -- 1) One of the tables that use the domain should migrate it. -- 2) The table that requires data modification should migrate it. -- 3) One of the tables that require data modification should migrate -- it. Note that modification of constraints may conflict with the -- data in the other tables. In this case, these constraints should be -- created as NOT VALID (see -- http://www.postgresql.org/docs/current/static/sql-alterdomain.html -- for more info) and VALIDATEd in the migration of the last table -- with the conflicting data. -- -- TODO: the proper solution to this is to version the domains to be -- able to handle (1) and the first and last part of (3) by migrating -- the domain itself, however that requires substantial change to the -- migration system. -- -- As opposed to the current solution, the other temporary one is to -- create domains statically and not worry about migrations. The problem -- with this approach is the separation of domain creation from the rest -- of the universe, which results in problems later, when the proper -- solution will have to be implemented (i.e. one would need to go back -- and edit old migrations), whereas the current solution makes the -- transition trivial. data Domain = Domain { -- | Name of the domain. domName :: RawSQL () -- | Type of the domain. , domType :: ColumnType -- | Defines whether the domain value can be NULL. -- *Cannot* be superseded by a table column definition. , domNullable :: Bool -- Default value for the domain. *Can* be -- superseded by a table column definition. , domDefault :: Maybe (RawSQL ()) -- Set of constraint checks on the domain. , domChecks :: Set Check } deriving (Eq, Ord, Show) mkChecks :: [Check] -> Set Check mkChecks = fromList sqlCreateDomain :: Domain -> RawSQL () sqlCreateDomain Domain{..} = smconcat [ "CREATE DOMAIN" <+> domName <+> "AS" , columnTypeToSQL domType , if domNullable then "NULL" else "NOT NULL" , maybe "" ("DEFAULT" <+>) domDefault ] sqlAlterDomain :: RawSQL () -> RawSQL () -> RawSQL () sqlAlterDomain dname alter = "ALTER DOMAIN" <+> dname <+> alter sqlDropDomain :: RawSQL () -> RawSQL () sqlDropDomain dname = "DROP DOMAIN" <+> dname