1
- {-# LANGUAGE RecursiveDo #-}
2
1
module Database.PostgreSQL.Driver.Connection where
3
2
4
3
@@ -62,12 +61,13 @@ type NotificationHandler = Notification -> IO ()
62
61
-- All possible at errors
63
62
data Error
64
63
= PostgresError ErrorDesc
65
- | ImpossibleError
64
+ | AuthError AuthError
65
+ | ImpossibleError B. ByteString
66
66
deriving (Show )
67
67
68
68
data AuthError
69
- = AuthPostgresError ErrorDesc
70
- | AuthNotSupported B. ByteString
69
+ = AuthNotSupported B. ByteString
70
+ | AuthInvalidAddress
71
71
deriving (Show )
72
72
73
73
data DataMessage = DataMessage [V. Vector B. ByteString ]
@@ -87,32 +87,39 @@ defaultUnixPathDirectory = "/var/run/postgresql"
87
87
unixPathFilename :: B. ByteString
88
88
unixPathFilename = " .s.PGSQL."
89
89
90
- createRawConnection :: ConnectionSettings -> IO RawConnection
90
+
91
+ createRawConnection :: ConnectionSettings -> IO (Either Error RawConnection )
91
92
createRawConnection settings
92
93
| host == " " = unixConnection defaultUnixPathDirectory
93
94
| " /" `B.isPrefixOf` host = unixConnection host
94
95
| otherwise = tcpConnection
95
96
where
96
- host = settingsHost settings
97
97
unixConnection dirPath = do
98
- -- 47 - `/`
99
- let dir = B. reverse . B. dropWhile (== 47 ) $ B. reverse dirPath
100
- path = dir <> " /" <> unixPathFilename
101
- <> BS. pack (show $ settingsPort settings)
102
- -- TODO check for Nothing
103
- address = fromJust $ socketAddressUnixPath path
104
- s <- socket :: IO (Socket Unix Stream Unix )
105
- Socket. connect s address
106
- pure $ constructRawConnection s
98
+ let mAddress = socketAddressUnixPath $ makeUnixPath dirPath
99
+ case mAddress of
100
+ Nothing -> throwAuthErrorInIO AuthInvalidAddress
101
+ Just address -> do
102
+ s <- socket :: IO (Socket Unix Stream Unix )
103
+ Socket. connect s address
104
+ pure . Right $ constructRawConnection s
105
+
107
106
tcpConnection = do
108
107
addressInfo <- getAddressInfo (Just host) Nothing aiV4Mapped
109
108
:: IO [AddressInfo Inet Stream TCP ]
110
- let address = (socketAddress $ head addressInfo)
111
- { inetPort = fromIntegral $ settingsPort settings }
112
- -- TODO check for empty
113
- s <- socket :: IO (Socket Inet Stream TCP )
114
- Socket. connect s address
115
- pure $ constructRawConnection s
109
+ case socketAddress <$> addressInfo of
110
+ [] -> throwAuthErrorInIO AuthInvalidAddress
111
+ (address: _) -> do
112
+ s <- socket :: IO (Socket Inet Stream TCP )
113
+ Socket. connect s address
114
+ { inetPort = fromIntegral $ settingsPort settings }
115
+ pure . Right $ constructRawConnection s
116
+
117
+ host = settingsHost settings
118
+ makeUnixPath dirPath =
119
+ -- 47 - `/`, removing slash on the end of the path
120
+ let dir = B. reverse . B. dropWhile (== 47 ) $ B. reverse dirPath
121
+ in dir <> " /" <> unixPathFilename
122
+ <> BS. pack (show $ settingsPort settings)
116
123
117
124
constructRawConnection :: Socket f t p -> RawConnection
118
125
constructRawConnection s = RawConnection
@@ -123,40 +130,47 @@ constructRawConnection s = RawConnection
123
130
}
124
131
125
132
-- | Public
126
- connect :: ConnectionSettings -> IO Connection
133
+ connect :: ConnectionSettings -> IO ( Either Error Connection )
127
134
connect settings = connectWith settings defaultFilter
128
135
129
- connectWith :: ConnectionSettings -> ServerMessageFilter -> IO Connection
130
- connectWith settings msgFilter = do
131
- rawConn <- createRawConnection settings
132
- when (settingsTls settings == RequiredTls ) $ handshakeTls rawConn
133
- authResult <- authorize rawConn settings
134
- -- TODO should close connection on error
135
- connParams <- either (\ e -> print e >> error " invalid connection" )
136
- pure authResult
137
-
136
+ connectWith
137
+ :: ConnectionSettings
138
+ -> ServerMessageFilter
139
+ -> IO (Either Error Connection )
140
+ connectWith settings msgFilter =
141
+ createRawConnection settings >>=
142
+ either throwErrorInIO (\ rawConn ->
143
+ authorize rawConn settings >>=
144
+ either throwErrorInIO (\ params ->
145
+ Right <$> buildConnection rawConn params msgFilter))
146
+
147
+ buildConnection
148
+ :: RawConnection
149
+ -> ConnectionParameters
150
+ -> ServerMessageFilter
151
+ -> IO Connection
152
+ buildConnection rawConn connParams msgFilter = do
138
153
(inDataChan, outDataChan) <- newChan
139
154
(inAllChan, outAllChan) <- newChan
140
155
storage <- newStatementStorage
141
156
modeRef <- newIORef defaultConnectionMode
142
157
143
158
tid <- forkIO $
144
159
receiverThread msgFilter rawConn inDataChan inAllChan modeRef
145
- rec conn <- pure Connection
146
- { connRawConnection = rawConn
147
- , connReceiverThread = tid
148
- , connOutDataChan = outDataChan
149
- , connOutAllChan = outAllChan
150
- , connStatementStorage = storage
151
- , connParameters = connParams
152
- , connMode = modeRef
153
- }
154
- pure conn
160
+ pure Connection
161
+ { connRawConnection = rawConn
162
+ , connReceiverThread = tid
163
+ , connOutDataChan = outDataChan
164
+ , connOutAllChan = outAllChan
165
+ , connStatementStorage = storage
166
+ , connParameters = connParams
167
+ , connMode = modeRef
168
+ }
155
169
156
170
authorize
157
171
:: RawConnection
158
172
-> ConnectionSettings
159
- -> IO (Either AuthError ConnectionParameters )
173
+ -> IO (Either Error ConnectionParameters )
160
174
authorize rawConn settings = do
161
175
sendStartMessage rawConn $ consStartupMessage settings
162
176
-- 4096 should be enough for the whole response from a server at
@@ -173,15 +187,17 @@ authorize rawConn settings = do
173
187
let pass = " md5" <> md5Hash (md5Hash (settingsPassword settings
174
188
<> settingsUser settings) <> salt)
175
189
in performPasswordAuth $ PasswordMD5 pass
176
- AuthenticationGSS -> pure $ Left $ AuthNotSupported " GSS"
177
- AuthenticationSSPI -> pure $ Left $ AuthNotSupported " SSPI"
178
- AuthenticationGSSContinue _ -> pure $ Left $ AuthNotSupported " GSS"
179
- AuthErrorResponse desc -> pure $ Left $ AuthPostgresError desc
190
+ AuthenticationGSS ->
191
+ throwAuthErrorInIO $ AuthNotSupported " GSS"
192
+ AuthenticationSSPI ->
193
+ throwAuthErrorInIO $ AuthNotSupported " SSPI"
194
+ AuthenticationGSSContinue _ ->
195
+ throwAuthErrorInIO $ AuthNotSupported " GSS"
196
+ AuthErrorResponse desc ->
197
+ throwErrorInIO $ PostgresError desc
180
198
-- TODO handle this case
181
199
f -> error " athorize"
182
200
where
183
- performPasswordAuth
184
- :: PasswordText -> IO (Either AuthError ConnectionParameters )
185
201
performPasswordAuth password = do
186
202
sendMessage rawConn $ PasswordMessage password
187
203
r <- rReceive rawConn 4096
@@ -190,7 +206,7 @@ authorize rawConn settings = do
190
206
AuthenticationOk ->
191
207
pure $ Right $ parseParameters rest
192
208
AuthErrorResponse desc ->
193
- pure $ Left $ AuthPostgresError desc
209
+ throwErrorInIO $ PostgresError desc
194
210
_ -> error " Impossible happened"
195
211
-- TODO handle this case
196
212
f -> error " authorize"
@@ -321,7 +337,6 @@ sendMessage rawConn msg = void $ do
321
337
let smsg = toStrict . toLazyByteString $ encodeClientMessage msg
322
338
rSend rawConn smsg
323
339
324
-
325
340
-- Public
326
341
data Query = Query
327
342
{ qStatement :: B. ByteString
@@ -422,3 +437,9 @@ describeStatement conn stmt = do
422
437
xs -> maybe (error " Impossible happened" ) (Left . PostgresError )
423
438
$ findFirstError xs
424
439
440
+ throwErrorInIO :: Error -> IO (Either Error a )
441
+ throwErrorInIO = pure . Left
442
+
443
+ throwAuthErrorInIO :: AuthError -> IO (Either Error a )
444
+ throwAuthErrorInIO = pure . Left . AuthError
445
+
0 commit comments