{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-} -- | Defines an Postgresql event store. module Eventful.Store.Postgresql ( postgresqlEventStoreWriter , initializePostgresqlEventStore , module Eventful.Store.Class , module Eventful.Store.Sql ) where import Control.Monad.Reader import Data.Monoid ((<>)) import Data.Text (Text) import Database.Persist import Database.Persist.Sql import Eventful.Store.Class import Eventful.Store.Sql -- | An 'EventStore' that uses a PostgreSQL database as a backend. Use -- 'SqlEventStoreConfig' to configure this event store. postgresqlEventStoreWriter :: (MonadIO m, PersistEntity entity, PersistEntityBackend entity ~ SqlBackend) => SqlEventStoreConfig entity serialized -> EventStoreWriter (SqlPersistT m) serialized postgresqlEventStoreWriter config = EventStoreWriter $ transactionalExpectedWriteHelper getLatestVersion storeEvents' where getLatestVersion = sqlMaxEventVersion config maxPostgresVersionSql storeEvents' = sqlStoreEvents config (Just tableLockFunc) maxPostgresVersionSql maxPostgresVersionSql :: DBName -> DBName -> DBName -> Text maxPostgresVersionSql (DBName tableName) (DBName uuidFieldName) (DBName versionFieldName) = "SELECT COALESCE(MAX(" <> versionFieldName <> "), -1) FROM " <> tableName <> " WHERE " <> uuidFieldName <> " = ?" -- | This function runs migrations to create the events table if it isn't -- present. initializePostgresqlEventStore :: (MonadIO m) => ConnectionPool -> m () initializePostgresqlEventStore pool = do -- Run migrations _ <- liftIO $ runSqlPool (runMigrationSilent migrateSqlEvent) pool return () -- | We need to lock the events table or else our global sequence number might -- not be monotonically increasing over time from the point of view of a -- reader. -- -- For example, say transaction A begins to write an event and the -- auto-increment key is 1. Then, transaction B starts to insert an event and -- gets an id of 2. If transaction B is quick and completes, then a listener -- might see the event from B and thinks it has all the events up to a sequence -- number of 2. However, once A finishes and the event with the id of 1 is -- done, then the listener won't know that event exists. tableLockFunc :: Text -> Text tableLockFunc tableName = "LOCK " <> tableName <> " IN EXCLUSIVE MODE"