@@ -32,18 +32,25 @@ import Control.Monad
3232import Control.Monad.Except
3333import Control.Monad.IO.Class
3434import Control.Monad.Trans.Except
35+ import Data.ByteString.Builder (Builder )
36+ import qualified Data.ByteString.Builder as BB
37+ import Data.ByteString.Char8 (ByteString )
38+ import qualified Data.ByteString.Char8 as B
39+ import qualified Data.ByteString.Lazy as LB
3540import Data.Bitraversable (bimapM )
3641import Data.Either (fromRight )
3742import Data.Functor (($>) )
3843import Data.Int (Int64 )
44+ import Data.List (intersperse )
3945import qualified Data.Map.Strict as M
4046import Data.Maybe (catMaybes )
4147import qualified Data.Text as T
4248import Data.Time.Clock.System (SystemTime (.. ), getSystemTime )
4349import Database.PostgreSQL.Simple (Binary (.. ), Only (.. ), Query , SqlError )
4450import qualified Database.PostgreSQL.Simple as DB
51+ import qualified Database.PostgreSQL.Simple.Copy as DB
4552import Database.PostgreSQL.Simple.FromField (FromField (.. ))
46- import Database.PostgreSQL.Simple.ToField (ToField (.. ))
53+ import Database.PostgreSQL.Simple.ToField (Action ( .. ), ToField (.. ))
4754import Database.PostgreSQL.Simple.Errors (ConstraintViolation (.. ), constraintViolation )
4855import Database.PostgreSQL.Simple.SqlQQ (sql )
4956import GHC.IO (catchAny )
@@ -160,7 +167,7 @@ instance StoreQueueClass q => QueueStoreClass q (PostgresQueueStore q) where
160167 loadSndQueue = loadQueue " WHERE sender_id = ?" $ \ rId -> TM. insert qId rId senders
161168 loadNtfQueue = loadQueue " WHERE notifier_id = ?" $ \ _ -> pure () -- do NOT cache ref - ntf subscriptions are rare
162169 loadQueue condition insertRef =
163- runExceptT $ do
170+ E. uninterruptibleMask_ $ runExceptT $ do
164171 (rId, qRec) <-
165172 withDB " getQueue_" st $ \ db -> firstRow rowToQueueRec AUTH $
166173 DB. query db (queueRecQuery <> condition <> " AND deleted_at IS NULL" ) (Only qId)
@@ -182,7 +189,7 @@ instance StoreQueueClass q => QueueStoreClass q (PostgresQueueStore q) where
182189
183190 secureQueue :: PostgresQueueStore q -> q -> SndPublicAuthKey -> IO (Either ErrorType () )
184191 secureQueue st sq sKey =
185- withQueueDB sq " secureQueue" $ \ q -> do
192+ withQueueRec sq " secureQueue" $ \ q -> do
186193 verify q
187194 assertUpdated $ withDB' " secureQueue" st $ \ db ->
188195 DB. execute db " UPDATE msg_queues SET sender_key = ? WHERE recipient_id = ? AND deleted_at IS NULL" (sKey, rId)
@@ -196,7 +203,7 @@ instance StoreQueueClass q => QueueStoreClass q (PostgresQueueStore q) where
196203
197204 addQueueNotifier :: PostgresQueueStore q -> q -> NtfCreds -> IO (Either ErrorType (Maybe NotifierId ))
198205 addQueueNotifier st sq ntfCreds@ NtfCreds {notifierId = nId, notifierKey, rcvNtfDhSecret} =
199- withQueueDB sq " addQueueNotifier" $ \ q ->
206+ withQueueRec sq " addQueueNotifier" $ \ q ->
200207 ExceptT $ withLockMap (notifierLocks st) nId " addQueueNotifier" $
201208 ifM (TM. memberIO nId notifiers) (pure $ Left DUPLICATE_ ) $ runExceptT $ do
202209 assertUpdated $ withDB " addQueueNotifier" st $ \ db ->
@@ -223,7 +230,7 @@ instance StoreQueueClass q => QueueStoreClass q (PostgresQueueStore q) where
223230
224231 deleteQueueNotifier :: PostgresQueueStore q -> q -> IO (Either ErrorType (Maybe NotifierId ))
225232 deleteQueueNotifier st sq =
226- withQueueDB sq " deleteQueueNotifier" $ \ q ->
233+ withQueueRec sq " deleteQueueNotifier" $ \ q ->
227234 ExceptT $ fmap sequence $ forM (notifier q) $ \ NtfCreds {notifierId = nId} ->
228235 withLockMap (notifierLocks st) nId " deleteQueueNotifier" $ runExceptT $ do
229236 assertUpdated $ withDB' " deleteQueueNotifier" st update
@@ -260,7 +267,7 @@ instance StoreQueueClass q => QueueStoreClass q (PostgresQueueStore q) where
260267
261268 updateQueueTime :: PostgresQueueStore q -> q -> RoundedSystemTime -> IO (Either ErrorType QueueRec )
262269 updateQueueTime st sq t =
263- withQueueDB sq " updateQueueTime" $ \ q@ QueueRec {updatedAt} ->
270+ withQueueRec sq " updateQueueTime" $ \ q@ QueueRec {updatedAt} ->
264271 if updatedAt == Just t
265272 then pure q
266273 else do
@@ -295,21 +302,19 @@ batchInsertQueues tty queues toStore = do
295302 qs <- catMaybes <$> mapM (\ (rId, q) -> (rId,) <$$> readTVarIO (queueRec q)) (M. assocs queues)
296303 putStrLn $ " Importing " <> show (length qs) <> " queues..."
297304 let st = dbStore toStore
298- (qCnt, count) <- foldM (processChunk st) (0 , 0 ) $ toChunks 1000000 qs
305+ count <-
306+ withConnection st $ \ db -> do
307+ DB. copy_ db " COPY msg_queues (recipient_id, recipient_key, rcv_dh_secret, sender_id, sender_key, snd_secure, notifier_id, notifier_key, rcv_ntf_dh_secret, status, updated_at) FROM STDIN WITH (FORMAT CSV)"
308+ mapM_ (putQueue db) (zip [1 .. ] qs)
309+ DB. putCopyEnd db
310+ Only qCnt : _ <- withConnection st (`DB.query_` " SELECT count(*) FROM msg_queues" )
299311 putStrLn $ progress count
300312 pure qCnt
301313 where
302- processChunk st (qCnt, i) qs = do
303- qCnt' <- withConnection st $ \ db -> DB. executeMany db insertQueueQuery $ map queueRecToRow qs
304- let i' = i + length qs
305- when tty $ putStr (progress i' <> " \r " ) >> hFlush stdout
306- pure (qCnt + qCnt', i')
314+ putQueue db (i :: Int , q ) = do
315+ DB. putCopyData db $ queueRecToText q
316+ when (tty && i `mod` 100000 == 0 ) $ putStr (progress i <> " \r " ) >> hFlush stdout
307317 progress i = " Imported: " <> show i <> " queues"
308- toChunks :: Int -> [a ] -> [[a ]]
309- toChunks _ [] = []
310- toChunks n xs =
311- let (ys, xs') = splitAt n xs
312- in ys : toChunks n xs'
313318
314319insertQueueQuery :: Query
315320insertQueueQuery =
@@ -349,21 +354,49 @@ queueRecToRow :: (RecipientId, QueueRec) -> QueueRecRow
349354queueRecToRow (rId, QueueRec {recipientKey, rcvDhSecret, senderId, senderKey, sndSecure, notifier = n, status, updatedAt}) =
350355 (rId, recipientKey, rcvDhSecret, senderId, senderKey, sndSecure, notifierId <$> n, notifierKey <$> n, rcvNtfDhSecret <$> n, status, updatedAt)
351356
357+ queueRecToText :: (RecipientId , QueueRec ) -> ByteString
358+ queueRecToText (rId, QueueRec {recipientKey, rcvDhSecret, senderId, senderKey, sndSecure, notifier = n, status, updatedAt}) =
359+ LB. toStrict $ BB. toLazyByteString $ mconcat tabFields <> BB. char7 ' \n '
360+ where
361+ tabFields = BB. char7 ' ,' `intersperse` fields
362+ fields =
363+ [ renderField (toField rId),
364+ renderField (toField recipientKey),
365+ renderField (toField rcvDhSecret),
366+ renderField (toField senderId),
367+ nullable senderKey,
368+ renderField (toField sndSecure),
369+ nullable (notifierId <$> n),
370+ nullable (notifierKey <$> n),
371+ nullable (rcvNtfDhSecret <$> n),
372+ BB. char7 ' "' <> renderField (toField status) <> BB. char7 ' "' ,
373+ nullable updatedAt
374+ ]
375+ nullable :: ToField a => Maybe a -> Builder
376+ nullable = maybe mempty (renderField . toField)
377+ renderField :: Action -> Builder
378+ renderField = \ case
379+ Plain bld -> bld
380+ Escape s -> BB. byteString s
381+ EscapeByteA s -> BB. string7 " \\ x" <> BB. byteStringHex s
382+ EscapeIdentifier s -> BB. byteString s -- Not used in COPY data
383+ Many as -> mconcat (map renderField as)
384+
352385rowToQueueRec :: QueueRecRow -> (RecipientId , QueueRec )
353386rowToQueueRec (rId, recipientKey, rcvDhSecret, senderId, senderKey, sndSecure, notifierId_, notifierKey_, rcvNtfDhSecret_, status, updatedAt) =
354387 let notifier = NtfCreds <$> notifierId_ <*> notifierKey_ <*> rcvNtfDhSecret_
355388 in (rId, QueueRec {recipientKey, rcvDhSecret, senderId, senderKey, sndSecure, notifier, status, updatedAt})
356389
357390setStatusDB :: StoreQueueClass q => String -> PostgresQueueStore q -> q -> ServerEntityStatus -> ExceptT ErrorType IO () -> IO (Either ErrorType () )
358391setStatusDB op st sq status writeLog =
359- withQueueDB sq op $ \ q -> do
392+ withQueueRec sq op $ \ q -> do
360393 assertUpdated $ withDB' op st $ \ db ->
361394 DB. execute db " UPDATE msg_queues SET status = ? WHERE recipient_id = ? AND deleted_at IS NULL" (status, recipientId sq)
362395 atomically $ writeTVar (queueRec sq) $ Just q {status}
363396 writeLog
364397
365- withQueueDB :: StoreQueueClass q => q -> String -> (QueueRec -> ExceptT ErrorType IO a ) -> IO (Either ErrorType a )
366- withQueueDB sq op action =
398+ withQueueRec :: StoreQueueClass q => q -> String -> (QueueRec -> ExceptT ErrorType IO a ) -> IO (Either ErrorType a )
399+ withQueueRec sq op action =
367400 withQueueLock sq op $ E. uninterruptibleMask_ $ runExceptT $ ExceptT (readQueueRecIO $ queueRec sq) >>= action
368401
369402assertUpdated :: ExceptT ErrorType IO Int64 -> ExceptT ErrorType IO ()
@@ -379,7 +412,7 @@ withDB op st action =
379412 logErr :: E. SomeException -> IO (Either ErrorType a )
380413 logErr e = logError (" STORE: " <> T. pack err) $> Left (STORE err)
381414 where
382- err = op <> " , withLog , " <> show e
415+ err = op <> " , withDB , " <> show e
383416
384417withLog :: MonadIO m => String -> PostgresQueueStore q -> (StoreLog 'WriteMode -> IO () ) -> m ()
385418withLog op PostgresQueueStore {dbStoreLog} action =
0 commit comments