Files
simplexmq/src/Simplex/Messaging/Agent/Store/Postgres/DB.hs
T
Evgeny 4dc40bd795 smp server: PostgreSQL queue store (#1448)
* smp server: queue store typeclass

* parameterize JournalMsgStore

* typeclass for queue store

* postgres WIP

* compiles, passes tests

* remove StoreType

* split migrations

* progress

* addQueueRec

* reduce type spaghetti

* remove addQueue from typeclass definition

* getQueue

* test postgres storage in SMP server

* fix schema

* comment

* import queues to postgresql

* import queues to postgresql

* log

* fix test

* counts

* ci: test smp server with postgres backend (#1463)

* ci: test smp server with postgres backend

* postgres service

* attempt

* attempt

* empty

* empty

* PGHOST attempt

* PGHOST + softlink attempt

* only softlink attempt

* working attempt (PGHOST)

* remove env var

* empty

* do not start server without DB schema, do not import when schema exists

* export database

* enable all tests, disable two tests

* option for migration confirmation

* comments

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2025-02-24 15:47:27 +00:00

90 lines
2.9 KiB
Haskell

{-# LANGUAGE ScopedTypeVariables #-}
module Simplex.Messaging.Agent.Store.Postgres.DB
( BoolInt (..),
PSQL.Binary (..),
PSQL.Connection,
FromField (..),
ToField (..),
PSQL.connect,
PSQL.close,
execute,
execute_,
executeMany,
PSQL.query,
PSQL.query_,
blobFieldDecoder,
fromTextField_,
)
where
import Control.Monad (void)
import Data.ByteString.Char8 (ByteString)
import Data.Int (Int64)
import Data.Text (Text)
import Data.Text.Encoding (decodeUtf8)
import Data.Typeable (Typeable)
import Data.Word (Word16, Word32)
import Database.PostgreSQL.Simple (ResultError (..))
import qualified Database.PostgreSQL.Simple as PSQL
import Database.PostgreSQL.Simple.FromField (Field (..), FieldParser, FromField (..), returnError)
import Database.PostgreSQL.Simple.ToField (ToField (..))
import Database.PostgreSQL.Simple.TypeInfo.Static (textOid, varcharOid)
newtype BoolInt = BI {unBI :: Bool}
instance FromField BoolInt where
fromField field dat = BI . (/= (0 :: Int)) <$> fromField field dat
{-# INLINE fromField #-}
instance ToField BoolInt where
toField (BI b) = toField ((if b then 1 else 0) :: Int)
{-# INLINE toField #-}
execute :: PSQL.ToRow q => PSQL.Connection -> PSQL.Query -> q -> IO ()
execute db q qs = void $ PSQL.execute db q qs
{-# INLINE execute #-}
execute_ :: PSQL.Connection -> PSQL.Query -> IO ()
execute_ db q = void $ PSQL.execute_ db q
{-# INLINE execute_ #-}
executeMany :: PSQL.ToRow q => PSQL.Connection -> PSQL.Query -> [q] -> IO ()
executeMany db q qs = void $ PSQL.executeMany db q qs
{-# INLINE executeMany #-}
-- orphan instances
-- used in FileSize
instance FromField Word32 where
fromField field dat = do
i :: Int64 <- fromField field dat
if i >= 0 && i <= fromIntegral (maxBound :: Word32)
then pure (fromIntegral i :: Word32)
else returnError ConversionFailed field "Negative value can't be converted to Word32"
-- used in Version
instance FromField Word16 where
fromField field dat = do
i :: Int64 <- fromField field dat
if i >= 0 && i <= fromIntegral (maxBound :: Word16)
then pure (fromIntegral i :: Word16)
else returnError ConversionFailed field "Negative value can't be converted to Word16"
blobFieldDecoder :: Typeable k => (ByteString -> Either String k) -> FieldParser k
blobFieldDecoder dec f val = do
x <- fromField f val
case dec x of
Right k -> pure k
Left e -> returnError ConversionFailed f ("couldn't parse field: " ++ e)
fromTextField_ :: Typeable a => (Text -> Maybe a) -> FieldParser a
fromTextField_ fromText f val =
if typeOid f `elem` [textOid, varcharOid]
then case val of
Just t -> case fromText $ decodeUtf8 t of
Just x -> pure x
_ -> returnError ConversionFailed f "invalid text value"
Nothing -> returnError UnexpectedNull f "NULL value found for non-NULL field"
else returnError Incompatible f "expecting TEXT or VARCHAR column type"