11module IHP.DataSync.Pool
2- ( hasqlPoolVaultKey
3- , hasqlPoolMiddleware
4- , requestHasqlPool
5- , initHasqlPoolMiddleware
2+ ( requestHasqlPool
63, initHasqlPool
74, initHasqlPoolWithRLS
85) where
96
107import IHP.Prelude
11- import Network.Wai
12- import qualified Data.Vault.Lazy as Vault
13- import System.IO.Unsafe (unsafePerformIO )
148import qualified Hasql.Pool
15- import qualified Hasql.Pool.Config as Hasql.Pool.Config
16- import qualified Hasql.Connection.Settings as HasqlSettings
17- import IHP.FrameworkConfig (findOptionOrNothing , configIO , defaultDatabaseUrl )
18- import IHP.FrameworkConfig.Types (DatabaseUrl (.. ), DBPoolMaxConnections (.. ), CustomMiddleware (.. ), RLSAuthenticatedRole (.. ))
9+ import IHP.FrameworkConfig (findOptionOrNothing , addInitializer )
10+ import IHP.FrameworkConfig.Types (RLSAuthenticatedRole (.. ))
11+ import IHP.ModelSupport.Types (ModelContext (.. ))
1912import qualified Control.Monad.Trans.State.Strict as State
2013import qualified Data.TMap as TMap
21- import qualified Control.Concurrent as Concurrent
2214import qualified IHP.DataSync.Role as Role
2315
24- hasqlPoolVaultKey :: Vault. Key Hasql.Pool. Pool
25- hasqlPoolVaultKey = unsafePerformIO Vault. newKey
26- {-# NOINLINE hasqlPoolVaultKey #-}
27-
28- hasqlPoolMiddleware :: Hasql.Pool. Pool -> Middleware
29- hasqlPoolMiddleware pool app req respond =
30- let req' = req { vault = Vault. insert hasqlPoolVaultKey pool req. vault }
31- in app req' respond
32-
33- requestHasqlPool :: Request -> Hasql.Pool. Pool
34- requestHasqlPool req = case Vault. lookup hasqlPoolVaultKey req. vault of
16+ -- | Returns the hasql pool from the model context.
17+ requestHasqlPool :: (? modelContext :: ModelContext ) => Hasql.Pool. Pool
18+ requestHasqlPool = case ? modelContext. hasqlPool of
3519 Just pool -> pool
36- Nothing -> error " requestHasqlPool: No hasql pool in request vault. Add hasqlPoolMiddleware to your Config.hs."
37-
38- -- | Convenience: creates pool and returns middleware. Pool lives for app lifetime.
39- initHasqlPoolMiddleware :: ByteString -> Int -> IO Middleware
40- initHasqlPoolMiddleware databaseUrl poolSize = do
41- let poolConfig = Hasql.Pool.Config. settings
42- [ Hasql.Pool.Config. size poolSize
43- , Hasql.Pool.Config. staticConnectionSettings
44- (HasqlSettings. connectionString (cs databaseUrl))
45- ]
46- pool <- Hasql.Pool. acquire poolConfig
47- pure (hasqlPoolMiddleware pool)
20+ Nothing -> error " requestHasqlPool: No hasql pool available in ModelContext"
4821
49- -- | Reads 'DatabaseUrl' and 'DBPoolMaxConnections' from the framework config,
50- -- creates a hasql connection pool, and composes 'hasqlPoolMiddleware' into the
51- -- existing 'CustomMiddleware' stack.
52- --
53- -- Use this in your @Config.hs@:
54- --
55- -- > config :: ConfigBuilder
56- -- > config = do
57- -- > initHasqlPool
22+ -- | No-op for backwards compatibility. The hasql pool is now always created
23+ -- as part of the ModelContext. You can remove this call from your Config.hs.
5824initHasqlPool :: State. StateT TMap. TMap IO ()
59- initHasqlPool = do
60- initHasqlPoolImpl
61- pure ()
25+ initHasqlPool = pure ()
6226
63- -- | Like 'initHasqlPool', but also ensures the RLS authenticated role exists
64- -- in postgres. Reads 'RLSAuthenticatedRole' from the config (default: @ihp_authenticated@).
27+ -- | Ensures the RLS authenticated role exists in postgres at startup.
28+ -- Reads 'RLSAuthenticatedRole' from the config (default: @ihp_authenticated@).
6529--
6630-- Use this in your @Config.hs@:
6731--
@@ -70,33 +34,10 @@ initHasqlPool = do
7034-- > initHasqlPoolWithRLS
7135initHasqlPoolWithRLS :: State. StateT TMap. TMap IO ()
7236initHasqlPoolWithRLS = do
73- pool <- initHasqlPoolImpl
7437 rlsRole <- findOptionOrNothing @ RLSAuthenticatedRole >>= \ case
7538 Just (RLSAuthenticatedRole role) -> pure role
7639 Nothing -> pure " ihp_authenticated"
77- configIO $ Role. ensureAuthenticatedRoleExistsWithRole pool rlsRole
78-
79- -- | Shared implementation: creates the pool, installs the middleware, returns the pool.
80- initHasqlPoolImpl :: State. StateT TMap. TMap IO Hasql.Pool. Pool
81- initHasqlPoolImpl = do
82- databaseUrl <- findOptionOrNothing @ DatabaseUrl >>= \ case
83- Just (DatabaseUrl url) -> pure url
84- Nothing -> configIO defaultDatabaseUrl
85- maxConnections <- findOptionOrNothing @ DBPoolMaxConnections >>= \ case
86- Just (DBPoolMaxConnections n) -> pure n
87- Nothing -> configIO $ max 20 <$> Concurrent. getNumCapabilities
88- pool <- configIO $ do
89- let poolConfig = Hasql.Pool.Config. settings
90- [ Hasql.Pool.Config. size maxConnections
91- , Hasql.Pool.Config. staticConnectionSettings
92- (HasqlSettings. connectionString (cs databaseUrl))
93- ]
94- Hasql.Pool. acquire poolConfig
95- existingMiddleware <- findOptionOrNothing @ CustomMiddleware >>= \ case
96- Just (CustomMiddleware mw) -> pure mw
97- Nothing -> pure id
98- State. modify (\ map -> map
99- |> TMap. delete @ CustomMiddleware
100- |> TMap. insert (CustomMiddleware (existingMiddleware . hasqlPoolMiddleware pool))
101- )
102- pure pool
40+ addInitializer do
41+ case ? modelContext. hasqlPool of
42+ Just pool -> Role. ensureAuthenticatedRoleExistsWithRole pool rlsRole
43+ Nothing -> pure ()
0 commit comments