{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} module Database.Persist.Postgresql.Streaming ( selectStream ) where import Control.Monad.Reader.Class (MonadReader(ask)) import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Resource (MonadResource) import Data.Conduit (ConduitT) import Data.Conduit.Lift (runReaderC) import Data.Foldable (toList) import Database.Persist.Sql.Types.Internal (SqlBackend(..)) import Database.Persist.Sql.Util ( commaSeparated , keyAndEntityColumnNames , parseEntityValues ) import Database.Persist.Postgresql import Database.Persist.Postgresql.Streaming.Internal -- | Run a query against a PostgreSQL backend, streaming back the results -- in constant memory using the PostgreSQL cursor feature. -- -- NB: this function is likely to perform worse than 'selectSource' on small -- result sets. -- -- NB: for large result sets, this function is likely to perform very poorly -- unless you manually configure the @cursor_tuple_fraction@ setting to be -- close to @1@. This setting decides how much PostgreSQL will prioritise -- returning the first rows quickly vs. returning all rows efficiently. See -- -- https://postgresqlco.nf/doc/en/param/cursor_tuple_fraction/ -- -- for more. selectStream :: ( MonadResource m , MonadReader backend m , PersistRecordBackend record SqlBackend , BackendCompatible (RawPostgresql SqlBackend) backend ) => [Filter record] -> [SelectOpt record] -> ConduitT () (Entity record) m () selectStream filts opts = do backend@(RawPostgresql conn _) <- lift $ projectBackend <$> ask runReaderC backend $ rawSelectStream (parseEntityValues t) (sql conn) (getFiltsValues conn filts) where (limit, offset, orders) = limitOffsetOrder opts t = entityDef $ dummyFromFilts filts wher conn = filterClause Nothing conn filts ord conn = orderClause Nothing conn orders cols = commaSeparated . toList . keyAndEntityColumnNames t sql conn = connLimitOffset conn (limit,offset) $ mconcat [ "SELECT " , cols conn , " FROM " , connEscapeTableName conn t , wher conn , ord conn ] getFiltsValues conn = snd . filterClauseWithVals Nothing conn dummyFromFilts :: [Filter record] -> Maybe record dummyFromFilts _ = Nothing