1
1
{-# LANGUAGE CPP #-}
2
- {-|
3
- This module has functions to send commands LISTEN and NOTIFY to the database server.
4
- It also has a function to wait for and handle notifications on a database connection.
5
2
6
- For more information check the [PostgreSQL documentation](https://www.postgresql.org/docs/current/libpq-notify.html).
7
-
8
- -}
3
+ -- |
4
+ -- This module has functions to send commands LISTEN and NOTIFY to the database server.
5
+ -- It also has a function to wait for and handle notifications on a database connection.
6
+ --
7
+ -- For more information check the [PostgreSQL documentation](https://www.postgresql.org/docs/current/libpq-notify.html).
9
8
module Hasql.Notifications
10
- ( notifyPool
11
- , notify
12
- , listen
13
- , unlisten
14
- , waitForNotifications
15
- , PgIdentifier
16
- , toPgIdentifier
17
- , fromPgIdentifier
18
- ) where
19
-
20
- import Hasql.Pool (Pool , UsageError , use )
21
- import Hasql.Session (sql , run , statement )
22
- import qualified Hasql.Session as S
23
- import qualified Hasql.Statement as HST
24
- import Hasql.Connection (Connection , withLibPQConnection )
25
- import qualified Hasql.Decoders as HD
26
- import qualified Hasql.Encoders as HE
27
- import qualified Database.PostgreSQL.LibPQ as PQ
9
+ ( notifyPool ,
10
+ notify ,
11
+ listen ,
12
+ unlisten ,
13
+ waitForNotifications ,
14
+ PgIdentifier ,
15
+ toPgIdentifier ,
16
+ fromPgIdentifier ,
17
+ )
18
+ where
19
+
20
+ import Control.Concurrent (threadDelay , threadWaitRead )
21
+ import Control.Exception (Exception , throw )
22
+ import Control.Monad (forever , unless , void )
23
+ import Data.ByteString.Char8 (ByteString )
24
+ import Data.Functor.Contravariant (contramap )
28
25
import Data.Text (Text )
29
26
import qualified Data.Text as T
30
27
import qualified Data.Text.Encoding as T
31
- import Data.ByteString.Char8 (ByteString )
32
- import Data.Functor.Contravariant (contramap )
33
- import Control.Monad (void , forever )
34
- #if defined(mingw32_HOST_OS)
35
- import Control.Concurrent ( threadDelay )
36
- #else
37
- import Control.Concurrent (threadWaitRead , threadDelay )
38
- #endif
39
- import Control.Exception (Exception , throw )
28
+ import qualified Database.PostgreSQL.LibPQ as PQ
29
+ import Hasql.Connection (Connection , withLibPQConnection )
30
+ import qualified Hasql.Decoders as HD
31
+ import qualified Hasql.Encoders as HE
32
+ import Hasql.Pool (Pool , UsageError , use )
33
+ import Hasql.Session (run , sql , statement )
34
+ import qualified Hasql.Session as S
35
+ import qualified Hasql.Statement as HST
40
36
41
37
-- | A wrapped text that represents a properly escaped and quoted PostgreSQL identifier
42
38
newtype PgIdentifier = PgIdentifier Text deriving (Show )
43
39
44
40
-- | Uncatchable exceptions thrown and never caught.
45
- newtype FatalError = FatalError { fatalErrorMessage :: String }
41
+ newtype FatalError = FatalError { fatalErrorMessage :: String }
46
42
deriving (Show )
47
43
48
44
instance Exception FatalError
@@ -60,101 +56,114 @@ toPgIdentifier x =
60
56
strictlyReplaceQuotes = T. replace " \" " (" \"\" " :: Text )
61
57
62
58
-- | Given a Hasql Pool, a channel and a message sends a notify command to the database
63
- notifyPool :: Pool -- ^ Pool from which the connection will be used to issue a NOTIFY command.
64
- -> Text -- ^ Channel where to send the notification
65
- -> Text -- ^ Payload to be sent with the notification
66
- -> IO (Either UsageError () )
59
+ notifyPool ::
60
+ -- | Pool from which the connection will be used to issue a NOTIFY command.
61
+ Pool ->
62
+ -- | Channel where to send the notification
63
+ Text ->
64
+ -- | Payload to be sent with the notification
65
+ Text ->
66
+ IO (Either UsageError () )
67
67
notifyPool pool channel mesg =
68
- use pool (statement (channel, mesg) callStatement)
69
- where
70
- callStatement = HST. Statement (" SELECT pg_notify" <> " ($1, $2)" ) encoder HD. noResult False
71
- encoder = contramap fst (HE. param $ HE. nonNullable HE. text) <> contramap snd (HE. param $ HE. nonNullable HE. text)
68
+ use pool (statement (channel, mesg) callStatement)
69
+ where
70
+ callStatement = HST. Statement (" SELECT pg_notify" <> " ($1, $2)" ) encoder HD. noResult False
71
+ encoder = contramap fst (HE. param $ HE. nonNullable HE. text) <> contramap snd (HE. param $ HE. nonNullable HE. text)
72
72
73
73
-- | Given a Hasql Connection, a channel and a message sends a notify command to the database
74
- notify :: Connection -- ^ Connection to be used to send the NOTIFY command
75
- -> PgIdentifier -- ^ Channel where to send the notification
76
- -> Text -- ^ Payload to be sent with the notification
77
- -> IO (Either S. QueryError () )
74
+ notify ::
75
+ -- | Connection to be used to send the NOTIFY command
76
+ Connection ->
77
+ -- | Channel where to send the notification
78
+ PgIdentifier ->
79
+ -- | Payload to be sent with the notification
80
+ Text ->
81
+ IO (Either S. QueryError () )
78
82
notify con channel mesg =
79
- run (sql $ T. encodeUtf8 (" NOTIFY " <> fromPgIdentifier channel <> " , '" <> mesg <> " '" )) con
80
-
81
- {-|
82
- Given a Hasql Connection and a channel sends a listen command to the database.
83
- Once the connection sends the LISTEN command the server register its interest in the channel.
84
- Hence it's important to keep track of which connection was used to open the listen command.
85
-
86
- Example of listening and waiting for a notification:
87
-
88
- @
89
- import System.Exit (die)
90
-
91
- import Hasql.Connection
92
- import Hasql.Notifications
93
-
94
- main :: IO ()
95
- main = do
96
- dbOrError <- acquire "postgres://localhost/db_name"
97
- case dbOrError of
98
- Right db -> do
99
- let channelToListen = toPgIdentifier "sample-channel"
100
- listen db channelToListen
101
- waitForNotifications (\channel _ -> print $ "Just got notification on channel " <> channel) db
102
- _ -> die "Could not open database connection"
103
- @
104
- -}
105
- listen :: Connection -- ^ Connection to be used to send the LISTEN command
106
- -> PgIdentifier -- ^ Channel this connection will be registered to listen to
107
- -> IO ()
83
+ run (sql $ T. encodeUtf8 (" NOTIFY " <> fromPgIdentifier channel <> " , '" <> mesg <> " '" )) con
84
+
85
+ -- |
86
+ -- Given a Hasql Connection and a channel sends a listen command to the database.
87
+ -- Once the connection sends the LISTEN command the server register its interest in the channel.
88
+ -- Hence it's important to keep track of which connection was used to open the listen command.
89
+ --
90
+ -- Example of listening and waiting for a notification:
91
+ --
92
+ -- @
93
+ -- import System.Exit (die)
94
+ --
95
+ -- import Hasql.Connection
96
+ -- import Hasql.Notifications
97
+ --
98
+ -- main :: IO ()
99
+ -- main = do
100
+ -- dbOrError <- acquire "postgres://localhost/db_name"
101
+ -- case dbOrError of
102
+ -- Right db -> do
103
+ -- let channelToListen = toPgIdentifier "sample-channel"
104
+ -- listen db channelToListen
105
+ -- waitForNotifications (\channel _ -> print $ "Just got notification on channel " <> channel) db
106
+ -- _ -> die "Could not open database connection"
107
+ -- @
108
+ listen ::
109
+ -- | Connection to be used to send the LISTEN command
110
+ Connection ->
111
+ -- | Channel this connection will be registered to listen to
112
+ PgIdentifier ->
113
+ IO ()
108
114
listen con channel =
109
115
void $ withLibPQConnection con execListen
110
116
where
111
117
execListen pqCon = void $ PQ. exec pqCon $ T. encodeUtf8 $ " LISTEN " <> fromPgIdentifier channel
112
118
113
119
-- | Given a Hasql Connection and a channel sends a unlisten command to the database
114
- unlisten :: Connection -- ^ Connection currently registerd by a previous 'listen' call
115
- -> PgIdentifier -- ^ Channel this connection will be deregistered from
116
- -> IO ()
120
+ unlisten ::
121
+ -- | Connection currently registerd by a previous 'listen' call
122
+ Connection ->
123
+ -- | Channel this connection will be deregistered from
124
+ PgIdentifier ->
125
+ IO ()
117
126
unlisten con channel =
118
127
void $ withLibPQConnection con execListen
119
128
where
120
129
execListen pqCon = void $ PQ. exec pqCon $ T. encodeUtf8 $ " UNLISTEN " <> fromPgIdentifier channel
121
130
122
-
123
- {-|
124
- Given a function that handles notifications and a Hasql connection it will listen
125
- on the database connection and call the handler everytime a message arrives.
126
-
127
- The message handler passed as first argument needs two parameters channel and payload.
128
- See an example of handling notification on a separate thread:
129
-
130
- @
131
- import Control.Concurrent.Async (async )
132
- import Control.Monad (void )
133
- import System.Exit (die)
134
-
135
- import Hasql.Connection
136
- import Hasql.Notifications
137
-
138
- notificationHandler :: ByteString -> ByteString -> IO()
139
- notificationHandler channel payload =
140
- void $ async do
141
- print $ "Handle payload " <> payload <> " in its own thread"
142
-
143
- main :: IO ()
144
- main = do
145
- dbOrError <- acquire "postgres://localhost/db_name"
146
- case dbOrError of
147
- Right db -> do
148
- let channelToListen = toPgIdentifier "sample-channel"
149
- listen db channelToListen
150
- waitForNotifications notificationHandler db
151
- _ -> die "Could not open database connection"
152
- @
153
- -}
154
-
155
- waitForNotifications :: ( ByteString -> ByteString -> IO () ) -- ^ Callback function to handle incoming notifications
156
- -> Connection -- ^ Connection where we will listen to
157
- -> IO ()
131
+ -- |
132
+ -- Given a function that handles notifications and a Hasql connection it will listen
133
+ -- on the database connection and call the handler everytime a message arrives.
134
+ --
135
+ -- The message handler passed as first argument needs two parameters channel and payload.
136
+ -- See an example of handling notification on a separate thread:
137
+ --
138
+ -- @
139
+ -- import Control.Concurrent.Async (async)
140
+ -- import Control.Monad (void )
141
+ -- import System.Exit (die )
142
+ --
143
+ -- import Hasql.Connection
144
+ -- import Hasql.Notifications
145
+ --
146
+ -- notificationHandler :: ByteString -> ByteString -> IO()
147
+ -- notificationHandler channel payload =
148
+ -- void $ async do
149
+ -- print $ "Handle payload " <> payload <> " in its own thread"
150
+ --
151
+ -- main :: IO ()
152
+ -- main = do
153
+ -- dbOrError <- acquire "postgres://localhost/db_name"
154
+ -- case dbOrError of
155
+ -- Right db -> do
156
+ -- let channelToListen = toPgIdentifier "sample-channel"
157
+ -- listen db channelToListen
158
+ -- waitForNotifications notificationHandler db
159
+ -- _ -> die "Could not open database connection"
160
+ -- @
161
+ waitForNotifications ::
162
+ -- | Callback function to handle incoming notifications
163
+ ( ByteString -> ByteString -> IO () ) ->
164
+ -- | Connection where we will listen to
165
+ Connection ->
166
+ IO ()
158
167
waitForNotifications sendNotification con =
159
168
withLibPQConnection con $ void . forever . pqFetch
160
169
where
@@ -164,11 +173,7 @@ waitForNotifications sendNotification con =
164
173
Nothing -> do
165
174
mfd <- PQ. socket pqCon
166
175
case mfd of
167
- Nothing -> void $ threadDelay 1000000
168
- #if defined(mingw32_HOST_OS)
169
- Just _ -> do
170
- void $ threadDelay 1000000
171
- #else
176
+ Nothing -> void $ threadDelay 1000000
172
177
Just fd -> do
173
178
void $ threadWaitRead fd
174
179
@@ -177,6 +182,6 @@ waitForNotifications sendNotification con =
177
182
mError <- PQ. errorMessage pqCon
178
183
panic $ maybe " Error checking for PostgreSQL notifications" show mError
179
184
Just notification ->
180
- sendNotification (PQ. notifyRelname notification) (PQ. notifyExtra notification)
185
+ sendNotification (PQ. notifyRelname notification) (PQ. notifyExtra notification)
181
186
panic :: String -> a
182
187
panic a = throw (FatalError a)
0 commit comments