packages feed

hasql 0.5.0 → 0.6.0

raw patch · 10 files changed

+509/−388 lines, 10 filesdep +eitherdep +mtldep +resource-pooldep −bytestringdep −ex-pooldep −loch-thdep ~hasql-backenddep ~hasql-postgresdep ~list-tPVP ok

version bump matches the API change (PVP)

Dependencies added: either, mtl, resource-pool

Dependencies removed: bytestring, ex-pool, loch-th, placeholders, safe, time

Dependency ranges changed: hasql-backend, hasql-postgres, list-t, monad-control, vector

API changes (from Hackage documentation)

- Hasql: CantConnect :: Text -> Error
- Hasql: ConnectionLost :: Text -> Error
- Hasql: ErroneousResult :: Text -> Error
- Hasql: NotInTransaction :: Error
- Hasql: UnexpectedResult :: Text -> Error
- Hasql: UnparsableRow :: Text -> Error
- Hasql: UnparsableTemplate :: Text -> Error
- Hasql: count :: (Backend b, Mapping b Word64) => Statement b -> Tx b s Word64
- Hasql: data Error
- Hasql: data IsolationLevel :: *
- Hasql: data SessionSettings
- Hasql: instance Applicative (Tx b s)
- Hasql: instance Applicative m => Applicative (Session b s m)
- Hasql: instance Eq Error
- Hasql: instance Exception Error
- Hasql: instance Functor (Tx b s)
- Hasql: instance Functor m => Functor (Session b s m)
- Hasql: instance Monad (Tx b s)
- Hasql: instance Monad m => Monad (Session b s m)
- Hasql: instance MonadBase IO m => MonadBase IO (Session b s m)
- Hasql: instance MonadBaseControl IO m => MonadBaseControl IO (Session b s m)
- Hasql: instance MonadIO m => MonadIO (Session b s m)
- Hasql: instance MonadTrans (Session b s)
- Hasql: instance MonadTransControl (Session b s)
- Hasql: instance Ord Error
- Hasql: instance Show Error
- Hasql: instance Typeable Error
- Hasql: list :: (Backend b, RowParser b r) => Statement b -> Tx b s [r]
- Hasql: parseRow :: RowParser b r => Vector (Result b) -> Either Text r
- Hasql: q :: QuasiQuoter
- Hasql: sessionSettings :: Word32 -> NominalDiffTime -> Maybe SessionSettings
- Hasql: sessionUnlifter :: MonadBaseControl IO m => Session b s m (Session b s m r -> m r)
- Hasql: single :: (Backend b, RowParser b r) => Statement b -> Tx b s (Maybe r)
- Hasql: stream :: (Backend b, RowParser b r) => Statement b -> TxListT s (Tx b s) r
- Hasql: type Mode = Maybe (IsolationLevel, Bool)
- Hasql: unit :: Backend b => Statement b -> Tx b s ()
+ Hasql: CxError :: (CxError c) -> SessionError c
+ Hasql: ResultError :: Text -> SessionError c
+ Hasql: TxError :: (TxError c) -> SessionError c
+ Hasql: acquirePool :: Cx c => CxSettings c -> PoolSettings -> IO (Pool c)
+ Hasql: countTx :: CxValue c Word64 => Stmt c -> Tx c s Word64
+ Hasql: data Pool c
+ Hasql: data PoolSettings
+ Hasql: data SessionError c
+ Hasql: data Stmt c :: * -> *
+ Hasql: data TxIsolationLevel :: *
+ Hasql: instance (Eq (CxError c), Eq (TxError c)) => Eq (SessionError c)
+ Hasql: instance (Show (CxError c), Show (TxError c)) => Show (SessionError c)
+ Hasql: instance Applicative (Tx c s)
+ Hasql: instance Functor (Tx c s)
+ Hasql: instance MFunctor (Session c)
+ Hasql: instance Monad (Tx c s)
+ Hasql: instance Monad m => Applicative (Session c m)
+ Hasql: instance Monad m => Functor (Session c m)
+ Hasql: instance Monad m => Monad (Session c m)
+ Hasql: instance Monad m => MonadError (SessionError c) (Session c m)
+ Hasql: instance MonadBase IO m => MonadBase IO (Session c m)
+ Hasql: instance MonadBaseControl IO m => MonadBaseControl IO (Session c m)
+ Hasql: instance MonadIO m => MonadIO (Session c m)
+ Hasql: instance MonadTrans (Session c)
+ Hasql: instance MonadTransControl (Session c)
+ Hasql: listTx :: RowParser c r => Stmt c -> Tx c s [r]
+ Hasql: maybeTx :: RowParser c r => Stmt c -> Tx c s (Maybe r)
+ Hasql: poolSettings :: Int -> Int -> Maybe PoolSettings
+ Hasql: releasePool :: Pool c -> IO ()
+ Hasql: singleTx :: RowParser c r => Stmt c -> Tx c s r
+ Hasql: stmt :: QuasiQuoter
+ Hasql: streamTx :: RowParser c r => Stmt c -> Tx c s (TxListT s (Tx c s) r)
+ Hasql: type TxMode = Maybe (TxIsolationLevel, TxWriteMode)
+ Hasql: type TxWriteMode = Maybe Bool
+ Hasql: unitTx :: Stmt c -> Tx c s ()
+ Hasql: vectorTx :: RowParser c r => Stmt c -> Tx c s (Vector r)
- Hasql: ReadCommitted :: IsolationLevel
+ Hasql: ReadCommitted :: TxIsolationLevel
- Hasql: ReadUncommitted :: IsolationLevel
+ Hasql: ReadUncommitted :: TxIsolationLevel
- Hasql: RepeatableReads :: IsolationLevel
+ Hasql: RepeatableReads :: TxIsolationLevel
- Hasql: Serializable :: IsolationLevel
+ Hasql: Serializable :: TxIsolationLevel
- Hasql: class RowParser b r
+ Hasql: class RowParser c r
- Hasql: data Session b s m r
+ Hasql: data Session c m r
- Hasql: data Tx b s r
+ Hasql: data Tx c s r
- Hasql: session :: (Backend b, MonadBaseControl IO m) => b -> SessionSettings -> (forall s. Session b s m r) -> m r
+ Hasql: session :: Pool c -> Session c m a -> m (Either (SessionError c) a)
- Hasql: tx :: (Backend b, MonadBase IO m) => Mode -> (forall s. Tx b s r) -> Session b s m r
+ Hasql: tx :: (CxTx c, MonadBaseControl IO m) => TxMode -> (forall s. Tx c s r) -> Session c m r

Files

CHANGELOG.md view
@@ -1,3 +1,17 @@+# 0.6.0 - Major API overhaul+* The connection pool acquisition is now explicit and is no longer handled by the `Session` monad. This should provide for a simpler integration with other libraries.+* The `Session` monad is now merely a convenience thing for providing a context to multiple transactions. One can run it as many times as he wants - it won't reestablish any resources any more.+* The connection timeout is now set using `Int` for simplicity.+* There are no exceptions any more. All the error-reporting is typed and done explicitly, using `Either`.+* The error types are now mostly backend-specific.+* The transaction mode is now extended to support uncommittable transactions with the `TxWriteMode` type.+* All `Tx` functions are now appended with a "Tx" suffix.+* Added `vectorTx` and `maybeTx` and updated the semantics of `singleTx`.+* `q` statement quasi-quoter is now renamed to more meaningful `stmt`.+* The `Statement` type is renamed to `Stmt` and is now exported from the main API.+* `RowParser` is now uninstantiable. This enforces the idiomatic usage of the library.+* Statement templates now support UTF-8.+ # 0.5.0 * Update the "list-t" and "monad-control" deps 
demo/Main.hs view
@@ -1,16 +1,17 @@ -- You can execute this file with 'cabal bench demo'. {-# LANGUAGE QuasiQuotes, ScopedTypeVariables, OverloadedStrings #-} -import Control.Monad+import Control.Monad hiding (forM_, mapM_, forM, mapM) import Control.Monad.IO.Class import Control.Monad.Trans.Class import Control.Monad.Trans.Maybe import Data.Functor.Identity+import Data.Foldable  -- Import the API from the "hasql" library import qualified Hasql as H --- Import the backend settings from the "hasql-postgres" library+-- Import the backend API from the "hasql-postgres" library import qualified Hasql.Postgres as HP  @@ -18,23 +19,28 @@    let postgresSettings = HP.ParamSettings "localhost" 5432 "postgres" "" "postgres" -  -- Prepare session settings with a smart constructor,+  -- Prepare the pool settings with a smart constructor,   -- which checks the inputted values on correctness.-  -- Set the connections pool size to 6 and the timeout to 30.-  sessionSettings <- maybe (fail "Improper session settings") return $ -                     H.sessionSettings 6 30+  -- Set the connection pool size to 6 and the timeout to 30 seconds.+  poolSettings <- maybe (fail "Improper session settings") return $ +                  H.poolSettings 6 30 -  -- Run a database session,-  -- while automatically managing the resources and exceptions.-  H.session postgresSettings sessionSettings $ do+  -- Acquire the database connections pool.+  -- Gotta help the compiler with the type signature of the pool a bit.+  pool :: H.Pool HP.Postgres +       <- H.acquirePool postgresSettings poolSettings +  -- Provide a context for execution of transactions.+  -- 'Session' is merely a convenience wrapper around 'ReaderT'.+  H.session pool $ do+     -- Execute a group of statements without any locking and ACID guarantees:     H.tx Nothing $ do-      H.unit [H.q|DROP TABLE IF EXISTS a|]-      H.unit [H.q|CREATE TABLE a (id SERIAL NOT NULL, balance INT8, PRIMARY KEY (id))|]+      H.unitTx [H.stmt|DROP TABLE IF EXISTS a|]+      H.unitTx [H.stmt|CREATE TABLE a (id SERIAL NOT NULL, balance INT8, PRIMARY KEY (id))|]       -- Insert three rows:       replicateM_ 3 $ do -        H.unit [H.q|INSERT INTO a (balance) VALUES (0)|]+        H.unitTx [H.stmt|INSERT INTO a (balance) VALUES (0)|]      -- Declare a list of transfer settings, which we'll later use.     -- The tuple structure is: @@ -45,23 +51,26 @@     forM_ transfers $ \(id1, id2, amount) -> do       -- Run a transaction with ACID guarantees.       -- Hasql will automatically roll it back and retry it in case of conflicts.-      H.tx (Just (H.Serializable, True)) $ do+      H.tx (Just (H.Serializable, (Just True))) $ do         -- Use MaybeT to handle empty results:         runMaybeT $ do           -- To distinguish results rows containing just one column,            -- we use 'Identity' as a sort of a single element tuple.-          Identity balance1 <- MaybeT $ H.single $ [H.q|SELECT balance FROM a WHERE id=?|] id1-          Identity balance2 <- MaybeT $ H.single $ [H.q|SELECT balance FROM a WHERE id=?|] id2-          lift $ H.unit $ [H.q|UPDATE a SET balance=? WHERE id=?|] (balance1 - amount) id1-          lift $ H.unit $ [H.q|UPDATE a SET balance=? WHERE id=?|] (balance2 + amount) id2+          Identity balance1 <- MaybeT $ H.maybeTx $ [H.stmt|SELECT balance FROM a WHERE id=?|] id1+          Identity balance2 <- MaybeT $ H.maybeTx $ [H.stmt|SELECT balance FROM a WHERE id=?|] id2+          lift $ H.unitTx $ [H.stmt|UPDATE a SET balance=? WHERE id=?|] (balance1 - amount) id1+          lift $ H.unitTx $ [H.stmt|UPDATE a SET balance=? WHERE id=?|] (balance2 + amount) id2      -- Output all the updated rows:     do       -- Unfortunately in this case there's no way to infer the type of the results,       -- so we need to specify it explicitly:-      rows :: [(Int, Int)] <- H.tx Nothing $ H.list $ [H.q|SELECT * FROM a|]-      forM_ rows $ \(id, amount) -> do+      rows <- H.tx Nothing $ H.vectorTx $ [H.stmt|SELECT * FROM a|]+      forM_ rows $ \(id :: Int, amount :: Int) -> do         liftIO $ putStrLn $ "ID: " ++ show id ++ ", Amount: " ++ show amount++  -- Release all previously acquired resources. Just for fun.+  H.releasePool pool   
hasql.cabal view
@@ -1,7 +1,7 @@ name:   hasql version:-  0.5.0+  0.6.0 synopsis:   A minimalistic general high level API for relational databases description:@@ -10,7 +10,7 @@   .   Features:   .-  * Concise and crisp API. Just a few functions and two monads doing all the+  * Concise and crisp API. Just a few functions and one monad doing all the   boilerplate job for you.   .   * A powerful transaction abstraction, which provides @@ -31,7 +31,7 @@   * Automated management of resources related to connections, transactions and   cursors.   .-  * A built-in connections pool.+  * A built-in connection pool.   .   * Compile-time generation of templates. You just can't write a statement with an   incorrect number of placeholders.@@ -94,25 +94,21 @@   exposed-modules:     Hasql   build-depends:-    hasql-backend == 0.2.*,-    -- template-haskell:+    -- +    resource-pool == 0.2.*,+    hasql-backend == 0.3.*,+    --      template-haskell >= 2.8 && < 2.10,-    -- parsing:+    --      attoparsec >= 0.10 && < 0.13,-    -- database:-    ex-pool == 0.2.*,-    -- data:+    --      vector < 0.11,-    time >= 1.4 && < 1.6,-    bytestring == 0.10.*,     text >= 1.0 && < 1.3,-    -- errors:-    loch-th == 0.2.*,-    placeholders == 0.1.*,-    -- general:-    safe == 0.3.*,-    list-t >= 0.4 && < 0.5,+    -- +    either >= 4.3 && < 4.4,+    list-t >= 0.3.1 && < 0.5,     mmorph == 1.0.*,+    mtl >= 2.1 && < 2.3,     monad-control == 1.0.*,     transformers-base == 0.4.*,     transformers >= 0.3 && < 0.5,@@ -138,10 +134,12 @@   build-depends:     --      hasql,-    hasql-backend == 0.2.*,+    hasql-backend,     --      hspec == 2.1.*,     -- +    vector,+    --      mtl-prelude < 3,     base-prelude,     base@@ -165,7 +163,7 @@   build-depends:     --      hasql,-    hasql-postgres == 0.9.*,+    hasql-postgres == 0.10.*,     --      slave-thread == 0.1.*,     -- @@ -173,6 +171,8 @@     --      text,     -- +    monad-control,+    either,     mtl-prelude < 3,     base-prelude,     base@@ -197,7 +197,7 @@     Haskell2010   build-depends:     hasql,-    hasql-postgres == 0.9.*,+    hasql-postgres == 0.10.*,     transformers >= 0.3 && < 0.5,     base >= 4.5 && < 4.8 
hspec-postgres/Main.hs view
@@ -1,5 +1,7 @@ import BasePrelude import MTLPrelude+import Control.Monad.Trans.Either+import Control.Monad.Trans.Control import Test.Hspec import Data.Text (Text) import qualified Hasql as H@@ -10,23 +12,42 @@ main =    hspec $ do +    context "Tx" $ do++      it "does not commit if in uncommitting mode" $ do+        flip shouldBe (Right (Nothing :: Maybe (Identity Int))) =<< do+          session $ do+            H.tx Nothing $ do+              H.unitTx $ [H.stmt|DROP TABLE IF EXISTS a|]+              H.unitTx $ [H.stmt|CREATE TABLE a (x INT8 NOT NULL, PRIMARY KEY (x))|]+            H.tx (Just (H.Serializable, Just False)) $ do+              H.unitTx $ [H.stmt|INSERT INTO a (x) VALUES (2)|]+            H.tx Nothing $ do+              H.maybeTx $ [H.stmt|SELECT x FROM a WHERE x = 2|]++    context "UTF-8 templates" $ do++      it "encode properly" $ do+        flip shouldBe (Right (Just (Identity ("Ёжик" :: Text)))) =<< do+          session $ H.tx Nothing $ H.maybeTx $ [H.stmt| SELECT 'Ёжик' |]+     context "Bug" $ do        context "Unhandled transaction conflict" $ do          it "should not be" $ do           session $ H.tx Nothing $ do-            H.unit [H.q|DROP TABLE IF EXISTS artist|]-            H.unit [H.q|DROP TABLE IF EXISTS artist_union|]-            H.unit $-              [H.q|+            H.unitTx [H.stmt|DROP TABLE IF EXISTS artist|]+            H.unitTx [H.stmt|DROP TABLE IF EXISTS artist_union|]+            H.unitTx $+              [H.stmt|                 CREATE TABLE "artist_union" (                   "id" BIGSERIAL,                   PRIMARY KEY ("id")                 )               |]-            H.unit $-              [H.q|+            H.unitTx $+              [H.stmt|                 CREATE TABLE "artist" (                   "id" BIGSERIAL,                   "artist_union_id" INT8 NOT NULL,@@ -39,14 +60,14 @@           let              insertArtistUnion :: H.Tx HP.Postgres s Int64             insertArtistUnion =-              fmap (runIdentity . fromJust) $ H.single $-              [H.q| +              fmap (runIdentity . fromJust) $ H.maybeTx $+              [H.stmt|                  INSERT INTO artist_union DEFAULT VALUES RETURNING id               |]             insertArtist :: Int64 -> [Text] -> H.Tx HP.Postgres s Int64             insertArtist unionID artistNames = -              fmap (runIdentity . fromJust) $ H.single $-              [H.q| +              fmap (runIdentity . fromJust) $ H.maybeTx $+              [H.stmt|                  INSERT INTO artist                   (artist_union_id,                     names)@@ -58,7 +79,7 @@             process =                  SlaveThread.fork $ do                   session $ replicateM_ 100 $ do -                    H.tx (Just (H.Serializable, True)) $ do+                    H.tx (Just (H.Serializable, Just True)) $ do                       unionID <- insertArtistUnion                       insertArtist unionID ["a", "b", "c"]                   signal@@ -68,32 +89,25 @@     context "RowParser" $ do        it "should fail on incorrect arity" $ do-        flip shouldThrow (\case H.UnparsableRow _ -> True; _ -> False) $+        flip shouldSatisfy (\case Left (H.ResultError _) -> True; _ -> False) =<< do           session $ do             H.tx Nothing $ do-              H.unit [H.q|DROP TABLE IF EXISTS data|]-              H.unit [H.q|CREATE TABLE data (-                              field1    DECIMAL NOT NULL,-                              field2    BIGINT  NOT NULL,-                              PRIMARY KEY (field1)-                          )|]-              H.unit [H.q|INSERT INTO data (field1, field2) VALUES (0, 0)|]+              H.unitTx [H.stmt|DROP TABLE IF EXISTS data|]+              H.unitTx [H.stmt|CREATE TABLE data (+                                   field1    DECIMAL NOT NULL,+                                   field2    BIGINT  NOT NULL,+                                   PRIMARY KEY (field1)+                               )|]+              H.unitTx [H.stmt|INSERT INTO data (field1, field2) VALUES (0, 0)|]             mrow :: Maybe (Double, Int64, String) <-                H.tx Nothing $  -                H.single $ [H.q|SELECT * FROM data|]+                H.maybeTx $ [H.stmt|SELECT * FROM data|]             return ()   -- * Helpers ------------------------- -session :: (forall s. H.Session HP.Postgres s IO r) -> IO r-session =-  H.session backendSettings poolSettings-  where-    backendSettings = HP.ParamSettings "localhost" 5432 "postgres" "" "postgres"-    poolSettings = fromJust $ H.sessionSettings 6 30- newBatchGate :: Int -> IO (IO (), IO ()) newBatchGate amount =   do@@ -102,3 +116,21 @@       let signal = atomically $ readTVar counter >>= writeTVar counter . pred           block = atomically $ readTVar counter >>= \x -> when (x > 0) retry           in (signal, block)+++-- * Hasql utils+-------------------------++type Session m =+  H.Session HP.Postgres m++session :: MonadBaseControl IO m => Session m r -> m (Either (H.SessionError HP.Postgres) r)+session m =+  control $ \unlift -> do+    p <- H.acquirePool backendSettings poolSettings+    r <- unlift $ H.session p m+    H.releasePool p+    return r+  where+    backendSettings = HP.ParamSettings "localhost" 5432 "postgres" "" "postgres"+    poolSettings = fromJust $ H.poolSettings 6 30
hspec/Main.hs view
@@ -2,21 +2,43 @@ import Test.Hspec import qualified Hasql as H import qualified Hasql.Backend as HB+import qualified Data.Vector as V   data X-instance HB.Backend X where-  data StatementArgument X = -    StatementArgument String-    deriving (Eq, Show)-instance HB.Mapping X Char where-  renderValue = StatementArgument . show +data instance HB.StmtParam X = +  StmtParam String +  deriving (Eq, Show) +deriving instance Show (HB.Stmt X)+deriving instance Eq (HB.Stmt X)++instance HB.CxValue X Char where+  encodeValue = StmtParam . show+ main =    hspec $ do     context "Quasi quoter" $ do       it "generates a proper statement" $ do         (flip shouldBe)-          (" SELECT ? ", [HB.renderValue 'a'], True)-          ([H.q| SELECT ? |] 'a' :: HB.Statement X)+          (HB.Stmt "SELECT (? + ?)" (V.fromList [HB.encodeValue 'a', HB.encodeValue 'b']) True)+          ([H.stmt| SELECT (? + ?) |] 'a' 'b' :: HB.Stmt X)+      it "does not drop quotes" $ do+        let +          HB.Stmt t _ _ =+            [H.stmt| SELECT "a", 'b' |]+        (flip shouldBe)+          "SELECT \"a\", 'b'"+          t+      it "cleans whitespace" $ do+        let +          HB.Stmt t _ _ =+            [H.stmt| CREATE TABLE data (+                       field1    DECIMAL NOT NULL,+                       field2    BIGINT  NOT NULL,+                       PRIMARY KEY (field1)+                     ) |]+        (flip shouldBe)+          "CREATE TABLE data ( field1 DECIMAL NOT NULL, field2 BIGINT NOT NULL, PRIMARY KEY (field1) )"+          t
library/Hasql.hs view
@@ -4,265 +4,316 @@ -- For an introduction to the package  -- and links to more documentation please refer to  -- <../ the package's index page>.+-- +-- This API is completely disinfected from exceptions. +-- All error-reporting is explicit and +-- is presented using the 'Either' type. module Hasql (+  -- * Pool+  Pool,+  acquirePool,+  releasePool,++  -- ** Pool Settings+  PoolSettings,+  poolSettings,+   -- * Session   Session,   session,-  sessionUnlifter, -  -- ** Session Settings-  SessionSettings,-  sessionSettings,+  -- ** Session Error+  SessionError(..), +  -- * Statement+  Bknd.Stmt,+  stmt,++  -- ** Statement Execution+  unitTx,+  countTx,+  singleTx,+  maybeTx,+  listTx,+  vectorTx,+  streamTx,+   -- * Transaction   Tx,   tx,    -- ** Transaction Settings-  Mode,-  Backend.IsolationLevel(..),--  -- * Statement Quasi-Quoter-  q,--  -- * Statement Execution-  unit,-  count,-  single,-  list,-  stream,+  Bknd.TxMode(..),+  Bknd.TxIsolationLevel(..),+  Bknd.TxWriteMode(..), -  -- * Results Stream+  -- ** Result Stream   TxListT, -  -- * Row parser-  RowParser.RowParser(..),-  -  -- * Error-  Error(..),+  -- * Row Parser+  RowParser.RowParser, ) where -import Hasql.Prelude hiding (Error)-import Hasql.Backend (Backend)-import Hasql.RowParser (RowParser)-import qualified Hasql.Backend as Backend+import Hasql.Prelude+import qualified Hasql.Backend as Bknd import qualified Hasql.RowParser as RowParser import qualified Hasql.QParser as QParser import qualified ListT import qualified Data.Pool as Pool+import qualified Data.Text as Text import qualified Data.Vector as Vector+import qualified Data.Vector.Mutable as MVector import qualified Language.Haskell.TH as TH import qualified Language.Haskell.TH.Quote as TH import qualified Hasql.TH as THUtil  --- * Session+-- * Resources -------------------------  -- |--- A monad transformer,--- which executes transactions.--- --- * @b@ is a backend.--- --- * @s@ is an anonymous variable, --- used to associate 'sessionUnlifter' with a specific session. --- --- * @m@ is an inner (transformed) monad.--- --- * @r@ is a result.-newtype Session b s m r =-  Session (ReaderT (Pool.Pool (Backend.Connection b)) m r)-  deriving (Functor, Applicative, Monad, MonadTrans, MonadIO)--instance MonadTransControl (Session b s) where-  type StT (Session b s) a = a-  liftWith onRunner =-    Session $ ReaderT $ \e -> onRunner $ \(Session (ReaderT f)) -> f e-  restoreT = -    Session . ReaderT . const--instance (MonadBase IO m) => MonadBase IO (Session b s m) where-  liftBase = Session . liftBase--instance (MonadBaseControl IO m) => MonadBaseControl IO (Session b s m) where-  type StM (Session b s m) a = ComposeSt (Session b s) m a-  liftBaseWith = defaultLiftBaseWith-  restoreM = defaultRestoreM----- |--- Given backend settings, session settings, and a session monad transformer,--- execute it in the inner monad.--- --- It uses the same trick as 'ST' with the anonymous @s@ type argument--- to prohibit the use of the result of--- 'sessionUnlifter' outside of its creator session.-session :: -  (Backend.Backend b, MonadBaseControl IO m) =>-  b -> SessionSettings -> (forall s. Session b s m r) -> m r-session backend (SessionSettings size timeout) s =-  control $ \runInIO ->-    mask $ \unmask -> do-      p <- Pool.createPool (Backend.connect backend) Backend.disconnect 1 timeout size-      r <- try $ unmask $ runInIO $ runSession p s-      Pool.purgePool p-      either (throwIO :: SomeException -> IO r) return r+-- A connection pool. +newtype Pool c =+  Pool (Pool.Pool (Either (Bknd.CxError c) c))  -- |--- Get a session unlifting function, --- which allows to execute a session in the inner monad--- using the resources of the current session.--- --- Using this function in combination with 'lift'--- you can interleave 'Session' with other monad transformers.+-- Given backend-specific connection settings and pool settings, +-- acquire a backend connection pool,+-- which can then be used to work with the DB. -- --- This function has the following property:+-- When combining Hasql with other libraries, +-- which throw exceptions it makes sence to utilize +-- @Control.Exception.'bracket'@+-- like this: -- --- > (sessionUnlifter >>= \unlift -> lift (unlift m)) ≡ m-sessionUnlifter :: (MonadBaseControl IO m) => Session b s m (Session b s m r -> m r)-sessionUnlifter =-  Session $ ReaderT $ return . runSession+-- >bracket (acquirePool bkndStngs poolStngs) (releasePool) $ \pool -> do+-- >  session pool $ do+-- >    ...+-- >  ... any other IO code+acquirePool :: Bknd.Cx c => Bknd.CxSettings c -> PoolSettings -> IO (Pool c)+acquirePool cxSettings (PoolSettings size timeout) =+  fmap Pool $+  Pool.createPool (Bknd.acquireCx cxSettings) +                  (either (const $ return ()) Bknd.releaseCx) +                  (1)+                  (fromIntegral timeout) +                  (size) -runSession :: (MonadBaseControl IO m) => Pool.Pool (Backend.Connection b) -> Session b s m r -> m r-runSession e (Session r) =-  control $ \runInIO -> -    catch (runInIO (runReaderT r e)) $ \case-      Backend.CantConnect t -> throwIO $ CantConnect t-      Backend.ConnectionLost t -> throwIO $ ConnectionLost t-      Backend.ErroneousResult t -> throwIO $ ErroneousResult t-      Backend.UnexpectedResult t -> throwIO $ UnexpectedResult t-      Backend.UnparsableTemplate t -> throwIO $ UnparsableTemplate t-      Backend.TransactionConflict -> $bug "Unexpected TransactionConflict exception"-      Backend.NotInTransaction -> throwIO $ NotInTransaction+-- |+-- Release all connections acquired by the pool.+releasePool :: Pool c -> IO ()+releasePool (Pool p) =+  Pool.destroyAllResources p  --- ** Session Settings+-- ** Pool Settings -------------------------  -- |--- Settings of a session.-data SessionSettings =-  SessionSettings !Word32 !NominalDiffTime+-- Settings of a pool.+data PoolSettings =+  PoolSettings !Int !Int  -- | --- A smart constructor for session settings.-sessionSettings :: -  Word32+-- A smart constructor for pool settings.+poolSettings :: +  Int   -- ^   -- The maximum number of connections to keep open.    -- The smallest acceptable value is 1.   -- Requests for connections will block if this limit is reached.   -> -  NominalDiffTime+  Int   -- ^-  -- The amount of time for which an unused connection is kept open. -  -- The smallest acceptable value is 0.5 seconds.+  -- The amount of seconds for which an unused connection is kept open. +  -- The smallest acceptable value is 1.   -> -  Maybe SessionSettings+  Maybe PoolSettings   -- ^-  -- Maybe session settings, if they are correct.-sessionSettings size timeout =-  if size > 0 && timeout >= 0.5-    then Just $ SessionSettings size timeout+  -- Maybe pool settings, if they are correct.+poolSettings size timeout =+  if size > 0 && timeout >= 1+    then Just $ PoolSettings size timeout     else Nothing  --- ** Error+-- * Session -------------------------  -- |--- The only exception type that this API can raise.-data Error =-  -- |-  -- Cannot connect to a server.-  CantConnect Text |-  -- |-  -- The connection got interrupted.-  ConnectionLost Text |-  -- |-  -- An error returned from the database.-  ErroneousResult Text |-  -- |-  -- Unexpected result structure.-  -- Indicates usage of inappropriate statement executor.-  UnexpectedResult Text |+-- A convenience wrapper around 'ReaderT', +-- which provides a shared context for execution and error-handling of transactions.+newtype Session c m r =+  Session (ReaderT (Pool c) (EitherT (SessionError c) m) r)+  deriving (Functor, Applicative, Monad, MonadIO, MonadError (SessionError c))++instance MonadTrans (Session c) where+  lift = Session . lift . lift++instance MonadTransControl (Session c) where+  type StT (Session c) a = Either (SessionError c) a+  liftWith onUnlift =+    Session $ ReaderT $ \e -> +      lift $ onUnlift $ \(Session m) -> +        runEitherT $ flip runReaderT e $ m+  restoreT = +    Session . ReaderT . const . EitherT++deriving instance MonadBase IO m => MonadBase IO (Session c m)++instance (MonadBaseControl IO m) => MonadBaseControl IO (Session c m) where+  type StM (Session c m) a = ComposeSt (Session c) m a+  liftBaseWith = defaultLiftBaseWith+  restoreM = defaultRestoreM++instance MFunctor (Session c) where+  hoist f (Session m) = +    Session $ ReaderT $ \e ->+      EitherT $ f $ runEitherT $ flip runReaderT e $ m++-- |+-- Execute a session using an established connection pool.+-- +-- This is merely a wrapper around 'runReaderT',+-- so you can run it around every transaction,+-- if you want.+session :: Pool c -> Session c m a -> m (Either (SessionError c) a)+session pool m =+  runEitherT $ flip runReaderT pool $ case m of Session m -> m+++-- * Transaction+-------------------------++-- |+-- A transaction specialized for a backend connection @c@, +-- associated with its intermediate results using an anonymous type-argument @s@ (same trick as in 'ST')+-- and producing a result @r@.+-- +-- Running `IO` in `Tx` is prohibited. +-- The motivation is identical to `STM`: +-- the `Tx` block may get executed multiple times if any transaction conflicts arise. +-- This will result in your effectful `IO` code being executed +-- an unpredictable amount of times as well, +-- which, chances are, is not what you want.+newtype Tx c s r =+  Tx { unwrapTx :: EitherT (SessionError c) (Bknd.Tx c) r }+  deriving (Functor, Applicative, Monad)+++data SessionError c =   -- |-  -- Incorrect statement template.-  UnparsableTemplate Text |+  -- A backend-specific connection acquisition error.+  -- E.g., a failure to establish a connection.+  CxError (Bknd.CxError c) |   -- |-  -- An operation, -  -- which requires a database transaction was executed without one.-  NotInTransaction |+  -- A backend-specific transaction error.+  -- It should cover all possible failures related to an established connection,+  -- including the loss of connection, query errors and database failures.+  TxError (Bknd.TxError c) |   -- |-  -- Attempt to parse a row into an incompatible type.+  -- Attempt to parse a result into an incompatible type.   -- Indicates either a mismatching schema or an incorrect query.-  UnparsableRow Text-  deriving (Show, Typeable, Eq, Ord)+  ResultError Text -instance Exception Error+deriving instance (Show (Bknd.CxError c), Show (Bknd.TxError c)) => Show (SessionError c)+deriving instance (Eq (Bknd.CxError c), Eq (Bknd.TxError c)) => Eq (SessionError c) +-- |+-- Execute a transaction in a session.+-- +-- This function ensures on the type level, +-- that it's impossible to return @'TxListT' s m r@ from it.+tx :: (Bknd.CxTx c, MonadBaseControl IO m) => Bknd.TxMode -> (forall s. Tx c s r) -> Session c m r+tx mode (Tx m) =+  Session $ ReaderT $ \(Pool pool) ->+    Pool.withResource pool $ \e -> do+      c <- hoistEither $ mapLeft CxError e+      let+        attempt =+          do+            r <- EitherT $ liftBase $ fmap (either (Left . TxError) Right) $ +                 Bknd.runTx c mode $ runEitherT m+            maybe attempt hoistEither r+        in attempt --- * Transaction++-- * Statements execution -------------------------  -- |--- A transaction specialized for a backend @b@, --- associated with its intermediate results using an anonymous type-argument @s@ (same as in 'ST')--- and producing a result @r@.-newtype Tx b s r =-  Tx (ReaderT (Backend.Connection b) IO r)-  deriving (Functor, Applicative, Monad)+-- Execute a statement without processing the result.+unitTx :: Bknd.Stmt c -> Tx c s ()+unitTx = +  Tx . lift . Bknd.unitTx  -- |--- A transaction mode defining how a transaction should be executed.+-- Execute a statement and count the amount of affected rows.+-- Useful for resolving how many rows were updated or deleted.+countTx :: Bknd.CxValue c Word64 => Bknd.Stmt c -> Tx c s Word64+countTx =+  Tx . lift . Bknd.countTx++-- |+-- Execute a statement,+-- which produces exactly one result row.+-- E.g., @INSERT@, which returns an autoincremented identifier,+-- or @SELECT COUNT@, or @SELECT EXISTS@. -- --- * @Just (isolationLevel, write)@ indicates that a database transaction--- should be established with a specified isolation level and a boolean, --- defining, whether it would perform any modification operations.+-- Please note that using this executor for selecting rows is conceptually wrong, +-- since in that case the results are always optional. +-- Use 'maybeTx', 'listTx' or 'vectorTx' instead. -- --- * @Nothing@ indicates that there should be no database transaction established on--- the backend and therefore it should be executed with no ACID guarantees,--- but also without any induced overhead.-type Mode =-  Maybe (Backend.IsolationLevel, Bool)+-- If the result is empty this executor will raise 'ResultError'.+singleTx :: RowParser.RowParser c r => Bknd.Stmt c -> Tx c s r+singleTx =+  join . fmap (maybe (Tx $ left $ ResultError "No rows on 'singleTx'") return) .+  maybeTx  -- |--- Execute a transaction in a session.+-- Execute a statement,+-- which optionally produces a single result row.+maybeTx :: RowParser.RowParser c r => Bknd.Stmt c -> Tx c s (Maybe r)+maybeTx =+  fmap (fmap Vector.unsafeHead . mfilter (not . Vector.null) . Just) . vectorTx++-- |+-- Execute a statement,+-- and produce a list of results.+listTx :: RowParser.RowParser c r => Bknd.Stmt c -> Tx c s [r]+listTx =+  fmap toList . vectorTx++-- |+-- Execute a statement,+-- and produce a vector of results.+vectorTx :: RowParser.RowParser c r => Bknd.Stmt c -> Tx c s (Vector r)+vectorTx s =+  Tx $ do+    r <- lift $ Bknd.vectorTx s+    EitherT $ return $ traverse ((mapLeft ResultError) . RowParser.parseRow) $ r++-- |+-- Execute a @SELECT@ statement with a cursor,+-- and produce a result stream. -- --- This function ensures on the type level, --- that it's impossible to return @'TxListT' s m r@ from it.-tx :: -  (Backend.Backend b, MonadBase IO m) =>-  Mode -> (forall s. Tx b s r) -> Session b s m r-tx m t =-  Session $ ReaderT $ \p -> liftBase $ Pool.withResource p $ \c -> runTx c m t-  where-    runTx ::-      Backend b => -      Backend.Connection b -> Mode -> (forall s. Tx b s r) -> IO r-    runTx connection mode (Tx reader) =-      maybe (const id) inTransaction mode connection (runReaderT reader connection)-      where-        inTransaction ::-          Backend b => -          Backend.TransactionMode -> Backend.Connection b -> IO r -> IO r-        inTransaction mode c io =-          let -            io' = -              Backend.beginTransaction mode c *> io <* Backend.finishTransaction True c-            in-              try io' >>= \case-                Left Backend.TransactionConflict -> do-                  Backend.finishTransaction False c-                  inTransaction mode c io-                Left e -> throwIO e-                Right r -> return r+-- Cursor allows you to fetch virtually limitless results in a constant memory+-- at a cost of a small overhead.+-- Note that in most databases cursors require establishing a database transaction,+-- so depending on a backend the transaction may result in an error,+-- if you run it improperly.+streamTx :: RowParser.RowParser c r => Bknd.Stmt c -> Tx c s (TxListT s (Tx c s) r)+streamTx s =+  Tx $ do+    r <- lift $ Bknd.streamTx s+    return $ TxListT $ do+      row <- hoist (Tx . lift) r+      lift $ Tx $ EitherT $ return $ mapLeft ResultError $ RowParser.parseRow $ row  --- * Results Stream+-- * Result Stream -------------------------  -- |@@ -273,7 +324,7 @@ -- which uses the same trick as the 'ST' monad to associate with the -- context transaction and become impossible to be used outside of it. -- This lets the library ensure that it is safe to automatically--- release all the resources associated with this stream.+-- release all the connections associated with this stream. --  -- All the functions of the \"list-t\" library are applicable to this type, -- amongst which are 'ListT.head', 'ListT.toList', 'ListT.fold', 'ListT.traverse_'.@@ -289,72 +340,21 @@     (unsafeCoerce :: TxListT s m r -> ListT.ListT m r)  --- * Statements execution------------------------------ |--- Execute a statement, which produces no result.-unit :: Backend b => Backend.Statement b -> Tx b s ()-unit s =-  Tx $ ReaderT $ Backend.execute s---- |--- Execute a statement and count the amount of affected rows.--- Useful for resolving how many rows were updated or deleted.-count :: (Backend b, Backend.Mapping b Word64) => Backend.Statement b -> Tx b s Word64-count s =-  Tx $ ReaderT $ Backend.executeAndCountEffects s---- |--- Execute a statement,--- which produces a single result row: --- a @SELECT@ --- or an @INSERT@, which produces a generated value (e.g., an auto-incremented id).-single :: (Backend b, RowParser b r) => Backend.Statement b -> Tx b s (Maybe r)-single s =-  headMay <$> list s---- |--- Execute a @SELECT@ statement,--- and produce a list of results.-list :: (Backend b, RowParser b r) => Backend.Statement b -> Tx b s [r]-list s =-  Tx $ ReaderT $ \c -> do-    m <- Backend.executeAndGetMatrix s c-    traverse (either (throwIO . UnparsableRow) return . RowParser.parseRow) $ Vector.toList m---- |--- Execute a @SELECT@ statement with a cursor,--- and produce a results stream.--- --- Cursor allows you to fetch virtually limitless results in a constant memory--- at a cost of a small overhead.--- Note that in most databases cursors require establishing a database transaction,--- so a 'NotInTransaction' error will be raised if you run it improperly.-stream :: (Backend b, RowParser b r) => Backend.Statement b -> TxListT s (Tx b s) r-stream s =-  do-    s <- lift $ Tx $ ReaderT $ \c -> Backend.executeAndStream s c-    TxListT $ hoist (Tx . lift) $ do-      row <- s-      either (lift . throwIO . UnparsableRow) return $ RowParser.parseRow row-- -- * Statements quasi-quotation -------------------------  -- | -- Produces a lambda-expression,  -- which takes as many parameters as there are placeholders in the quoted text--- and results in a 'Backend.Statement'. +-- and results in a 'Bknd.Stmt'.  --  -- E.g.: -- --- >selectFive :: Statement b--- >selectFive = [q|SELECT (? + ?)|] 2 3+-- >selectSum :: Int -> Int -> Stmt c+-- >selectSum = [stmt|SELECT (? + ?)|] -- -q :: TH.QuasiQuoter-q = +stmt :: TH.QuasiQuoter+stmt =    TH.QuasiQuoter     (parseExp)     (const $ fail "Pattern context is not supported")@@ -363,19 +363,18 @@   where     parseExp s =       do-        n <- either (fail . showString "Parsing failure: ") return (QParser.parse (fromString s))-        return $ statementF s n+        (t, n) <- either (fail . showString "Parsing failure: ") return (QParser.parse (fromString s))+        return $ statementF (Text.unpack t) (fromIntegral n)     statementF s n =       TH.LamE         (map TH.VarP argNames)-        (THUtil.purify [|(,,) $(pure statementE) $(pure argsE) True|])+        (THUtil.purify [|Bknd.Stmt $(pure statementE) $(pure argsE) True|])       where         argNames =            map (TH.mkName . ('_' :) . show) [1 .. n]         statementE =            TH.LitE (TH.StringL s)-        argsE = -          TH.ListE $ flip map argNames $ \x ->-            THUtil.purify-            [| Backend.renderValue $(TH.varE x) |]-        +        argsE =+          THUtil.vectorE $+          map (\x -> THUtil.purify [| Bknd.encodeValue $(TH.varE x) |]) $+          argNames
library/Hasql/Prelude.hs view
@@ -1,40 +1,44 @@ module Hasql.Prelude (    module Exports,-  bug,-  bottom, ) where   -- base-prelude --------------------------import BasePrelude as Exports+import BasePrelude as Exports hiding (left, right, isLeft, isRight)  -- transformers --------------------------import Control.Monad.Trans.State.Strict as Exports hiding (liftCallCC, liftCatch)-import Control.Monad.Trans.Reader as Exports hiding (liftCallCC, liftCatch)+import Control.Monad.Trans.State.Strict as Exports hiding (liftCallCC, liftCatch, liftListen, liftPass)+import Control.Monad.Trans.Reader as Exports hiding (liftCallCC, liftCatch, liftListen, liftPass)+import Control.Monad.Trans.Maybe as Exports hiding (liftCallCC, liftCatch, liftListen, liftPass) import Control.Monad.Trans.Class as Exports import Control.Monad.IO.Class as Exports import Data.Functor.Identity as Exports --- mmorph+-- transformers-base --------------------------import Control.Monad.Morph as Exports+import Control.Monad.Base as Exports  -- monad-control ------------------------- import Control.Monad.Trans.Control as Exports hiding (embed, embed_) --- transformers-base+-- mtl --------------------------import Control.Monad.Base as Exports+import Control.Monad.Error.Class as Exports hiding (Error) --- safe+-- mmorph --------------------------import Safe as Exports+import Control.Monad.Morph as Exports +-- either+-------------------------+import Control.Monad.Trans.Either as Exports+import Data.Either.Combinators as Exports+ -- list-t ------------------------- import ListT as Exports (ListT)@@ -46,26 +50,3 @@ -- text ------------------------- import Data.Text as Exports (Text)---- bytestring---------------------------import Data.ByteString as Exports (ByteString)---- time---------------------------import Data.Time as Exports---- placeholders---------------------------import Development.Placeholders as Exports---- custom---------------------------import qualified Debug.Trace.LocationTH---bug = [e| $(Debug.Trace.LocationTH.failure) . (msg <>) |]-  where-    msg = "A \"hasql\" package bug: " :: String--bottom = [e| $bug "Bottom evaluated" |]
library/Hasql/QParser.hs view
@@ -2,37 +2,49 @@  import Hasql.Prelude hiding (takeWhile) import Data.Attoparsec.Text hiding (Result)-import qualified Data.Text as Text+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Builder as TLB   -- |--- The amount of placeholders.-type Result =-  (Word)--parse :: Text -> Either String Result+-- Produces a whitespace-cleaned text and a count of placeholders in it.+parse :: Text -> Either String (Text, Int) parse = -  parseOnly $ flip execStateT 0 $-    statement *>-    (lift endOfInput <|> -     (void (lift (char ';')) <* fail "A semicolon detected. Only single statements are allowed"))-  where-    statement =-      skipMany1 $ -        void (lift stringLit) <|> -        void (lift (char '?') <* modify succ) <|>-        void (lift (notChar ';'))+  parseOnly $ singleTemplate +singleTemplate :: Parser (Text, Int)+singleTemplate =+  template <* +  ((endOfInput) <|>+   (() <$ skipSpace <* char ';' <* fail "A semicolon detected. Only single statements are allowed"))++template :: Parser (Text, Int)+template =+  flip runStateT 0 $ do+    lift $ skipSpace+    fmap (TL.toStrict . TLB.toLazyText . mconcat) $ +      many $ +        (mempty <$ lift trailingWhitespace) <|>+        (TLB.singleton ' ' <$ lift (takeWhile1 isSpace)) <|>+        (TLB.fromText <$> lift stringLit) <|>+        (TLB.singleton <$> lift (char '?') <* modify succ) <|>+        (TLB.singleton <$> lift (notChar ';'))++trailingWhitespace :: Parser ()+trailingWhitespace =+  () <$ takeWhile1 isSpace <* endOfInput+ stringLit :: Parser Text stringLit =   do     quote <-        char '"' <|> char '\''-    text <- +    content <-        fmap mconcat $ many $ -        string "\\\\" <|> -        string (fromString ['\\', quote]) <|> -        (Text.singleton <$> notChar quote)+        TLB.fromText <$> string "\\\\" <|> +        TLB.fromText <$> string (fromString ['\\', quote]) <|> +        TLB.singleton <$> notChar quote     char quote-    return text-+    return $ TL.toStrict . TLB.toLazyText $+      TLB.singleton quote <> content <> TLB.singleton quote
library/Hasql/RowParser.hs view
@@ -2,23 +2,26 @@  import Hasql.Prelude import Language.Haskell.TH-import qualified Hasql.Backend as Backend+import qualified Hasql.Backend as Bknd import qualified Data.Vector as Vector import qualified Hasql.TH as THUtil  -class RowParser b r where-  parseRow :: Vector.Vector (Backend.Result b) -> Either Text r+-- |+-- This class is only intended to be used with the supplied instances,+-- which should be enough to cover all use cases.+class RowParser c r where+  parseRow :: Bknd.ResultRow c -> Either Text r -instance RowParser b () where+instance RowParser c () where   parseRow row =      if Vector.null row       then Right ()       else Left "Not an empty row" -instance Backend.Mapping b v => RowParser b (Identity v) where+instance Bknd.CxValue c v => RowParser c (Identity v) where   parseRow row = do-    Identity <$> Backend.parseResult (Vector.unsafeHead row)+    Identity <$> Bknd.decodeValue (Vector.unsafeHead row)  -- Generate tuple instaces using Template Haskell: return $ flip map [2 .. 24] $ \arity ->@@ -28,9 +31,9 @@     varTypes =       map VarT varNames     connectionType =-      VarT (mkName "b")+      VarT (mkName "c")     constraints =-      map (\t -> ClassP ''Backend.Mapping [connectionType, t]) varTypes+      map (\t -> ClassP ''Bknd.CxValue [connectionType, t]) varTypes     head =       AppT (AppT (ConT ''RowParser) connectionType) (foldl AppT (TupleT arity) varTypes)     parseRowDec =@@ -54,7 +57,7 @@               i <- [0 .. pred arity]               return $ THUtil.purify $                 [|-                  Backend.parseResult+                  Bknd.decodeValue                     (Vector.unsafeIndex $(varE rowVarName) $(litE (IntegerL $ fromIntegral i)) )                 |]     in InstanceD constraints head [parseRowDec]
library/Hasql/TH.hs view
@@ -4,6 +4,8 @@  import Hasql.Prelude import Language.Haskell.TH+import qualified Data.Vector as Vector+import qualified Data.Vector.Mutable as MVector   applicativeE :: Exp -> [Exp] -> Exp@@ -18,7 +20,54 @@           \case             e : o : t -> UInfixE e o (reduce t)             e : [] -> e-            _ -> $bug $ "Unexpected queue size. Exps: " <> show exps+            _ -> error $ "Unexpected queue size. Exps: " <> show exps  purify :: Q a -> a purify = unsafePerformIO . runQ++-- |+-- Produce a lambda expression of a given arity,+-- which efficiently constructs a vector of a size equal to the arity.+vectorLamE :: Int -> Exp+vectorLamE arity =+  LamE (map VarP argNames) body+  where+    argNames = +      map (mkName . ('_' :) . show) [1 .. arity]+    body =+      vectorE $ map VarE argNames++vectorE :: [Exp] -> Exp+vectorE cellExps =+  if null cellExps+    then+      VarE 'Vector.empty+    else+      AppE (VarE 'runST) $ DoE $ +        pure vectorDeclarationStmt <> cellAssignmentStmts <> pure freezingStmt+  where+    vectorVarName =+      mkName "v"+    vectorDeclarationStmt =+      (BindS +        (VarP vectorVarName) +        (AppE +          (VarE 'MVector.unsafeNew) +          (LitE (IntegerL (fromIntegral (length cellExps))))))+    cellAssignmentStmts =+      map (NoBindS . uncurry cellAssignmentExp) $ zip [0..] cellExps+      where+        cellAssignmentExp index exp =+          (AppE+            (AppE+              (AppE+                (VarE 'MVector.unsafeWrite)+                (VarE vectorVarName))+              (LitE (IntegerL (fromIntegral index))))+            (exp))+    freezingStmt =+      (NoBindS+        (AppE+          (VarE 'Vector.unsafeFreeze)+          (VarE vectorVarName)))+