Skip to content

Commit d655792

Browse files
committed
Store pid of the backend when connecting to Postgres
1 parent 8fb828c commit d655792

File tree

8 files changed

+92
-31
lines changed

8 files changed

+92
-31
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# hpqtypes-1.12.0.0 (????-??-??)
22
* Drop support for GHC 8.8.
33
* Attach `CallStack` to `DBException`.
4+
* Store ID of the server process attached to the current session.
45

56
# hpqtypes-1.11.1.2 (2023-11-08)
67
* Support multihost setups and the `connect_timeout` parameter in the connection

src/Database/PostgreSQL/PQTypes/Class.hs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ class (Applicative m, Monad m) => MonadDB m where
3232
-- 'getLastQuery'.
3333
withFrozenLastQuery :: m a -> m a
3434

35+
-- | Get ID of the server process attached to the current session.
36+
getBackendPid :: m Int
37+
3538
-- | Get current connection statistics.
3639
getConnectionStats :: HasCallStack => m ConnectionStats
3740

@@ -89,6 +92,7 @@ instance
8992
runPreparedQuery name = withFrozenCallStack $ lift . runPreparedQuery name
9093
getLastQuery = lift getLastQuery
9194
withFrozenLastQuery m = controlT $ \run -> withFrozenLastQuery (run m)
95+
getBackendPid = lift getBackendPid
9296
getConnectionStats = withFrozenCallStack $ lift getConnectionStats
9397
getQueryResult = lift getQueryResult
9498
clearQueryResult = lift clearQueryResult

src/Database/PostgreSQL/PQTypes/Internal/Connection.hs

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
{-# LANGUAGE TypeApplications #-}
2+
13
module Database.PostgreSQL.PQTypes.Internal.Connection
24
( -- * Connection
35
Connection (..)
6+
, getBackendPidIO
47
, ConnectionData (..)
58
, withConnectionData
69
, ConnectionStats (..)
@@ -26,10 +29,11 @@ import Control.Exception qualified as E
2629
import Control.Monad
2730
import Control.Monad.Base
2831
import Control.Monad.Catch
29-
import Data.Bifunctor
3032
import Data.ByteString.Char8 qualified as BS
3133
import Data.Foldable qualified as F
34+
import Data.Functor.Identity
3235
import Data.IORef
36+
import Data.Int
3337
import Data.Kind
3438
import Data.Pool
3539
import Data.Set qualified as S
@@ -48,6 +52,7 @@ import Database.PostgreSQL.PQTypes.Internal.Composite
4852
import Database.PostgreSQL.PQTypes.Internal.Error
4953
import Database.PostgreSQL.PQTypes.Internal.Error.Code
5054
import Database.PostgreSQL.PQTypes.Internal.Exception
55+
import Database.PostgreSQL.PQTypes.Internal.QueryResult
5156
import Database.PostgreSQL.PQTypes.Internal.Utils
5257
import Database.PostgreSQL.PQTypes.SQL.Class
5358
import Database.PostgreSQL.PQTypes.SQL.Raw
@@ -114,6 +119,8 @@ initialStats =
114119
data ConnectionData = ConnectionData
115120
{ cdPtr :: !(Ptr PGconn)
116121
-- ^ Pointer to connection object.
122+
, cdBackendPid :: !Int
123+
-- ^ Process ID of the server process attached to the current session.
117124
, cdStats :: !ConnectionStats
118125
-- ^ Statistics associated with the connection.
119126
, cdPreparedQueries :: !(IORef (S.Set T.Text))
@@ -125,6 +132,11 @@ newtype Connection = Connection
125132
{ unConnection :: MVar (Maybe ConnectionData)
126133
}
127134

135+
getBackendPidIO :: Connection -> IO Int
136+
getBackendPidIO conn = do
137+
withConnectionData conn "getBackendPidIO" $ \cd -> do
138+
pure (cd, cdBackendPid cd)
139+
128140
withConnectionData
129141
:: Connection
130142
-> String
@@ -133,7 +145,9 @@ withConnectionData
133145
withConnectionData (Connection mvc) fname f =
134146
modifyMVar mvc $ \mc -> case mc of
135147
Nothing -> hpqTypesError $ fname ++ ": no connection"
136-
Just cd -> first Just <$> f cd
148+
Just cd -> do
149+
(cd', r) <- f cd
150+
cd' `seq` pure (Just cd', r)
137151

138152
-- | Database connection supplier.
139153
newtype ConnectionSourceM m = ConnectionSourceM
@@ -215,10 +229,21 @@ connect ConnectionSettings {..} = mask $ \unmask -> do
215229
Just
216230
ConnectionData
217231
{ cdPtr = connPtr
232+
, cdBackendPid = 0
218233
, cdStats = initialStats
219234
, cdPreparedQueries = preparedQueries
220235
}
221236
F.forM_ csRole $ \role -> runQueryIO conn $ "SET ROLE " <> role
237+
238+
let selectPid = "SELECT pg_backend_pid()" :: RawSQL ()
239+
(_, res) <- runQueryIO conn selectPid
240+
case F.toList $ mkQueryResult @(Identity Int32) selectPid 0 res of
241+
[pid] -> withConnectionData conn fname $ \cd -> do
242+
pure (cd {cdBackendPid = fromIntegral pid}, ())
243+
pids -> do
244+
let err = HPQTypesError $ "unexpected backend pid: " ++ show pids
245+
rethrowWithContext selectPid 0 $ toException err
246+
222247
pure conn
223248
where
224249
fname = "connect"
@@ -317,6 +342,7 @@ runPreparedQueryIO conn (QueryName queryName) sql = do
317342
E.throwIO
318343
DBException
319344
{ dbeQueryContext = sql
345+
, dbeBackendPid = cdBackendPid
320346
, dbeError = HPQTypesError "runPreparedQueryIO: unnamed prepared query is not supported"
321347
, dbeCallStack = callStack
322348
}
@@ -329,7 +355,7 @@ runPreparedQueryIO conn (QueryName queryName) sql = do
329355
-- succeeds, we need to reflect that fact in cdPreparedQueries since
330356
-- you can't prepare a query with the same name more than once.
331357
res <- c_PQparamPrepare cdPtr nullPtr param cname query
332-
void . withForeignPtr res $ verifyResult sql cdPtr
358+
void . withForeignPtr res $ verifyResult sql cdBackendPid cdPtr
333359
modifyIORef' cdPreparedQueries $ S.insert queryName
334360
(,)
335361
<$> (fromIntegral <$> c_PQparamCount param)
@@ -353,7 +379,7 @@ runQueryImpl fname conn sql execSql = do
353379
-- runtime system is used) and react appropriately.
354380
queryRunner <- async . restore $ do
355381
(paramCount, res) <- execSql cd
356-
affected <- withForeignPtr res $ verifyResult sql cdPtr
382+
affected <- withForeignPtr res $ verifyResult sql cdBackendPid cdPtr
357383
stats' <- case affected of
358384
Left _ ->
359385
return
@@ -370,8 +396,7 @@ runQueryImpl fname conn sql execSql = do
370396
, statsValues = statsValues cdStats + (rows * columns)
371397
, statsParams = statsParams cdStats + paramCount
372398
}
373-
-- Force evaluation of modified stats to squash a space leak.
374-
stats' `seq` return (cd {cdStats = stats'}, (either id id affected, res))
399+
return (cd {cdStats = stats'}, (either id id affected, res))
375400
-- If we receive an exception while waiting for the execution to complete,
376401
-- we need to send a request to PostgreSQL for query cancellation and wait
377402
-- for the query runner thread to terminate. It is paramount we make the
@@ -399,10 +424,11 @@ runQueryImpl fname conn sql execSql = do
399424
verifyResult
400425
:: (HasCallStack, IsSQL sql)
401426
=> sql
427+
-> Int
402428
-> Ptr PGconn
403429
-> Ptr PGresult
404430
-> IO (Either Int Int)
405-
verifyResult sql conn res = do
431+
verifyResult sql pid conn res = do
406432
-- works even if res is NULL
407433
rst <- c_PQresultStatus res
408434
case rst of
@@ -421,7 +447,7 @@ verifyResult sql conn res = do
421447
_ | otherwise -> return . Left $ 0
422448
where
423449
throwSQLError =
424-
rethrowWithContext sql
450+
rethrowWithContext sql pid
425451
=<< if res == nullPtr
426452
then
427453
return . E.toException . QueryError
@@ -451,6 +477,7 @@ verifyResult sql conn res = do
451477
E.throwIO
452478
DBException
453479
{ dbeQueryContext = sql
480+
, dbeBackendPid = pid
454481
, dbeError = HPQTypesError ("verifyResult: string returned by PQcmdTuples is not a valid number: " ++ show sn)
455482
, dbeCallStack = callStack
456483
}

src/Database/PostgreSQL/PQTypes/Internal/Exception.hs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import Database.PostgreSQL.PQTypes.SQL.Class
1414
data DBException = forall e sql. (E.Exception e, Show sql) => DBException
1515
{ dbeQueryContext :: !sql
1616
-- ^ Last SQL query that was executed.
17+
, dbeBackendPid :: !Int
18+
-- ^ Process ID of the server process attached to the current session.
1719
, dbeError :: !e
1820
-- ^ Specific error.
1921
, dbeCallStack :: CallStack
@@ -24,11 +26,12 @@ deriving instance Show DBException
2426
instance E.Exception DBException
2527

2628
-- | Rethrow supplied exception enriched with given SQL.
27-
rethrowWithContext :: (HasCallStack, IsSQL sql) => sql -> E.SomeException -> IO a
28-
rethrowWithContext sql (E.SomeException e) =
29+
rethrowWithContext :: (HasCallStack, IsSQL sql) => sql -> Int -> E.SomeException -> IO a
30+
rethrowWithContext sql pid (E.SomeException e) =
2931
E.throwIO
3032
DBException
3133
{ dbeQueryContext = sql
34+
, dbeBackendPid = pid
3235
, dbeError = e
3336
, dbeCallStack = callStack
3437
}

src/Database/PostgreSQL/PQTypes/Internal/Monad.hs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import Control.Monad.State.Strict
1717
import Control.Monad.Trans.Control
1818
import Control.Monad.Trans.State.Strict qualified as S
1919
import Control.Monad.Writer.Class
20-
import Data.Bifunctor
2120
import GHC.Stack
2221

2322
import Database.PostgreSQL.PQTypes.Class
@@ -77,9 +76,9 @@ mapDBT f g m = DBT . StateT $ g . runStateT (unDBT m) . f
7776

7877
instance (m ~ n, MonadBase IO m, MonadMask m) => MonadDB (DBT_ m n) where
7978
runQuery sql = withFrozenCallStack $ DBT . StateT $ \st -> liftBase $ do
80-
second (updateStateWith st sql) <$> runQueryIO (dbConnection st) sql
79+
updateStateWith st sql =<< runQueryIO (dbConnection st) sql
8180
runPreparedQuery name sql = withFrozenCallStack $ DBT . StateT $ \st -> liftBase $ do
82-
second (updateStateWith st sql) <$> runPreparedQueryIO (dbConnection st) name sql
81+
updateStateWith st sql =<< runPreparedQueryIO (dbConnection st) name sql
8382

8483
getLastQuery = DBT . gets $ dbLastQuery
8584

@@ -88,6 +87,9 @@ instance (m ~ n, MonadBase IO m, MonadMask m) => MonadDB (DBT_ m n) where
8887
(x, st'') <- runStateT (unDBT callback) st'
8988
pure (x, st'' {dbRecordLastQuery = dbRecordLastQuery st})
9089

90+
getBackendPid = DBT . StateT $ \st -> do
91+
(,st) <$> liftBase (getBackendPidIO $ dbConnection st)
92+
9193
getConnectionStats = withFrozenCallStack $ do
9294
mconn <- DBT $ liftBase . readMVar =<< gets (unConnection . dbConnection)
9395
case mconn of
@@ -100,9 +102,8 @@ instance (m ~ n, MonadBase IO m, MonadMask m) => MonadDB (DBT_ m n) where
100102
getTransactionSettings = DBT . gets $ dbTransactionSettings
101103
setTransactionSettings ts = DBT . modify $ \st -> st {dbTransactionSettings = ts}
102104

103-
getNotification time = DBT . StateT $ \st ->
104-
(,st)
105-
<$> liftBase (getNotificationIO st time)
105+
getNotification time = DBT . StateT $ \st -> do
106+
(,st) <$> liftBase (getNotificationIO st time)
106107

107108
withNewConnection m = DBT . StateT $ \st -> do
108109
let cs = dbConnectionSource st

src/Database/PostgreSQL/PQTypes/Internal/QueryResult.hs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
module Database.PostgreSQL.PQTypes.Internal.QueryResult
44
( QueryResult (..)
5+
, mkQueryResult
56
, ntuples
67
, nfields
78

@@ -35,12 +36,27 @@ import Database.PostgreSQL.PQTypes.SQL.Class
3536
-- extraction appropriately.
3637
data QueryResult t = forall row. FromRow row => QueryResult
3738
{ qrSQL :: !SomeSQL
39+
, qrBackendPid :: !Int
3840
, qrResult :: !(ForeignPtr PGresult)
3941
, qrFromRow :: !(row -> t)
4042
}
4143

44+
mkQueryResult
45+
:: (FromRow t, IsSQL sql)
46+
=> sql
47+
-> Int
48+
-> ForeignPtr PGresult
49+
-> QueryResult t
50+
mkQueryResult sql pid res =
51+
QueryResult
52+
{ qrSQL = SomeSQL sql
53+
, qrBackendPid = pid
54+
, qrResult = res
55+
, qrFromRow = id
56+
}
57+
4258
instance Functor QueryResult where
43-
f `fmap` QueryResult ctx fres g = QueryResult ctx fres (f . g)
59+
f `fmap` QueryResult ctx pid fres g = QueryResult ctx pid fres (f . g)
4460

4561
instance Foldable QueryResult where
4662
foldr f acc = runIdentity . foldrImpl False (coerce f) acc
@@ -77,7 +93,7 @@ foldImpl
7793
-> acc
7894
-> QueryResult t
7995
-> m acc
80-
foldImpl initCtr termCtr advCtr strict f iacc (QueryResult (SomeSQL ctx) fres g) =
96+
foldImpl initCtr termCtr advCtr strict f iacc (QueryResult (SomeSQL ctx) pid fres g) =
8197
unsafePerformIO $ withForeignPtr fres $ \res -> do
8298
-- This bit is referentially transparent iff appropriate
8399
-- FrowRow and FromSQL instances are (the ones provided
@@ -87,6 +103,7 @@ foldImpl initCtr termCtr advCtr strict f iacc (QueryResult (SomeSQL ctx) fres g)
87103
E.throwIO
88104
DBException
89105
{ dbeQueryContext = ctx
106+
, dbeBackendPid = pid
90107
, dbeError =
91108
RowLengthMismatch
92109
{ lengthExpected = pqVariablesP rowp
@@ -101,7 +118,7 @@ foldImpl initCtr termCtr advCtr strict f iacc (QueryResult (SomeSQL ctx) fres g)
101118
then return acc
102119
else do
103120
-- mask asynchronous exceptions so they won't be wrapped in DBException
104-
obj <- E.mask_ (g <$> fromRow res err 0 i `E.catch` rethrowWithContext ctx)
121+
obj <- E.mask_ (g <$> fromRow res err 0 i `E.catch` rethrowWithContext ctx pid)
105122
worker `apply` (f obj =<< acc) $ advCtr i
106123
worker (pure iacc) =<< initCtr res
107124
where

src/Database/PostgreSQL/PQTypes/Internal/State.hs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,21 @@ data DBState m = DBState
2929
-- ^ Current query result.
3030
}
3131

32-
updateStateWith :: IsSQL sql => DBState m -> sql -> ForeignPtr PGresult -> DBState m
33-
updateStateWith st sql res =
34-
st
35-
{ dbLastQuery = if dbRecordLastQuery st then SomeSQL sql else dbLastQuery st
36-
, dbQueryResult =
37-
Just
38-
QueryResult
39-
{ qrSQL = SomeSQL sql
40-
, qrResult = res
41-
, qrFromRow = id
42-
}
43-
}
32+
updateStateWith
33+
:: IsSQL sql
34+
=> DBState m
35+
-> sql
36+
-> (r, ForeignPtr PGresult)
37+
-> IO (r, DBState m)
38+
updateStateWith st sql (r, res) = do
39+
pid <- getBackendPidIO $ dbConnection st
40+
pure
41+
( r
42+
, st
43+
{ dbLastQuery =
44+
if dbRecordLastQuery st
45+
then SomeSQL sql
46+
else dbLastQuery st
47+
, dbQueryResult = Just $ mkQueryResult sql pid res
48+
}
49+
)

src/Database/PostgreSQL/PQTypes/Utils.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ throwDB e = case fromException $ toException e of
3838
Just (dbe :: DBException) -> throwM dbe
3939
Nothing -> do
4040
SomeSQL sql <- getLastQuery
41+
pid <- getBackendPid
4142
throwM
4243
DBException
4344
{ dbeQueryContext = sql
45+
, dbeBackendPid = pid
4446
, dbeError = e
4547
, dbeCallStack = callStack
4648
}

0 commit comments

Comments
 (0)