@@ -1,3 +1,5 @@
1+{-# LANGUAGE FlexibleContexts #-}
2+{-# LANGUAGE StandaloneDeriving #-}
1{-# LANGUAGE GADTs #-}
3{-# LANGUAGE GADTs #-}
2{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3{-# LANGUAGE LambdaCase #-}
5{-# LANGUAGE LambdaCase #-}
@@ -24,69 +26,20 @@ import Control.Monad (forever)
24import Control.Monad.Reader
26import Control.Monad.Reader
25import Control.Monad.Trans (lift)
27import Control.Monad.Trans (lift)
26import Data.Aeson (FromJSON, ToJSON)
28import Data.Aeson (FromJSON, ToJSON)
27import Data.Attoparsec.ByteString
30import Data.Attoparsec.ByteString
28import Data.ByteString (ByteString)
31import Data.ByteString (ByteString)
29import Data.Default
32import Data.Default
33+import Data.Functor.Compose
30−import Data.Map.Monoidal (MonoidalMap)
34+import Data.Map.Monoidal (MonoidalMap (..))
31−import Data.MonoidMap (MonoidMap (..))
32import Data.Pool (withResource)
35import Data.Pool (withResource)
33import qualified Database.PostgreSQL.Simple as PG
37import qualified Database.PostgreSQL.Simple as PG
34import qualified Database.PostgreSQL.Simple.Transaction as PG
38import qualified Database.PostgreSQL.Simple.Transaction as PG
35−import Reflex (FunctorMaybe (..))
36import Reflex.Patch (Group)
39import Reflex.Patch (Group)
37import Reflex.Query.Class (QueryResult, SelectedCount (..))
40import Reflex.Query.Class (QueryResult, SelectedCount (..))
38import Snap.Core (Snap)
41import Snap.Core (Snap)
41−-- | A way for a pipeline to retrieve data
42−newtype QueryHandler q m = QueryHandler
43− { runQueryHandler :: q -> m (QueryResult q) }
45−-- | A way to push data into a pipeline
46−newtype Recipient q m = Recipient
47− { tellRecipient :: QueryResult q -> m () }
49−-- | A way of attaching to (and later detaching from) a pipeline
50−newtype Registrar q = Registrar { unRegistrar :: Recipient q IO -> IO (QueryHandler q IO, IO ()) }
52−-- | A way of connecting a source of data to a consumer of data. The consumer
53−-- can pull data from the datasource and the datasource can push data to the
56−-- q is the consumer side
57−-- q' is the datasource side
58−newtype Pipeline m q q' = Pipeline { unPipeline :: QueryHandler q' m -> Recipient q m -> IO (QueryHandler q m, Recipient q' m) }
60−instance Category (Pipeline m) where
61− id = Pipeline $ \qh r -> return (qh, r)
62− Pipeline f . Pipeline g = Pipeline $ \qh r -> do
63− rec (qh'', r') <- g qh' r
64− (qh', r'') <- f qh r'
67−-- | A key used to track particular consumers
68−newtype ClientKey = ClientKey { unClientKey :: Integer }
69− deriving (Eq, Ord, Show)
75− => Pool Postgresql -- ^ The DB to connect to
76− -> (forall a. request a -> write a)
77− -> (ByteString -> q' -> read (QueryResult q'))
78− -> (q' -> read (QueryResult q))
82−servePostgres db handleApi handleNotify handleQuery pipe = do
83− (getNextNotification, finalizeListener) <- startNotificationListener db
84− rec (qh, finalizeFeed) <- feedPipeline (handleNotify <$> getNextNotification) handleQuery r
85− (qh', r) <- unPipeline pipe qh r'
86− (r', handleListen) <- connectPipelineToWebsockets "" (RequestHandler handleApi) qh'
87− return (handleListen, finalizeFeed >> finalizeListener)
90newtype ReadDb a = ReadDb { unReadDb :: ReaderT PG.Connection IO a }
43newtype ReadDb a = ReadDb { unReadDb :: ReaderT PG.Connection IO a }
91 deriving (Functor, Applicative, Monad)
44 deriving (Functor, Applicative, Monad)
@@ -114,43 +67,39 @@ unsafeWriteDb :: (PG.Connection -> IO a) -> WriteDb a
114unsafeWriteDb f = WriteDb $ lift . f =<< ask
67unsafeWriteDb f = WriteDb $ lift . f =<< ask
116withApiHandler
69withApiHandler
117− :: forall query queryResult request a.
70+ :: forall v query q0 request a.
118− ( Monoid (query SelectedCount)
71+ ( View v
119− , QueryResult (query (MonoidMap ClientKey SelectedCount)) ~ queryResult (MonoidMap ClientKey SelectedCount)
72+ , Group (v (Const SelectedCount))
120− , QueryResult (query SelectedCount) ~ queryResult SelectedCount
73+ , query ~ v (Compose (MonoidalMap ClientKey) (Const SelectedCount))
121− , QueryResult (query ()) ~ queryResult ()
74+ , q0 ~ v Proxy
122− , Foldable queryResult
75+ , QueryResult query ~ v (Compose (MonoidalMap ClientKey) Identity)
123− , Functor query
76+ , QueryResult (v (Const SelectedCount)) ~ v Identity
124− , Functor queryResult
77+ , Monoid (v (Compose (MonoidalMap ClientKey) (Const SelectedCount)))
125− , FunctorMaybe queryResult
78+ , Monoid (v Identity)
126− , Monoid (query (MonoidMap ClientKey SelectedCount))
79+ , ToJSON (v Identity)
127− , Monoid (queryResult SelectedCount)
128− , Eq (queryResult SelectedCount)
129− , Eq (query (MonoidMap ClientKey SelectedCount))
130− , Eq (query SelectedCount)
131− , Group (query SelectedCount)
132− , ToJSON (queryResult ())
133− , FromJSON (query ())
80+ , FromJSON (v Proxy)
134 , Request request
81 , Request request
136 => ByteString -- ^ The URI of the DB to connect to
83 => ByteString -- ^ The URI of the DB to connect to
137 -> (forall x. request x -> WriteDb x) -- ^ How to handle an API request
84 -> (forall x. request x -> WriteDb x) -- ^ How to handle an API request
138− -> (forall x. query x -> Transaction -> ReadDb (QueryResult (query x))) -- ^ How to handle a change
85+ -> (v Proxy -> Transaction -> ReadDb (v Identity)) -- ^ How to handle a change
139− -> (forall x. query x -> ReadDb (QueryResult (query x))) -- ^ How to handle a query
86+ -> (v Proxy -> ReadDb (v Identity)) -- ^ How to handle a query
140 -> (Snap () -> IO a)
87 -> (Snap () -> IO a)
142withApiHandler dbUri handleApi handleChange handleQuery go = do
89withApiHandler dbUri handleApi handleChange handleQuery go = do
143 withNonEmptyTransactions dbUri $ \transactions -> do
90 withNonEmptyTransactions dbUri $ \transactions -> do
144 withConnectionPool dbUri $ \db -> do
91 withConnectionPool dbUri $ \db -> do
145− let onChange :: Transaction -> query x -> IO (QueryResult (query x))
92+ let onChange :: Transaction -> v (Compose (MonoidalMap ClientKey) (Const SelectedCount)) -> IO (v (Compose (MonoidalMap ClientKey) Identity))
146− onChange m q = withResource db $ \conn -> readTransaction conn $ handleChange q m
93+ onChange txn q = withResource db $ \conn -> readTransaction conn $ mapDecomposedV (flip handleChange txn) q
147− let pipe :: Pipeline IO (MonoidalMap ClientKey (query SelectedCount)) (query (MonoidMap ClientKey SelectedCount))
94+ let pipe :: Pipeline IO (MonoidalMap ClientKey (v (Const SelectedCount))) (v (Compose (MonoidalMap ClientKey) (Const SelectedCount)))
148− pipe = queryMorphismPipeline $ transposeMonoidMap . monoidMapQueryMorphism
95+ pipe = queryMorphismPipeline transposeView
149− rec (qh, finalizeFeed) <- feedPipeline (onChange <$> atomically (readTChan transactions)) (QueryHandler $ \q -> withResource db $ \conn -> readTransaction conn $ handleQuery q) r
96+ rec (qh, finalizeFeed) <- feedPipeline (onChange <$> atomically (readTChan transactions)) (QueryHandler $ \q -> withResource db $ \conn -> readTransaction conn $ mapDecomposedV handleQuery q) r
150 (qh', r) <- unPipeline pipe qh r'
97 (qh', r) <- unPipeline pipe qh r'
151 (r', handleListen) <- connectPipelineToWebsockets "" (\r'' -> withResource db $ \conn -> writeTransaction conn $ handleApi r'') qh'
98 (r', handleListen) <- connectPipelineToWebsockets "" (\r'' -> withResource db $ \conn -> writeTransaction conn $ handleApi r'') qh'
152 go handleListen
99 go handleListen
101+deriving instance Ord k => Align (MonoidalMap k)
154withNonEmptyTransactions :: ByteString -> (TChan Transaction -> IO a) -> IO a
103withNonEmptyTransactions :: ByteString -> (TChan Transaction -> IO a) -> IO a
155withNonEmptyTransactions dbUri go = do
104withNonEmptyTransactions dbUri go = do
156 let opts = def
105 let opts = def
@@ -167,21 +116,3 @@ withNonEmptyTransactions dbUri go = do
167 atomically $ writeTChan transactions transaction
116 atomically $ writeTChan transactions transaction
168 withAsync processLines $ \a -> link a >> do
117 withAsync processLines $ \a -> link a >> do
169 go transactions
118 go transactions
172−standardChangeChannel :: PG.Identifier
173−standardChangeChannel = "obelisk_api_updates"
175−standardChangeSubscriberTable :: PG.Identifier
176−standardChangeSubscriberTable = "obelisk_api_updates_subscriber"
178−standardChangeSpillTable :: PG.Identifier
179−standardChangeSpillTable = "obelisk_api_updates_spill"
181−-- | Listen on a Postgres notification channel. Uses a connection from the Pool for as long as it is r-- | Listen for Postgresql changes on a particular channel
182−withChangeListener :: FromJSON change => Pool PG.Connection -> (change -> IO ()) -> IO a -> IO a
183−withChangeListener db onChange = withBigNotificationListener db standardChangeChannel standardChangeSpillTable $ \message ->
184− case Aeson.eitherDecode' $ LBS.fromStrict message of
185− Left e -> fail $ "withChangeListener: " <> e
186− Right change -> onChange change