packages feed

postgresql-lo-stream (empty) → 0.1.0.0

raw patch · 5 files changed

+179/−0 lines, 5 filesdep +basedep +bytestringdep +io-streamssetup-changed

Dependencies added: base, bytestring, io-streams, lifted-base, monad-loops, mtl, postgresql-simple

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2019, Obsidian Systems LLC++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Obsidian Systems LLC nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,3 @@+# posgresql-lo-stream++This library provides some convenience functions for working with [Large Objects](https://www.postgresql.org/docs/current/largeobjects.html) in PostgreSQL. This is built on top of [postgresql-simple](https://hackage.haskell.org/package/postgresql-simple/docs/Database-PostgreSQL-Simple-LargeObjects.html), which provides the lower-level functions for interacting with Large Objects.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ postgresql-lo-stream.cabal view
@@ -0,0 +1,28 @@+cabal-version:       >=1.10+name:                postgresql-lo-stream+version:             0.1.0.0+synopsis:            Utilities for streaming PostgreSQL LargeObjects+description:         Functions for streaming large objects to and from PostgreSQL+homepage:            https://github.com/obsidiansystems/posgresql-lo-stream+bug-reports:         https://github.com/obsidiansystems/postgresql-lo-stream/issues+license:             BSD3+license-file:        LICENSE+author:              Obsidian Systems LLC+maintainer:          maintainer@obsidian.systems+copyright:           2019 Obsidian Systems LLC+category:            Database+build-type:          Simple+extra-source-files:  README.md++library+  exposed-modules:     Database.PostgreSQL.LargeObjects.Stream+  other-extensions:    LambdaCase+  build-depends:       base >=4.11 && <4.12+                     , bytestring >=0.10 && <0.11+                     , io-streams >=1.5.0 && <1.6+                     , lifted-base >=0.2.2 && <0.2.4+                     , monad-loops >=0.4 && <0.5+                     , mtl >=2.2 && <2.3+                     , postgresql-simple >=0.5 && <0.6+  hs-source-dirs:      src+  default-language:    Haskell2010
+ src/Database/PostgreSQL/LargeObjects/Stream.hs view
@@ -0,0 +1,116 @@+{-# LANGUAGE LambdaCase #-}+module Database.PostgreSQL.LargeObjects.Stream where++import Control.Exception.Lifted (AssertionFailed (..), bracket, throwIO)+import Control.Monad.Loops (whileJust_)+import Control.Monad.State as State+import qualified Data.ByteString as BS+import Data.ByteString.Builder (byteString)+import qualified Data.ByteString.Builder as BS+import qualified Data.ByteString.Lazy as LBS+import Data.IORef (modifyIORef, newIORef, readIORef)+import Data.Semigroup ((<>))+import Database.PostgreSQL.Simple+import Database.PostgreSQL.Simple.LargeObjects (LoFd, Oid (..))+import qualified Database.PostgreSQL.Simple.LargeObjects as Sql+import System.IO (IOMode (ReadMode, WriteMode))+import System.IO.Streams (makeOutputStream)+import qualified System.IO.Streams as Streams++-- | Given a strict ByteString, create a postgres large object and fill it with those contents.+newLargeObjectBS+  :: Connection+  -> BS.ByteString+  -> IO Oid+newLargeObjectBS conn contents = do+  oid <- Sql.loCreat conn+  n <- withLargeObject conn oid WriteMode $ \lofd -> Sql.loWrite conn lofd contents+  let l = BS.length contents+  when (n /= l) . throwIO . AssertionFailed $+    "newLargeObjectBS: loWrite reported writing " <> show n <> " bytes, expected " <> show l <> "."+  return oid++-- | Given a lazy ByteString, create a postgres large object and fill it with those contents.+-- Also returns the total length of the data written.+newLargeObjectLBS+  :: Connection+  -> LBS.ByteString+  -> IO (Oid, Int)+newLargeObjectLBS conn = newLargeObjectStream conn <=< Streams.fromLazyByteString++-- | Create a new large object from an input stream, returning its object id and overall size.+newLargeObjectStream+  :: Connection+  -> Streams.InputStream BS.ByteString+  -> IO (Oid, Int)+newLargeObjectStream conn s = do+  oid <- Sql.loCreat conn+  t <- withLargeObject conn oid WriteMode $ \lofd -> do+    whileJust_ (Streams.read s) $ \chunk -> do+      n <- Sql.loWrite conn lofd chunk+      let l = BS.length chunk+      when (n /= l) . throwIO . AssertionFailed $+        "newLargeObjectStream: loWrite reported writing " <> show n <> " bytes, expected " <> show l <> "."+    Sql.loTell conn lofd+  return (oid, t)++-- | Act on a large object given by id, opening and closing the file descriptor appropriately.+withLargeObject+  :: Connection+  -> Oid+  -> IOMode+  -> (LoFd -> IO a)+  -> IO a+withLargeObject conn oid mode f =+  bracket (Sql.loOpen conn oid mode)+          (\lofd -> Sql.loClose conn lofd)+          f++-- | Stream the contents of a database large object to the given output stream. Useful with Snap's 'addToOutput'.+streamLargeObject+  :: Connection+  -> Oid+  -> Streams.OutputStream BS.Builder+  -> IO ()+streamLargeObject conn oid os =+  withLargeObject conn oid ReadMode $ \lofd ->+    fix $ \again -> do+      chunk <- Sql.loRead conn lofd 8192 -- somewhat arbitrary+      case BS.length chunk of+        0 -> return ()+        _ -> do+          Streams.write (Just $ byteString chunk) os+          again++-- | Stream the contents of a database large object to the given output stream. Useful with Snap's 'addToOutput'.+streamLargeObjectRange+  :: Connection+  -> Oid+  -> Int+  -> Int+  -> Streams.OutputStream BS.Builder+  -> IO ()+streamLargeObjectRange conn oid start end os =+  withLargeObject conn oid ReadMode $ \lofd -> do+    _ <- Sql.loSeek conn lofd Sql.AbsoluteSeek start+    let again n = do+          let nextChunkSize = min 8192 (end - n)+          chunk <- Sql.loRead conn lofd nextChunkSize+          case BS.length chunk of+            0 -> return ()+            k -> do+              Streams.write (Just $ byteString chunk) os+              again (n + k)+    again start++-- | Act on the contents of a LargeObject as a Lazy ByteString+withLargeObjectLBS :: Connection -> Oid -> (LBS.ByteString -> IO ()) -> IO ()+withLargeObjectLBS conn oid f = do+  lo <- newIORef mempty+  cb <- makeOutputStream $ \case+    Just chunk -> modifyIORef lo $ \chunks -> chunks <> chunk+    Nothing -> do+      payload <- readIORef lo+      f $ BS.toLazyByteString payload+  streamLargeObject conn oid cb+  Streams.write Nothing cb