content: {-# LANGUAGE LambdaCase #-}
  {-# LANGUAGE NamedFieldPuns #-}
  {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE QuasiQuotes #-}
  {-# LANGUAGE TupleSections #-}
  
  module Simplex.Messaging.Agent.Store.Postgres.Common
    ( DBStore (..),
      DBStorePool (..),
      DBOpts (..),
      newDBStorePool,
      withConnection,
      withConnection',
      withTransaction,
      withTransaction',
      withTransactionPriority,
    )
  where
  
  import Control.Concurrent.MVar
  import Control.Concurrent.STM
  import Control.Exception (bracket)
  import Data.ByteString (ByteString)
  import qualified Database.PostgreSQL.Simple as PSQL
  import Numeric.Natural (Natural)
  import Simplex.Messaging.Agent.Store.Postgres.Options
  
  -- TODO [postgres] use log_min_duration_statement instead of custom slow queries (SQLite's Connection type)
  data DBStore = DBStore
    { dbConnstr :: ByteString,
      dbSchema :: ByteString,
      dbPoolSize :: Int,
      dbPriorityPool :: DBStorePool,
      dbPool :: DBStorePool,
      -- dbPoolSize :: Int,
      -- dbPool :: TBQueue PSQL.Connection,
      -- -- MVar is needed for fair pool distribution, without STM retry contention.
      -- -- Only one thread can be blocked on STM read.
      -- dbSem :: MVar (),
      dbClosed :: TVar Bool,
      dbNew :: Bool
    }
  
  newDBStorePool :: Natural -> IO DBStorePool
  newDBStorePool poolSize = do
    dbSem <- newMVar ()
    dbPoolConns <- newTBQueueIO poolSize
    pure DBStorePool {dbSem, dbPoolConns}
  
  data DBStorePool = DBStorePool
    { dbPoolConns :: TBQueue PSQL.Connection,
      -- MVar is needed for fair pool distribution, without STM retry contention.
      -- Only one thread can be blocked on STM read.
      dbSem :: MVar ()
    }
  
  withConnectionPriority :: DBStore -> Bool -> (PSQL.Connection -> IO a) -> IO a
  withConnectionPriority DBStore {dbPriorityPool, dbPool} priority =
    withConnectionPool $ if priority then dbPriorityPool else dbPool
  {-# INLINE withConnectionPriority #-}
  
  withConnectionPool :: DBStorePool -> (PSQL.Connection -> IO a) -> IO a
  withConnectionPool DBStorePool {dbPoolConns, dbSem} =
    bracket
      (withMVar dbSem $ \_ -> atomically $ readTBQueue dbPoolConns)
      (atomically . writeTBQueue dbPoolConns)
  
  withConnection :: DBStore -> (PSQL.Connection -> IO a) -> IO a
  withConnection st = withConnectionPriority st False
  {-# INLINE withConnection #-}
  
  withConnection' :: DBStore -> (PSQL.Connection -> IO a) -> IO a
  withConnection' = withConnection
  {-# INLINE withConnection' #-}
  
  withTransaction' :: DBStore -> (PSQL.Connection -> IO a) -> IO a
  withTransaction' = withTransaction
  {-# INLINE withTransaction' #-}
  
  withTransaction :: DBStore -> (PSQL.Connection -> IO a) -> IO a
  withTransaction st = withTransactionPriority st False
  {-# INLINE withTransaction #-}
  
  -- TODO [postgres] analogue for dbBusyLoop?
  withTransactionPriority :: DBStore -> Bool -> (PSQL.Connection -> IO a) -> IO a
  withTransactionPriority st priority action = withConnectionPriority st priority transaction
    where
      transaction conn = PSQL.withTransaction conn $ action conn
  