packages feed

hasql-pipes (empty) → 0.1.0.0

raw patch · 6 files changed

+279/−0 lines, 6 filesdep +basedep +bytestring-tree-builderdep +hasqlsetup-changed

Dependencies added: base, bytestring-tree-builder, hasql, pipes, pipes-safe, protolude

Files

+ ChangeLog.md view
@@ -0,0 +1,3 @@+# Changelog for hasql-pipes++## Unreleased changes
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Author name here (c) 2020++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 Author name here 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,1 @@+# hasql-pipes
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ hasql-pipes.cabal view
@@ -0,0 +1,44 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.33.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: 2fc484dece0baf332928773545e2531ae503253a571ed39a54fea894455adb7d++name:           hasql-pipes+version:        0.1.0.0+synopsis:       A pipe to stream a postgres database cursor in the hasql ecosystem+description:    Please see the README on GitLab at <https://gitlab.com/paolo.veronelli/hasql-pipes>+category:       Database, Streaming+homepage:       https://github.com/https://gitlab.com/paolo.veronelli/hasql-pipes#readme+bug-reports:    https://github.com/https://gitlab.com/paolo.veronelli/hasql-pipes/issues+author:         Paolo Veronelli+maintainer:     paolo.veronelli@gmail.com+copyright:      2020 Paolo Veronelli+license:        BSD3+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md+    ChangeLog.md++source-repository head+  type: git+  location: https://github.com/https://gitlab.com/paolo.veronelli/hasql-pipes++library+  exposed-modules:+      Hasql.Pipes+  other-modules:+      Paths_hasql_pipes+  hs-source-dirs:+      src+  build-depends:+      base >=4.7 && <5+    , bytestring-tree-builder+    , hasql+    , pipes+    , pipes-safe+    , protolude+  default-language: Haskell2010
+ src/Hasql/Pipes.hs view
@@ -0,0 +1,199 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}++-- | This library has 2 high level functions+-- +--  * 'cursorPipe' to stream a query from postgres DB+--  * 'connect' to handle connection in 'SafeT IO'+-- +-- Here is how usage looks like from a production excerpt for a table of samples+--+-- mkDB :: ByteString+-- mkDB =+--   [qmb|+--     create table samples (+--       id         uuid not null,+--       time       bigint not null,+--       value      double precision not null,+--     );+--  |]+-- @+-- streamSamples+--   :: RunSession+--   -> Int64 -- ^ first sample time+--   -> Producer (UUID, Int64, Double) (SafeT IO) ()+-- streamSamples (RunSession run) from' =+--   yield from'+--     >-> cursorPipe+--       do run+--       do samplesEncoder+--       do samplesDecoder+--       do Cursor "sample_cursor"+--       do Template [qmb| select * from samples where time >= $1 order by time; |]+--       do 1000+--+-- samplesEncoder :: Params Int64+-- samplesEncoder = param $ E.nonNullable E.int8+--+-- samplesDecoder :: Row (UUID, Int64, Double)+-- samplesDecoder =+--   (,,)+--     <$> do column . D.nonNullable $ D.uuid+--     <*> do column . D.nonNullable $ D.int8+--     <*> do column . D.nonNullable $ D.float8+--+-- localConnect :: Trace IO DatabaseLog -> SafeT IO RunSession+-- localConnect tracer = do+--   connect tracer $+--     settings+--       "10.1.9.95"+--       5432+--       "postgres"+--       "postgres"+--       "postgres"+--+-- main :: IO ()+-- main = do+--   ts <- getPOSIXTime+--   runSafeT $ do+--     run' <- localConnect pPrint+--     runEffect $ streamSamples run' (floor ts - 600) >-> P.print+--+-- +-- @+++module Hasql.Pipes where++import qualified ByteString.TreeBuilder as TB+import Hasql.Connection (acquire, ConnectionError, Settings)+import Hasql.Decoders (Row, rowList)+import qualified Hasql.Decoders as D+import Hasql.Encoders (Params)+import qualified Hasql.Encoders as E+import Hasql.Session+import Hasql.Statement+import Pipes+import qualified Pipes as P+import Pipes.Safe+import qualified Pipes.Safe as P+import Protolude+import qualified Hasql.Connection as HC++-- | cursor name+newtype Cursor = Cursor ByteString deriving (IsString)++-- | query to run+newtype Template = Template ByteString deriving (IsString)++-- | a statement to declare a cursor parametrized over some parameters+declareCursor+  :: E.Params a -- ^ paramenters encoding+  -> Cursor -- ^ cursor name+  -> Template -- ^ query template+  -> Statement a ()+declareCursor encoder (Cursor name) (Template template) =+  Statement sql' encoder D.noResult False+  where+    sql' =+      TB.toByteString $+        "DECLARE "+          <> TB.byteString name+          <> " NO SCROLL CURSOR FOR "+          <> TB.byteString template++-- | a statement to close the cursor+closeCursor+  :: Cursor -- ^ cursor name+  -> Statement () ()+closeCursor (Cursor name) =+  Statement sql' E.noParams D.noResult True+  where+    sql' = "CLOSE " <> name++-- | a statement to fetch given number of rows from cursor forward and apply decoders+fetchFromCursor+  :: Cursor -- ^ cursor name+  -> Batch -- ^ max number of rows to fetch+  -> D.Result result -- ^ row decoders+  -> Statement () result+fetchFromCursor (Cursor name) (Batch batch) decoder =+  Statement sql' E.noParams decoder True+  where+    sql' =+      TB.toByteString $+        "FETCH FORWARD "+          <> TB.asciiIntegral batch+          <> " FROM "+          <> TB.byteString name++beginTransaction :: Session ()+beginTransaction = sql "BEGIN TRANSACTION"++endTransaction :: Session ()+endTransaction = sql "END TRANSACTION"++-- | number of rows+newtype Batch = Batch Int deriving (Num)++-- | stream rows for queries of the same template+cursorPipe+  :: (forall b. Session b -> IO b) -- ^ execute a session command+  -> Params z -- ^ query parameters encoders+  -> Row a -- ^ row decoders+  -> Cursor -- ^ desidered cursor name+  -> Template -- ^ query template+  -> Batch -- ^ number of rows to repeat fetching+  -> Pipe z a (SafeT IO) ()+cursorPipe runS encoders decoders cursor template batch = forever $ do+  parameters <- await+  void $+    liftIO $ runS do+      beginTransaction+      statement parameters $ declareCursor encoders cursor template+  transaction <- lift $+    register $ runS do+      statement () $ closeCursor cursor+      endTransaction+  let loop = do+        xs <-+          liftIO $+            runS $+              statement () $+                fetchFromCursor cursor batch $+                  rowList decoders+        case xs of+          [] -> pure ()+          _ -> P.each xs >> loop+  loop+  lift $ P.release transaction++data DatabaseLog+  = ConnectionReady+  | ConnectionClosed+  | ConnectionFailed ConnectionError+  | QueryFailed QueryError+  deriving (Show)++newtype RunSession = RunSession (forall a. Session a -> IO a)++connect :: (DatabaseLog -> IO ()) -> Settings -> SafeT IO RunSession+connect tracer settings' = do+  mconnection <- liftIO (acquire settings')+  connection <- case mconnection of +    Left e -> do +      liftIO $ tracer $ ConnectionFailed e+      panic "no connection"+    Right c -> pure c+  liftIO $ tracer ConnectionReady+  void $+    register $ do+      liftIO $ HC.release connection+      tracer ConnectionClosed+  pure $ RunSession \s -> do+    result <- run s connection+    case result of+      Left e -> tracer (QueryFailed e) >> panic "query failed"+      Right r -> pure r