beam-automigrate-0.1.0.0: DB migration library for beam, targeting Postgres.

Safe HaskellNone
LanguageHaskell2010

Database.Beam.AutoMigrate.Annotated

Contents

Description

This module provides an AnnotatedDatabaseSettings type to be used as a drop-in replacement for the standard DatabaseSettings. Is it possible to "downcast" an AnnotatedDatabaseSettings to a standard DatabaseSettings simply by calling deAnnotateDatabase.

Synopsis

User annotations

data Annotation where Source #

A user-defined annotation. Currently the only possible annotation is the ability to specify for which tables the FK-discovery algorithm is "turned" off.

Constructors

UserDefinedFk :: TableKind -> Annotation

Specifies that the given TableKind (i.e. a table) has user-specified FK constraints. This is useful in case of ambiguity, i.e. when the automatic FK-discovery algorithm is not capable to infer the correct ForeignKey constraints for a Table. This can happen when the TableConstraint type family is not injective, which means there are multiple tables of table FooT in the DB. Consider a situation where we have a table BarT having a field of type barField :: PrimaryKey FooT f but (crucially) there are two tables with type f (TableEntity FooT) in the final database. In this circumstance the FK-discovery algorithm will bail out with a (static) error, and this is where this annotation comes into play: it allows us to selectively "disable" the discovery for the given table(s), and manually override the FKs.

Caveat emptor: Due to what we said earlier (namely that we cannot enforce that tables are not repeated multiple times within a DB) there might be situations where also the specified TableKind is not unique. In this case the annotation would affect all the tables of the same type, but that is usually unavoidable, as the ambiguity was already present the minute we introduced in the DB two tables of the same type, and so it makes sense for the user to fully resolve the ambiguity manually.

Instances
(IsAnnotatedDatabaseEntity be (TableEntity tbl), GColumns GenSequences (Rep (TableSchema tbl)), Generic (TableSchema tbl), Table tbl, GTableConstraintColumns be db (Rep (TableSchema tbl))) => GTableEntry be db ([] :: [Annotation]) False (K1 R (AnnotatedDatabaseEntity be db (TableEntity tbl)) :: Type -> Type) Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Generic

(IsAnnotatedDatabaseEntity be (TableEntity tbl), GColumns GenSequences (Rep (TableSchema tbl)), Generic (TableSchema tbl), Table tbl) => GTableEntry be db ([] :: [Annotation]) True (K1 R (AnnotatedDatabaseEntity be db (TableEntity tbl)) :: Type -> Type) Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Generic

(GColumns GenSequences (Rep (TableSchema tbl)), Generic (TableSchema tbl), Table tbl) => GTableEntry be db (UserDefinedFk tbl' ': xs) True (K1 R (AnnotatedDatabaseEntity be db (TableEntity tbl)) :: Type -> Type) Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Generic

(GColumns GenSequences (Rep (TableSchema tbl)), Generic (TableSchema tbl), Table tbl, GTableEntry be db xs (TestTableEqual tbl tbl') (K1 R (AnnotatedDatabaseEntity be db (TableEntity tbl)) :: Type -> Type)) => GTableEntry be db (UserDefinedFk tbl' ': xs) False (K1 R (AnnotatedDatabaseEntity be db (TableEntity tbl)) :: Type -> Type) Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Generic

Annotating a DatabaseSettings

type AnnotatedDatabaseSettings be db = db (AnnotatedDatabaseEntity be db) Source #

An AnnotatedDatabaseSettings is similar in spirit to a beam-core DatabaseSettings, but it embellish the latter with extra metadata this library can use to derive more information about the input DB, like table and column constraints.

data AnnotatedDatabaseEntity be (db :: (* -> *) -> *) entityType where Source #

An AnnotatedDatabaseEntity wraps the underlying DatabaseEntity together with an annotated description called AnnotatedDatabaseEntityDescriptor, which is once again similar to the standard DatabaseEntityDescriptor from Beam.

An AnnotatedDatabaseEntityDescriptor is not a concrete type, but rather a data family provided by the IsAnnotatedDatabaseEntity.

Constructors

AnnotatedDatabaseEntity :: (IsAnnotatedDatabaseEntity be entityType, IsDatabaseEntity be entityType) => AnnotatedDatabaseEntityDescriptor be entityType -> DatabaseEntity be db entityType -> AnnotatedDatabaseEntity be db entityType 
Instances
(Generic (innerDB (AnnotatedDatabaseEntity be outerDB)), Database be innerDB, GTableEntry be outerDB xs found (Rep (innerDB (AnnotatedDatabaseEntity be outerDB)))) => GTableEntry be outerDB xs found (K1 R (innerDB (AnnotatedDatabaseEntity be outerDB)) :: Type -> Type) Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Generic

Methods

gTableEntries :: AnnotatedDatabaseSettings be outerDB -> Proxy xs -> Proxy found -> K1 R (innerDB (AnnotatedDatabaseEntity be outerDB)) p -> ([(TableName, Table)], Sequences) Source #

(IsAnnotatedDatabaseEntity be (TableEntity tbl), Table tbl, GEnums be db (Rep (TableSchema tbl)), Generic (TableSchema tbl)) => GEnums be db (S1 f (K1 R (AnnotatedDatabaseEntity be db (TableEntity tbl)) :: k -> Type) :: k -> Type) Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Generic

(IsAnnotatedDatabaseEntity be (TableEntity tbl), GColumns GenSequences (Rep (TableSchema tbl)), Generic (TableSchema tbl), Table tbl, GTableConstraintColumns be db (Rep (TableSchema tbl))) => GTableEntry be db ([] :: [Annotation]) False (K1 R (AnnotatedDatabaseEntity be db (TableEntity tbl)) :: Type -> Type) Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Generic

(IsAnnotatedDatabaseEntity be (TableEntity tbl), GColumns GenSequences (Rep (TableSchema tbl)), Generic (TableSchema tbl), Table tbl) => GTableEntry be db ([] :: [Annotation]) True (K1 R (AnnotatedDatabaseEntity be db (TableEntity tbl)) :: Type -> Type) Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Generic

(GTableLookupTablesExpectFail sel tbl (Rep (innerDb (AnnotatedDatabaseEntity be outerDb))) k, Generic (innerDb (AnnotatedDatabaseEntity be outerDb)), Database be innerDb) => GTableLookupTablesExpectFail sel tbl (K1 R (innerDb (AnnotatedDatabaseEntity be outerDb)) :: Type -> Type) k Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Generic

Methods

gTableLookupTablesExpectFail :: Proxy sel -> Proxy tbl -> (TableName, [ColumnName]) -> K1 R (innerDb (AnnotatedDatabaseEntity be outerDb)) p -> k p -> (TableName, [ColumnName]) Source #

(GTableLookupTableExpectFail (TestTableEqual tbl tbl') sel tbl k, Beamable tbl') => GTableLookupTablesExpectFail sel tbl (K1 R (AnnotatedDatabaseEntity be db (TableEntity tbl')) :: Type -> Type) k Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Generic

(GTableLookupTables sel tbl (Rep (innerDB (AnnotatedDatabaseEntity be outerDB))) k, Database be innerDB, Generic (innerDB (AnnotatedDatabaseEntity be outerDB))) => GTableLookupTables sel tbl (K1 R (innerDB (AnnotatedDatabaseEntity be outerDB)) :: Type -> Type) k Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Generic

Methods

gTableLookupTables :: Proxy sel -> Proxy tbl -> K1 R (innerDB (AnnotatedDatabaseEntity be outerDB)) p -> k p -> (TableName, [ColumnName]) Source #

(GTableLookupTable (TestTableEqual tbl tbl') sel tbl k, Beamable tbl', Table tbl') => GTableLookupTables sel tbl (K1 R (AnnotatedDatabaseEntity be db (TableEntity tbl')) :: Type -> Type) k Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Generic

Methods

gTableLookupTables :: Proxy sel -> Proxy tbl -> K1 R (AnnotatedDatabaseEntity be db (TableEntity tbl')) p -> k p -> (TableName, [ColumnName]) Source #

(GColumns GenSequences (Rep (TableSchema tbl)), Generic (TableSchema tbl), Table tbl) => GTableEntry be db (UserDefinedFk tbl' ': xs) True (K1 R (AnnotatedDatabaseEntity be db (TableEntity tbl)) :: Type -> Type) Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Generic

(GColumns GenSequences (Rep (TableSchema tbl)), Generic (TableSchema tbl), Table tbl, GTableEntry be db xs (TestTableEqual tbl tbl') (K1 R (AnnotatedDatabaseEntity be db (TableEntity tbl)) :: Type -> Type)) => GTableEntry be db (UserDefinedFk tbl' ': xs) False (K1 R (AnnotatedDatabaseEntity be db (TableEntity tbl)) :: Type -> Type) Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Generic

type TableSchema tbl = tbl (TableFieldSchema tbl) Source #

A table schema.

data TableFieldSchema (tbl :: (* -> *) -> *) ty where Source #

A schema for a field within a given table

Constructors

TableFieldSchema 

Fields

Instances
GTableConstraintColumns be db (S1 m (K1 R (TableFieldSchema tbl ty) :: k -> Type) :: k -> Type) Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Generic

GEnums be db (S1 f (K1 R (PrimaryKey tbl1 (g (TableFieldSchema tbl2))) :: k -> Type) :: k -> Type) Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Generic

Methods

gEnums :: AnnotatedDatabaseSettings be db -> S1 f (K1 R (PrimaryKey tbl1 (g (TableFieldSchema tbl2)))) p -> Enumerations Source #

GEnums be db (S1 f (K1 R (PrimaryKey tbl1 (TableFieldSchema tbl2)) :: k -> Type) :: k -> Type) Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Generic

HasColumnType ty => GEnums be db (S1 f (K1 R (TableFieldSchema tbl ty) :: k -> Type) :: k -> Type) Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Generic

HasCompanionSequence ty => GColumns GenSequences (S1 m (K1 R (TableFieldSchema tbl ty) :: Type -> Type)) Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Generic

GColumns NoGenSequences (S1 m (K1 R (TableFieldSchema tbl ty) :: Type -> Type)) Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Generic

data FieldSchema ty where Source #

Instances
Show (FieldSchema ty) Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Annotated

defaultTableSchema :: forall tbl. (GDefaultTableSchema (Rep (TableSchema tbl) ()) (Rep (TableSettings tbl) ()), Generic (TableSchema tbl), Generic (TableSettings tbl)) => TableSettings tbl -> TableSchema tbl Source #

Downcasting annotated types

deannotate :: SimpleGetter (AnnotatedDatabaseEntity be db entityType) (DatabaseEntity be db entityType) Source #

Specifying constraints

Once an AnnotatedDatabaseSettings has been acquired, the user is able to customise the default medatata associated with it. In order to do so, one can reuse the existing machinery from Beam, in particular the withDbModification. For example:

annotatedDB :: AnnotatedDatabaseSettings Postgres FlowerDB
annotatedDB = defaultAnnotatedDbSettings flowerDB `withDbModification` dbModification
  { dbFlowers   = annotateTableFields tableModification { flowerDiscounted = defaultsTo (val_ $ Just True)
                                                        , flowerPrice = defaultsTo (val_ $ Just 10.0)
                                                        }
               <> uniqueFields [U (addressPostalCode . addressRegion . flowerAddress)]
  , dbLineItems = annotateTableFields tableModification { lineItemDiscount = defaultsTo (val_ $ Just False) }
               <> uniqueFields [U lineItemFlowerID, U lineItemOrderID, U lineItemQuantity]
  , dbOrders = annotateTableFields tableModification { orderTime = defaultsTo (cast_ currentTimestamp_ utctime) }
             <> foreignKeyOnPk (dbFlowers flowerDB) orderFlowerIdRef Cascade Restrict
             <> uniqueFields [U (addressPostalCode . addressRegion . orderAddress)]
  }

Refer to the rest of the documentation for this module for more information about annotateTableFields, uniqueFields and foreignKeyOnPk.

annotateTableFields :: tbl (FieldModification (TableFieldSchema tbl)) -> EntityModification (AnnotatedDatabaseEntity be db) be (TableEntity tbl) Source #

Annotate the table fields for a given AnnotatedDatabaseEntity. Refer to the $specifyingConstraints section for an example.

Specifying Column constraints

Due to the fact most column constraints can span multiple columns (think about UNIQUE or FOREIGN KEY) the only constraint associated to a TableFieldSchema we allow to customise at the "single-column-granularity" is DEFAULT.

defaultsTo :: (HasColumnType ty, HasSqlValueSyntax PgValueSyntax ty) => (forall ctx s. QGenExpr ctx Postgres s ty) -> FieldModification (TableFieldSchema tbl) ty Source #

Specify a default value for an entity. The relevant migration will generate an associated SQL DEFAULT. This function accepts any Beam's expression that also the standard field machinery would accept, for example:

defaultsTo (val_ $ Just 10)

Specifying Table constraints

Is it possible to annotate an AnnotatedDatabaseEntity with UNIQUE and FOREIGN KEY constraints.

data UniqueConstraint (tbl :: (* -> *) -> *) where Source #

Constructors

U :: HasColumnNames entity tbl => (tbl (TableField tbl) -> entity) -> UniqueConstraint tbl

Use this to "tag" a standard Beam TableField selector or TableConstraint.

Unique constraint

uniqueConstraintOn :: [UniqueConstraint tbl] -> EntityModification (AnnotatedDatabaseEntity be db) be (TableEntity tbl) Source #

Given a list of TableField selectors wrapped in a UniqueConstraint type constructor, it adds to the relevant AnnotatedDatabaseEntity a new UNIQUE TableConstraint composed by all the fields specified. To put it differently, every call to uniqueConstraintOn generates a separate UNIQUE constraint composed by the listed fields. If a TableConstraint is passed as input, it will desugar under the hood into as many columns as the primary key refers to.

Foreign key constraint

data ForeignKeyConstraint (tbl :: (* -> *) -> *) (tbl' :: (* -> *) -> *) where Source #

Constructors

References :: Beamable (PrimaryKey tbl') => (tbl (TableField tbl) -> PrimaryKey tbl' (TableField tbl)) -> (tbl' (TableField tbl') -> Columnar Identity (TableField tbl' ty)) -> ForeignKeyConstraint tbl tbl' 

foreignKeyOnPk Source #

Arguments

:: (Beamable (PrimaryKey tbl'), Beamable tbl', Table tbl', PrimaryKey tbl' f ~ PrimaryKey tbl' g) 
=> DatabaseEntity be db (TableEntity tbl')

The DatabaseEntity of the referenced table.

-> (tbl (TableField tbl) -> PrimaryKey tbl' (TableField tbl))

A function yielding a TableConstraint. This is usually a record field of the table you want to define the FK for, and it must have PrimaryKey externalTable f as its column-tag.

-> ReferenceAction

What do to "on delete"

-> ReferenceAction

What do to "on update"

-> EntityModification (AnnotatedDatabaseEntity be db) be (TableEntity tbl) 

Special-case combinator to use when defining FK constraints referencing the primary key of the target table.

foreignKeyOn Source #

Arguments

:: Beamable tbl' 
=> DatabaseEntity be db (TableEntity tbl') 
-> [ForeignKeyConstraint tbl tbl'] 
-> ReferenceAction

On Delete

-> ReferenceAction

On Update

-> EntityModification (AnnotatedDatabaseEntity be db) be (TableEntity tbl) 

Other types and functions

type TableKind = (Type -> Type) -> Type Source #

To make kind signatures more readable.

type DatabaseKind = (Type -> Type) -> Type Source #

To make kind signatures more readable.

Ports from Beam

zipTables :: (Generic (db f), Generic (db g), Generic (db h), Monad m, GZipDatabase be f g h (Rep (db f)) (Rep (db g)) (Rep (db h))) => Proxy be -> (forall tbl. (IsAnnotatedDatabaseEntity be tbl, AnnotatedDatabaseEntityRegularRequirements be tbl) => f tbl -> g tbl -> m (h tbl)) -> db f -> db g -> m (db h) Source #

Zip tables together. Unfortunately we cannot reuse the stock zipTables from 'beam-core', because it works by supplying a rank-2 function with IsDatabaseEntity and DatabaseEntityRegularRequirements as witnesses, we we need the annotated counterparts instead.

This function can be written without the need of a typeclass, but alas it requires the unexported GZipDatabase from 'beam-core', so we had to re-implement this ourselves for now.

class GZipDatabase be f g h x y z Source #

See above on why this class has been re-implemented.

Minimal complete definition

gZipDatabase

Instances
(Database be db, Generic (db f), Generic (db g), Generic (db h), GZipDatabase be f g h (Rep (db f)) (Rep (db g)) (Rep (db h))) => GZipDatabase be f g h (K1 R (db f) :: Type -> Type) (K1 R (db g) :: Type -> Type) (K1 R (db h) :: Type -> Type) Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Annotated

Methods

gZipDatabase :: Monad m => (Proxy f, Proxy g, Proxy h, Proxy be) -> (forall tbl. (IsAnnotatedDatabaseEntity be tbl, AnnotatedDatabaseEntityRegularRequirements be tbl) => f tbl -> g tbl -> m (h tbl)) -> K1 R (db f) () -> K1 R (db g) () -> m (K1 R (db h) ())

(IsAnnotatedDatabaseEntity be tbl, AnnotatedDatabaseEntityRegularRequirements be tbl) => GZipDatabase be f g h (K1 R (f tbl) :: Type -> Type) (K1 R (g tbl) :: Type -> Type) (K1 R (h tbl) :: Type -> Type) Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Annotated

Methods

gZipDatabase :: Monad m => (Proxy f, Proxy g, Proxy h, Proxy be) -> (forall tbl0. (IsAnnotatedDatabaseEntity be tbl0, AnnotatedDatabaseEntityRegularRequirements be tbl0) => f tbl0 -> g tbl0 -> m (h tbl0)) -> K1 R (f tbl) () -> K1 R (g tbl) () -> m (K1 R (h tbl) ())

(GZipDatabase be f g h ax ay az, GZipDatabase be f g h bx by bz) => GZipDatabase be f g h (ax :*: bx) (ay :*: by) (az :*: bz) Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Annotated

Methods

gZipDatabase :: Monad m => (Proxy f, Proxy g, Proxy h, Proxy be) -> (forall tbl. (IsAnnotatedDatabaseEntity be tbl, AnnotatedDatabaseEntityRegularRequirements be tbl) => f tbl -> g tbl -> m (h tbl)) -> (ax :*: bx) () -> (ay :*: by) () -> m ((az :*: bz) ())

GZipDatabase be f g h x y z => GZipDatabase be f g h (M1 a b x) (M1 a b y) (M1 a b z) Source # 
Instance details

Defined in Database.Beam.AutoMigrate.Annotated

Methods

gZipDatabase :: Monad m => (Proxy f, Proxy g, Proxy h, Proxy be) -> (forall tbl. (IsAnnotatedDatabaseEntity be tbl, AnnotatedDatabaseEntityRegularRequirements be tbl) => f tbl -> g tbl -> m (h tbl)) -> M1 a b x () -> M1 a b y () -> m (M1 a b z ())

Internals

pgDefaultConstraint :: forall ty. (HasColumnType ty, HasSqlValueSyntax PgValueSyntax ty) => (forall ctx s. QGenExpr ctx Postgres s ty) -> ColumnConstraint Source #

Postgres-specific function to convert any QGenExpr into a meaningful PgExpressionSyntax, so that it can be rendered inside a Default column constraint.