-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | A simple interface for generating persistent data and linking its dependencies -- -- Please see README.md @package graphula @version 2.0.2.2 -- | Internal type class(es) for Graphula-related behaviors module Graphula.Class type MonadGraphula m = (Monad m, MonadIO m, MonadGraphulaBackend m, MonadGraphulaFrontend m) class MonadGraphulaFrontend m insert :: (MonadGraphulaFrontend m, PersistEntityBackend a ~ SqlBackend, PersistEntity a, Monad m, GraphulaSafeToInsert a) => Maybe (Key a) -> a -> m (Maybe (Entity a)) remove :: (MonadGraphulaFrontend m, PersistEntityBackend a ~ SqlBackend, PersistEntity a, Monad m) => Key a -> m () class MonadGraphulaBackend m where { type Logging m :: Type -> Constraint; } askGen :: MonadGraphulaBackend m => m (IORef QCGen) logNode :: (MonadGraphulaBackend m, Logging m a) => a -> m () -- | A class that provides backwards compatibility with -- persistent-2.14 -- -- If you are using that version or above, then this is a class alias for -- SafeToInsert. Otherwise, it is an identity alias. class (SafeToInsert a) => GraphulaSafeToInsert a instance Database.Persist.Class.PersistEntity.SafeToInsert a => Graphula.Class.GraphulaSafeToInsert a -- | Arbitrary operations that respect Graphula's seed module Graphula.Arbitrary -- | Run a generator -- -- This is akin to generate, but utilizing graphula's generation. -- The size passed to the generator is always 30; if you want another -- size then you should explicitly use resize. generate :: (MonadIO m, MonadGraphulaBackend m) => Gen a -> m a -- | Machinery for the Generic-based HasDependencies -- instance module Graphula.Dependencies.Generic class GHasDependencies nodeTyProxy depsTyProxy node deps genericDependsOn :: GHasDependencies nodeTyProxy depsTyProxy node deps => nodeTyProxy -> depsTyProxy -> node -> deps -> node instance (Graphula.Dependencies.Generic.FindMatches nodeTy depsTy node deps GHC.Types.~ fields, Graphula.Dependencies.Generic.GHasDependenciesRecursive (Data.Proxy.Proxy fields) node deps) => Graphula.Dependencies.Generic.GHasDependencies (Data.Proxy.Proxy nodeTy) (Data.Proxy.Proxy depsTy) (Data.Either.Either node Data.Void.Void) (Data.Either.Either deps Data.Void.Void) instance (a GHC.Types.~ dep, Graphula.Dependencies.Generic.GHasDependenciesRecursive (Data.Proxy.Proxy fields) as deps) => Graphula.Dependencies.Generic.GHasDependenciesRecursive (Data.Proxy.Proxy ('Graphula.Dependencies.Generic.Match a : fields)) (a, as) (dep, deps) instance Graphula.Dependencies.Generic.GHasDependenciesRecursive (Data.Proxy.Proxy fields) as deps => Graphula.Dependencies.Generic.GHasDependenciesRecursive (Data.Proxy.Proxy ('Graphula.Dependencies.Generic.NoMatch a : fields)) (a, as) deps instance Graphula.Dependencies.Generic.GHasDependenciesRecursive (Data.Proxy.Proxy '[]) () () instance Graphula.Dependencies.Generic.GHasDependencies (Data.Proxy.Proxy nodeTy) (Data.Proxy.Proxy depsTy) Data.Void.Void (Data.Either.Either () Data.Void.Void) instance (TypeError ...) => Graphula.Dependencies.Generic.GHasDependencies (Data.Proxy.Proxy nodeTy) (Data.Proxy.Proxy depsTy) Data.Void.Void (Data.Either.Either deps rest) instance (TypeError ...) => Graphula.Dependencies.Generic.GHasDependencies (Data.Proxy.Proxy nodeTy) (Data.Proxy.Proxy depsTy) (Data.Either.Either left (Data.Either.Either right rest)) (Data.Either.Either deps Data.Void.Void) instance (TypeError ...) => Graphula.Dependencies.Generic.GHasDependencies (Data.Proxy.Proxy nodeTy) (Data.Proxy.Proxy depsTy) (Data.Either.Either node Data.Void.Void) (Data.Either.Either left (Data.Either.Either right rest)) instance (TypeError ...) => Graphula.Dependencies.Generic.GHasDependencies (Data.Proxy.Proxy nodeTy) (Data.Proxy.Proxy depsTy) (Data.Either.Either left1 (Data.Either.Either right1 rest1)) (Data.Either.Either left2 (Data.Either.Either right2 rest2)) instance (TypeError ...) => Graphula.Dependencies.Generic.GHasDependencies (Data.Proxy.Proxy nodeTy) (Data.Proxy.Proxy depsTy) node Data.Void.Void -- | A version of GraphulaT that removes all -- inserted data afterward module Graphula.Idempotent data GraphulaIdempotentT m a runGraphulaIdempotentT :: MonadUnliftIO m => GraphulaIdempotentT m a -> m a instance GHC.Base.Monad m => Control.Monad.Reader.Class.MonadReader (GHC.IORef.IORef (m ())) (Graphula.Idempotent.GraphulaIdempotentT m) instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Graphula.Idempotent.GraphulaIdempotentT m) instance GHC.Base.Monad m => GHC.Base.Monad (Graphula.Idempotent.GraphulaIdempotentT m) instance GHC.Base.Applicative m => GHC.Base.Applicative (Graphula.Idempotent.GraphulaIdempotentT m) instance GHC.Base.Functor m => GHC.Base.Functor (Graphula.Idempotent.GraphulaIdempotentT m) instance Control.Monad.IO.Unlift.MonadUnliftIO m => Control.Monad.IO.Unlift.MonadUnliftIO (Graphula.Idempotent.GraphulaIdempotentT m) instance Control.Monad.Trans.Class.MonadTrans Graphula.Idempotent.GraphulaIdempotentT instance (Control.Monad.IO.Class.MonadIO m, Graphula.Class.MonadGraphulaFrontend m) => Graphula.Class.MonadGraphulaFrontend (Graphula.Idempotent.GraphulaIdempotentT m) -- | A version of GraphulaT that logs the generated graph module Graphula.Logged data GraphulaLoggedT m a -- | Run the graph while logging to a temporary file runGraphulaLoggedT :: MonadUnliftIO m => GraphulaLoggedT m a -> m a -- | runGraphulaLoggedT, but to the specified file runGraphulaLoggedWithFileT :: MonadUnliftIO m => FilePath -> GraphulaLoggedT m a -> m a -- | runGraphulaLoggedT, but using the custom action to accumulate runGraphulaLoggedUsingT :: MonadUnliftIO m => (IORef (Seq Text) -> HUnitFailure -> m a) -> GraphulaLoggedT m a -> m a instance GHC.Base.Monad m => Control.Monad.Reader.Class.MonadReader (GHC.IORef.IORef (Data.Sequence.Internal.Seq Data.Text.Internal.Text)) (Graphula.Logged.GraphulaLoggedT m) instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Graphula.Logged.GraphulaLoggedT m) instance GHC.Base.Monad m => GHC.Base.Monad (Graphula.Logged.GraphulaLoggedT m) instance GHC.Base.Applicative m => GHC.Base.Applicative (Graphula.Logged.GraphulaLoggedT m) instance GHC.Base.Functor m => GHC.Base.Functor (Graphula.Logged.GraphulaLoggedT m) instance Control.Monad.IO.Unlift.MonadUnliftIO m => Control.Monad.IO.Unlift.MonadUnliftIO (Graphula.Logged.GraphulaLoggedT m) instance Control.Monad.Trans.Class.MonadTrans Graphula.Logged.GraphulaLoggedT instance (Graphula.Class.MonadGraphulaBackend m, Control.Monad.IO.Class.MonadIO m) => Graphula.Class.MonadGraphulaBackend (Graphula.Logged.GraphulaLoggedT m) instance (GHC.Base.Monad m, Graphula.Class.MonadGraphulaFrontend m) => Graphula.Class.MonadGraphulaFrontend (Graphula.Logged.GraphulaLoggedT m) -- | An empty Constraint -- -- Graphula accepts constraints for various uses. Frontends do not always -- utilize these constraints. NoConstraint is a universal class -- that all types inhabit. It has no behavior and no additional -- constraints. module Graphula.NoConstraint class NoConstraint a instance Graphula.NoConstraint.NoConstraint a module Graphula.Dependencies class HasDependencies a where { -- | A data type declaring the model's dependencies -- -- Models with no dependencies can declare an empty instance, -- --
-- instance HasDependencies School
--
--
-- Models with one dependency must use the Only 1-tuple
-- constructor,
--
--
-- instance HasDependencies Teacher where
-- type Dependencies Teacher = Only SchoolId
--
--
-- Models with multiple dependencies use tuple syntax,
--
--
-- instance HasDependencies Course where
-- type Dependencies Course = (SchoolId, TeacherId)
--
type Dependencies a;
-- | Specify the method for resolving a node's key
--
-- This can be
--
--
-- 'SourceDefault -- automatically generate keys from the database
-- 'SourceArbitrary -- automatically generate keys using Arbitrary
-- 'SourceExternal -- explicitly pass a key using nodeKeyed
--
--
-- Most types will use SourceDefault or SourceArbitrary.
-- Only use SourceExternal if the key for a value is always
-- defined externally.
type KeySource a :: KeySourceType;
type Dependencies _a = ();
type KeySource _a = 'SourceDefault;
}
-- | Assign values from the Dependencies collection to a value
--
-- This must be an idempotent operation. Law:
--
-- -- (\x d -> x `dependsOn` d `dependsOn` d) = dependsOn ---- -- The default, Generic-based implementation will assign values by -- the order of the fields in the model's type. dependsOn :: HasDependencies a => a -> Dependencies a -> a -- | Assign values from the Dependencies collection to a value -- -- This must be an idempotent operation. Law: -- --
-- (\x d -> x `dependsOn` d `dependsOn` d) = dependsOn ---- -- The default, Generic-based implementation will assign values by -- the order of the fields in the model's type. dependsOn :: (HasDependencies a, HasEot a, HasEot (Dependencies a), GHasDependencies (Proxy a) (Proxy (Dependencies a)) (Eot a) (Eot (Dependencies a))) => a -> Dependencies a -> a -- | For entities that only have singular Dependencies newtype Only a Only :: a -> Only a [fromOnly] :: Only a -> a only :: a -> Only a data KeySourceType -- | Generate keys using the database's DEFAULT strategy SourceDefault :: KeySourceType -- | Generate keys using the Arbitrary instance for the Key SourceArbitrary :: KeySourceType -- | Always explicitly pass an external key -- -- See nodeKeyed. SourceExternal :: KeySourceType -- | Abstract constraint that some a can generate a key -- -- This is part of ensuring better error messages. class (GenerateKeyInternal (KeySource a) a, KeyConstraint (KeySource a) a) => GenerateKey a generateKey :: (GenerateKeyInternal s a, KeyConstraint s a) => Gen (Maybe (Key a)) instance Data.Traversable.Traversable Graphula.Dependencies.Only instance Data.Foldable.Foldable Graphula.Dependencies.Only instance GHC.Base.Functor Graphula.Dependencies.Only instance GHC.Generics.Generic (Graphula.Dependencies.Only a) instance GHC.Classes.Ord a => GHC.Classes.Ord (Graphula.Dependencies.Only a) instance GHC.Show.Show a => GHC.Show.Show (Graphula.Dependencies.Only a) instance GHC.Classes.Eq a => GHC.Classes.Eq (Graphula.Dependencies.Only a) instance (TypeError ...) => Graphula.Dependencies.GenerateKeyInternal 'Graphula.Dependencies.SourceExternal a instance (Graphula.Dependencies.GenerateKeyInternal (Graphula.Dependencies.KeySource a) a, Graphula.Dependencies.KeyConstraint (Graphula.Dependencies.KeySource a) a) => Graphula.Dependencies.GenerateKey a instance Graphula.Dependencies.GenerateKeyInternal 'Graphula.Dependencies.SourceDefault a instance Graphula.Dependencies.GenerateKeyInternal 'Graphula.Dependencies.SourceArbitrary a module Graphula.Node -- | Generate a node with a default (Database-provided) key -- --
-- a <- node @A () mempty --node :: forall a m. (MonadGraphula m, Logging m a, Arbitrary a, HasDependencies a, GenerateKey a, PersistEntityBackend a ~ SqlBackend, PersistEntity a, Typeable a, GraphulaSafeToInsert a) => Dependencies a -> NodeOptions a -> m (Entity a) -- | Generate a node with an explictly-given key -- --
-- let someKey = UUID.fromString "..." -- a <- nodeKeyed @A someKey () mempty --nodeKeyed :: forall a m. (MonadGraphula m, Logging m a, Arbitrary a, HasDependencies a, PersistEntityBackend a ~ SqlBackend, PersistEntity a, Typeable a, GraphulaSafeToInsert a) => Key a -> Dependencies a -> NodeOptions a -> m (Entity a) -- | Options for generating an individual node -- -- NodeOptions can be created and combined with the Monoidal -- operations (<>) and mempty. -- --
-- a1 <- node @A () mempty
-- a2 <- node @A () $ edit $ \a -> a { someField = True }
-- a3 <- node @A () $ ensure $ (== True) . someField
--
data NodeOptions a
-- | Modify the node after it's been generated
--
--
-- a <- node @A () $ edit $ \a -> a { someField = True }
--
edit :: (a -> a) -> NodeOptions a
-- | Require a node to satisfy the specified predicate
--
-- -- a <- node @A () $ ensure $ (== True) . someField ---- -- N.B. ensuring a condition that is infrequently met can be -- innefficient. ensure :: (a -> Bool) -> NodeOptions a data GenerationFailure -- | Could not satisfy constraints defined using ensure GenerationFailureMaxAttemptsToConstrain :: TypeRep -> GenerationFailure -- | Could not satisfy database constraints on insert GenerationFailureMaxAttemptsToInsert :: TypeRep -> GenerationFailure instance GHC.Generics.Generic (Graphula.Node.Kendo m a) instance GHC.Generics.Generic (Graphula.Node.NodeOptions a) instance GHC.Classes.Eq Graphula.Node.GenerationFailure instance GHC.Show.Show Graphula.Node.GenerationFailure instance GHC.Exception.Type.Exception Graphula.Node.GenerationFailure instance GHC.Base.Semigroup (Graphula.Node.NodeOptions a) instance GHC.Base.Monoid (Graphula.Node.NodeOptions a) instance GHC.Base.Monad m => GHC.Base.Semigroup (Graphula.Node.Kendo m a) instance GHC.Base.Monad m => GHC.Base.Monoid (Graphula.Node.Kendo m a) -- | Graphula is a compact interface for generating data and linking its -- dependencies. You can use this interface to generate fixtures for -- automated testing. -- --
-- {- config/models
--
-- School
-- name Text
-- deriving Generic
--
-- Teacher
-- schoolId SchoolId
-- name Text
-- deriving Generic
--
-- Course
-- schoolId SchoolId
-- teacherId TeacherId
-- name Text
-- deriving Generic
--
-- -}
--
-- instance Arbitrary School where
-- -- ...
--
-- instance Arbitrary Teacher where
-- -- ...
--
-- instance Arbitrary Course where
-- -- ...
--
-- instance HasDependencies School
--
-- instance HasDependencies Teacher where
-- type Dependencies Teacher = Only SchoolId
--
-- instance HasDependencies Course where
-- type Dependencies Course = (SchoolId, CourseId)
--
-- runGraphulaT runDB $ do
-- school <- node @School () mempty
--
-- teacher <- node @Teacher (onlyKey school)
-- $ edit
-- $ t -> t { teacherName = "Alice" }
--
-- course <- node @Course (keys (school, teacher))
-- $ ensure
-- $ not . courseIsArchived
--
module Graphula
class HasDependencies a where {
-- | A data type declaring the model's dependencies
--
-- Models with no dependencies can declare an empty instance,
--
--
-- instance HasDependencies School
--
--
-- Models with one dependency must use the Only 1-tuple
-- constructor,
--
--
-- instance HasDependencies Teacher where
-- type Dependencies Teacher = Only SchoolId
--
--
-- Models with multiple dependencies use tuple syntax,
--
--
-- instance HasDependencies Course where
-- type Dependencies Course = (SchoolId, TeacherId)
--
type Dependencies a;
-- | Specify the method for resolving a node's key
--
-- This can be
--
--
-- 'SourceDefault -- automatically generate keys from the database
-- 'SourceArbitrary -- automatically generate keys using Arbitrary
-- 'SourceExternal -- explicitly pass a key using nodeKeyed
--
--
-- Most types will use SourceDefault or SourceArbitrary.
-- Only use SourceExternal if the key for a value is always
-- defined externally.
type KeySource a :: KeySourceType;
type Dependencies _a = ();
type KeySource _a = 'SourceDefault;
}
-- | Assign values from the Dependencies collection to a value
--
-- This must be an idempotent operation. Law:
--
-- -- (\x d -> x `dependsOn` d `dependsOn` d) = dependsOn ---- -- The default, Generic-based implementation will assign values by -- the order of the fields in the model's type. dependsOn :: HasDependencies a => a -> Dependencies a -> a -- | Assign values from the Dependencies collection to a value -- -- This must be an idempotent operation. Law: -- --
-- (\x d -> x `dependsOn` d `dependsOn` d) = dependsOn ---- -- The default, Generic-based implementation will assign values by -- the order of the fields in the model's type. dependsOn :: (HasDependencies a, HasEot a, HasEot (Dependencies a), GHasDependencies (Proxy a) (Proxy (Dependencies a)) (Eot a) (Eot (Dependencies a))) => a -> Dependencies a -> a -- | For entities that only have singular Dependencies newtype Only a Only :: a -> Only a [fromOnly] :: Only a -> a only :: a -> Only a -- | Generate a node with a default (Database-provided) key -- --
-- a <- node @A () mempty --node :: forall a m. (MonadGraphula m, Logging m a, Arbitrary a, HasDependencies a, GenerateKey a, PersistEntityBackend a ~ SqlBackend, PersistEntity a, Typeable a, GraphulaSafeToInsert a) => Dependencies a -> NodeOptions a -> m (Entity a) -- | Modify the node after it's been generated -- --
-- a <- node @A () $ edit $ \a -> a { someField = True }
--
edit :: (a -> a) -> NodeOptions a
-- | Require a node to satisfy the specified predicate
--
-- -- a <- node @A () $ ensure $ (== True) . someField ---- -- N.B. ensuring a condition that is infrequently met can be -- innefficient. ensure :: (a -> Bool) -> NodeOptions a data GraphulaT n m a runGraphulaT :: MonadUnliftIO m => Maybe Int -> (forall b. ReaderT SqlBackend n b -> m b) -> GraphulaT n m a -> m a data GenerationFailure -- | Could not satisfy constraints defined using ensure GenerationFailureMaxAttemptsToConstrain :: TypeRep -> GenerationFailure -- | Could not satisfy database constraints on insert GenerationFailureMaxAttemptsToInsert :: TypeRep -> GenerationFailure data KeySourceType -- | Generate keys using the database's DEFAULT strategy SourceDefault :: KeySourceType -- | Generate keys using the Arbitrary instance for the Key SourceArbitrary :: KeySourceType -- | Always explicitly pass an external key -- -- See nodeKeyed. SourceExternal :: KeySourceType -- | Generate a node with an explictly-given key -- --
-- let someKey = UUID.fromString "..." -- a <- nodeKeyed @A someKey () mempty --nodeKeyed :: forall a m. (MonadGraphula m, Logging m a, Arbitrary a, HasDependencies a, PersistEntityBackend a ~ SqlBackend, PersistEntity a, Typeable a, GraphulaSafeToInsert a) => Key a -> Dependencies a -> NodeOptions a -> m (Entity a) data GraphulaLoggedT m a -- | Run the graph while logging to a temporary file runGraphulaLoggedT :: MonadUnliftIO m => GraphulaLoggedT m a -> m a -- | runGraphulaLoggedT, but to the specified file runGraphulaLoggedWithFileT :: MonadUnliftIO m => FilePath -> GraphulaLoggedT m a -> m a data GraphulaIdempotentT m a runGraphulaIdempotentT :: MonadUnliftIO m => GraphulaIdempotentT m a -> m a -- | A constraint over lists of nodes for MonadGraphula, and -- GraphulaNode. -- -- Helpful for defining utility functions over many nodes. -- --
-- mkABC :: (GraphulaContext m '[A, B, C]) => m (Node m C)
-- mkABC = do
-- a <- node A () mempty
-- b <- node B (only a) mempty
-- node @C (a, b) $ edit $ n ->
-- n { cc = "spanish" }
--
type family GraphulaContext (m :: Type -> Type) (ts :: [Type]) :: Constraint
type GraphulaNode m a = (HasDependencies a, Logging m a, PersistEntityBackend a ~ SqlBackend, PersistEntity a, Typeable a, Arbitrary a)
type MonadGraphula m = (Monad m, MonadIO m, MonadGraphulaBackend m, MonadGraphulaFrontend m)
class MonadGraphulaBackend m where {
type Logging m :: Type -> Constraint;
}
askGen :: MonadGraphulaBackend m => m (IORef QCGen)
logNode :: (MonadGraphulaBackend m, Logging m a) => a -> m ()
class MonadGraphulaFrontend m
insert :: (MonadGraphulaFrontend m, PersistEntityBackend a ~ SqlBackend, PersistEntity a, Monad m, GraphulaSafeToInsert a) => Maybe (Key a) -> a -> m (Maybe (Entity a))
remove :: (MonadGraphulaFrontend m, PersistEntityBackend a ~ SqlBackend, PersistEntity a, Monad m) => Key a -> m ()
-- | Options for generating an individual node
--
-- NodeOptions can be created and combined with the Monoidal
-- operations (<>) and mempty.
--
--
-- a1 <- node @A () mempty
-- a2 <- node @A () $ edit $ \a -> a { someField = True }
-- a3 <- node @A () $ ensure $ (== True) . someField
--
data NodeOptions a
-- | Abstract constraint that some a can generate a key
--
-- This is part of ensuring better error messages.
class (GenerateKeyInternal (KeySource a) a, KeyConstraint (KeySource a) a) => GenerateKey a
class NoConstraint a
instance GHC.Base.Monad m => Control.Monad.Reader.Class.MonadReader (Graphula.Args Database.Persist.SqlBackend.Internal.SqlBackend n m) (Graphula.GraphulaT n m)
instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Graphula.GraphulaT n m)
instance GHC.Base.Monad m => GHC.Base.Monad (Graphula.GraphulaT n m)
instance GHC.Base.Applicative m => GHC.Base.Applicative (Graphula.GraphulaT n m)
instance GHC.Base.Functor m => GHC.Base.Functor (Graphula.GraphulaT n m)
instance Control.Monad.Trans.Class.MonadTrans (Graphula.GraphulaT n)
instance Control.Monad.IO.Unlift.MonadUnliftIO m => Control.Monad.IO.Unlift.MonadUnliftIO (Graphula.GraphulaT n m)
instance Control.Monad.IO.Class.MonadIO m => Graphula.Class.MonadGraphulaBackend (Graphula.GraphulaT n m)
instance (Control.Monad.IO.Class.MonadIO m, Control.Monad.IO.Class.MonadIO n) => Graphula.Class.MonadGraphulaFrontend (Graphula.GraphulaT n m)
-- | Convenience functions for working with Key dependencies
module Graphula.Key
-- | Equivalent to Only . entityKey
onlyKey :: Entity a -> Only (Key a)
keys :: EntityKeys a => a -> Keys a
-- | Type-class for turning a tuple of Entity into a tuple of
-- Key
--
-- For example, given:
--
-- -- instance HasDependencies Course where -- type Dependencies Course = (SchoolId, TeacherId) ---- -- You would have to do, -- --
-- course <- node @Course (entityKey school, entityKey teacher) mempty ---- -- This type-class allows you to do: -- --
-- course <- node @Course (keys (school, teacher)) mempty ---- -- The type class instances currently scale up 4-tuple -- Dependencies. type Keys a instance (TypeError ...) => Graphula.Key.EntityKeys (Database.Persist.Class.PersistEntity.Entity a) instance Graphula.Key.EntityKeys (Graphula.Dependencies.Only (Database.Persist.Class.PersistEntity.Entity a)) instance Graphula.Key.EntityKeys (Database.Persist.Class.PersistEntity.Entity a, Database.Persist.Class.PersistEntity.Entity b) instance Graphula.Key.EntityKeys (Database.Persist.Class.PersistEntity.Entity a, Database.Persist.Class.PersistEntity.Entity b, Database.Persist.Class.PersistEntity.Entity c) instance Graphula.Key.EntityKeys (Database.Persist.Class.PersistEntity.Entity a, Database.Persist.Class.PersistEntity.Entity b, Database.Persist.Class.PersistEntity.Entity c, Database.Persist.Class.PersistEntity.Entity d)