mirror of
https://github.com/nikita-volkov/hasql.git
synced 2024-11-25 17:26:28 +03:00
A complete overhaul
This commit is contained in:
parent
1dfed9be76
commit
39970138fa
12
CHANGELOG.md
12
CHANGELOG.md
@ -1,3 +1,15 @@
|
|||||||
|
# 0.6.0 - Major API overhaul
|
||||||
|
* Removed the `Session` monad, opening a direct access to the connection pool, thus providing for a simpler compatibility with other libraries.
|
||||||
|
* 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 uncommitable transactions with the `TxWriteMode` type.
|
||||||
|
* `Tx` now has a `MonadError` instance, which allows to handle errors while remaining in the transaction.
|
||||||
|
* All `Tx` functions now have a "Tx" suffix.
|
||||||
|
* There is no more `list` transaction. Instead there is `vectorTx`.
|
||||||
|
* 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.
|
||||||
|
|
||||||
# 0.5.0
|
# 0.5.0
|
||||||
* Update the "list-t" and "monad-control" deps
|
* Update the "list-t" and "monad-control" deps
|
||||||
|
|
||||||
|
42
hasql.cabal
42
hasql.cabal
@ -1,7 +1,7 @@
|
|||||||
name:
|
name:
|
||||||
hasql
|
hasql
|
||||||
version:
|
version:
|
||||||
0.5.0
|
0.6.0
|
||||||
synopsis:
|
synopsis:
|
||||||
A minimalistic general high level API for relational databases
|
A minimalistic general high level API for relational databases
|
||||||
description:
|
description:
|
||||||
@ -10,7 +10,7 @@ description:
|
|||||||
.
|
.
|
||||||
Features:
|
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.
|
boilerplate job for you.
|
||||||
.
|
.
|
||||||
* A powerful transaction abstraction, which provides
|
* A powerful transaction abstraction, which provides
|
||||||
@ -31,7 +31,7 @@ description:
|
|||||||
* Automated management of resources related to connections, transactions and
|
* Automated management of resources related to connections, transactions and
|
||||||
cursors.
|
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
|
* Compile-time generation of templates. You just can't write a statement with an
|
||||||
incorrect number of placeholders.
|
incorrect number of placeholders.
|
||||||
@ -94,27 +94,21 @@ library
|
|||||||
exposed-modules:
|
exposed-modules:
|
||||||
Hasql
|
Hasql
|
||||||
build-depends:
|
build-depends:
|
||||||
hasql-backend == 0.2.*,
|
--
|
||||||
-- template-haskell:
|
resource-pool == 0.2.*,
|
||||||
|
hasql-backend == 0.3.*,
|
||||||
|
--
|
||||||
template-haskell >= 2.8 && < 2.10,
|
template-haskell >= 2.8 && < 2.10,
|
||||||
-- parsing:
|
--
|
||||||
attoparsec >= 0.10 && < 0.13,
|
attoparsec >= 0.10 && < 0.13,
|
||||||
-- database:
|
--
|
||||||
ex-pool == 0.2.*,
|
|
||||||
-- data:
|
|
||||||
vector < 0.11,
|
vector < 0.11,
|
||||||
time >= 1.4 && < 1.6,
|
|
||||||
bytestring == 0.10.*,
|
|
||||||
text >= 1.0 && < 1.3,
|
text >= 1.0 && < 1.3,
|
||||||
-- errors:
|
--
|
||||||
loch-th == 0.2.*,
|
either >= 4.3 && < 4.4,
|
||||||
placeholders == 0.1.*,
|
list-t >= 0.3.1 && < 0.5,
|
||||||
-- general:
|
|
||||||
safe == 0.3.*,
|
|
||||||
list-t >= 0.4 && < 0.5,
|
|
||||||
mmorph == 1.0.*,
|
mmorph == 1.0.*,
|
||||||
monad-control == 1.0.*,
|
mtl >= 2.1 && < 2.3,
|
||||||
transformers-base == 0.4.*,
|
|
||||||
transformers >= 0.3 && < 0.5,
|
transformers >= 0.3 && < 0.5,
|
||||||
base-prelude >= 0.1.3 && < 0.2,
|
base-prelude >= 0.1.3 && < 0.2,
|
||||||
base >= 4.5 && < 4.8
|
base >= 4.5 && < 4.8
|
||||||
@ -138,10 +132,12 @@ test-suite hspec
|
|||||||
build-depends:
|
build-depends:
|
||||||
--
|
--
|
||||||
hasql,
|
hasql,
|
||||||
hasql-backend == 0.2.*,
|
hasql-backend,
|
||||||
--
|
--
|
||||||
hspec == 2.1.*,
|
hspec == 2.1.*,
|
||||||
--
|
--
|
||||||
|
vector,
|
||||||
|
--
|
||||||
mtl-prelude < 3,
|
mtl-prelude < 3,
|
||||||
base-prelude,
|
base-prelude,
|
||||||
base
|
base
|
||||||
@ -165,7 +161,7 @@ test-suite hspec-postgres
|
|||||||
build-depends:
|
build-depends:
|
||||||
--
|
--
|
||||||
hasql,
|
hasql,
|
||||||
hasql-postgres == 0.9.*,
|
hasql-postgres == 0.10.*,
|
||||||
--
|
--
|
||||||
slave-thread == 0.1.*,
|
slave-thread == 0.1.*,
|
||||||
--
|
--
|
||||||
@ -173,6 +169,8 @@ test-suite hspec-postgres
|
|||||||
--
|
--
|
||||||
text,
|
text,
|
||||||
--
|
--
|
||||||
|
monad-control,
|
||||||
|
either,
|
||||||
mtl-prelude < 3,
|
mtl-prelude < 3,
|
||||||
base-prelude,
|
base-prelude,
|
||||||
base
|
base
|
||||||
@ -197,7 +195,7 @@ benchmark demo
|
|||||||
Haskell2010
|
Haskell2010
|
||||||
build-depends:
|
build-depends:
|
||||||
hasql,
|
hasql,
|
||||||
hasql-postgres == 0.9.*,
|
hasql-postgres == 0.10.*,
|
||||||
transformers >= 0.3 && < 0.5,
|
transformers >= 0.3 && < 0.5,
|
||||||
base >= 4.5 && < 4.8
|
base >= 4.5 && < 4.8
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import BasePrelude
|
import BasePrelude
|
||||||
import MTLPrelude
|
import MTLPrelude
|
||||||
|
import Control.Monad.Trans.Either
|
||||||
|
import Control.Monad.Trans.Control
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Hasql as H
|
import qualified Hasql as H
|
||||||
@ -15,17 +17,17 @@ main =
|
|||||||
context "Unhandled transaction conflict" $ do
|
context "Unhandled transaction conflict" $ do
|
||||||
|
|
||||||
it "should not be" $ do
|
it "should not be" $ do
|
||||||
session $ H.tx Nothing $ do
|
session $ tx Nothing $ do
|
||||||
H.unit [H.q|DROP TABLE IF EXISTS artist|]
|
H.unitTx [H.q|DROP TABLE IF EXISTS artist|]
|
||||||
H.unit [H.q|DROP TABLE IF EXISTS artist_union|]
|
H.unitTx [H.q|DROP TABLE IF EXISTS artist_union|]
|
||||||
H.unit $
|
H.unitTx $
|
||||||
[H.q|
|
[H.q|
|
||||||
CREATE TABLE "artist_union" (
|
CREATE TABLE "artist_union" (
|
||||||
"id" BIGSERIAL,
|
"id" BIGSERIAL,
|
||||||
PRIMARY KEY ("id")
|
PRIMARY KEY ("id")
|
||||||
)
|
)
|
||||||
|]
|
|]
|
||||||
H.unit $
|
H.unitTx $
|
||||||
[H.q|
|
[H.q|
|
||||||
CREATE TABLE "artist" (
|
CREATE TABLE "artist" (
|
||||||
"id" BIGSERIAL,
|
"id" BIGSERIAL,
|
||||||
@ -39,13 +41,13 @@ main =
|
|||||||
let
|
let
|
||||||
insertArtistUnion :: H.Tx HP.Postgres s Int64
|
insertArtistUnion :: H.Tx HP.Postgres s Int64
|
||||||
insertArtistUnion =
|
insertArtistUnion =
|
||||||
fmap (runIdentity . fromJust) $ H.single $
|
fmap (runIdentity . fromJust) $ H.maybeTx $
|
||||||
[H.q|
|
[H.q|
|
||||||
INSERT INTO artist_union DEFAULT VALUES RETURNING id
|
INSERT INTO artist_union DEFAULT VALUES RETURNING id
|
||||||
|]
|
|]
|
||||||
insertArtist :: Int64 -> [Text] -> H.Tx HP.Postgres s Int64
|
insertArtist :: Int64 -> [Text] -> H.Tx HP.Postgres s Int64
|
||||||
insertArtist unionID artistNames =
|
insertArtist unionID artistNames =
|
||||||
fmap (runIdentity . fromJust) $ H.single $
|
fmap (runIdentity . fromJust) $ H.maybeTx $
|
||||||
[H.q|
|
[H.q|
|
||||||
INSERT INTO artist
|
INSERT INTO artist
|
||||||
(artist_union_id,
|
(artist_union_id,
|
||||||
@ -58,7 +60,7 @@ main =
|
|||||||
process =
|
process =
|
||||||
SlaveThread.fork $ do
|
SlaveThread.fork $ do
|
||||||
session $ replicateM_ 100 $ do
|
session $ replicateM_ 100 $ do
|
||||||
H.tx (Just (H.Serializable, True)) $ do
|
tx (Just (H.Serializable, Just True)) $ do
|
||||||
unionID <- insertArtistUnion
|
unionID <- insertArtistUnion
|
||||||
insertArtist unionID ["a", "b", "c"]
|
insertArtist unionID ["a", "b", "c"]
|
||||||
signal
|
signal
|
||||||
@ -68,32 +70,25 @@ main =
|
|||||||
context "RowParser" $ do
|
context "RowParser" $ do
|
||||||
|
|
||||||
it "should fail on incorrect arity" $ do
|
it "should fail on incorrect arity" $ do
|
||||||
flip shouldThrow (\case H.UnparsableRow _ -> True; _ -> False) $
|
flip shouldSatisfy (\case Left (H.UnparsableResult _) -> True; _ -> False) =<< do
|
||||||
session $ do
|
session $ do
|
||||||
H.tx Nothing $ do
|
tx Nothing $ do
|
||||||
H.unit [H.q|DROP TABLE IF EXISTS data|]
|
H.unitTx [H.q|DROP TABLE IF EXISTS data|]
|
||||||
H.unit [H.q|CREATE TABLE data (
|
H.unitTx [H.q|CREATE TABLE data (
|
||||||
field1 DECIMAL NOT NULL,
|
field1 DECIMAL NOT NULL,
|
||||||
field2 BIGINT NOT NULL,
|
field2 BIGINT NOT NULL,
|
||||||
PRIMARY KEY (field1)
|
PRIMARY KEY (field1)
|
||||||
)|]
|
)|]
|
||||||
H.unit [H.q|INSERT INTO data (field1, field2) VALUES (0, 0)|]
|
H.unitTx [H.q|INSERT INTO data (field1, field2) VALUES (0, 0)|]
|
||||||
mrow :: Maybe (Double, Int64, String) <-
|
mrow :: Maybe (Double, Int64, String) <-
|
||||||
H.tx Nothing $
|
tx Nothing $
|
||||||
H.single $ [H.q|SELECT * FROM data|]
|
H.maybeTx $ [H.q|SELECT * FROM data|]
|
||||||
return ()
|
return ()
|
||||||
|
|
||||||
|
|
||||||
-- * Helpers
|
-- * 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 :: Int -> IO (IO (), IO ())
|
||||||
newBatchGate amount =
|
newBatchGate amount =
|
||||||
do
|
do
|
||||||
@ -102,3 +97,29 @@ newBatchGate amount =
|
|||||||
let signal = atomically $ readTVar counter >>= writeTVar counter . pred
|
let signal = atomically $ readTVar counter >>= writeTVar counter . pred
|
||||||
block = atomically $ readTVar counter >>= \x -> when (x > 0) retry
|
block = atomically $ readTVar counter >>= \x -> when (x > 0) retry
|
||||||
in (signal, block)
|
in (signal, block)
|
||||||
|
|
||||||
|
|
||||||
|
-- * Hasql utils
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
newtype HSession m r =
|
||||||
|
HSession (ReaderT (H.Pool HP.Postgres) (EitherT (H.TxError HP.Postgres) m) r)
|
||||||
|
deriving (Functor, Applicative, Monad, MonadIO)
|
||||||
|
|
||||||
|
instance MonadTrans HSession where
|
||||||
|
lift = HSession . lift . lift
|
||||||
|
|
||||||
|
tx :: MonadIO m => H.TxMode -> (forall s. H.Tx HP.Postgres s a) -> HSession m a
|
||||||
|
tx mode m =
|
||||||
|
HSession $ ReaderT $ \p -> EitherT $ liftIO $ H.tx p mode m
|
||||||
|
|
||||||
|
session :: MonadBaseControl IO m => HSession m r -> m (Either (H.TxError HP.Postgres) r)
|
||||||
|
session (HSession m) =
|
||||||
|
control $ \unlift -> do
|
||||||
|
p <- H.acquirePool backendSettings poolSettings
|
||||||
|
r <- unlift $ runEitherT $ runReaderT m p
|
||||||
|
H.releasePool p
|
||||||
|
return r
|
||||||
|
where
|
||||||
|
backendSettings = HP.ParamSettings "localhost" 5432 "postgres" "" "postgres"
|
||||||
|
poolSettings = fromJust $ H.poolSettings 6 30
|
||||||
|
@ -2,21 +2,25 @@ import BasePrelude
|
|||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
import qualified Hasql as H
|
import qualified Hasql as H
|
||||||
import qualified Hasql.Backend as HB
|
import qualified Hasql.Backend as HB
|
||||||
|
import qualified Data.Vector as V
|
||||||
|
|
||||||
|
|
||||||
data X
|
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 =
|
main =
|
||||||
hspec $ do
|
hspec $ do
|
||||||
context "Quasi quoter" $ do
|
context "Quasi quoter" $ do
|
||||||
it "generates a proper statement" $ do
|
it "generates a proper statement" $ do
|
||||||
(flip shouldBe)
|
(flip shouldBe)
|
||||||
(" SELECT ? ", [HB.renderValue 'a'], True)
|
(HB.Stmt " SELECT (? + ?) " (V.fromList [HB.encodeValue 'a', HB.encodeValue 'b']) True)
|
||||||
([H.q| SELECT ? |] 'a' :: HB.Statement X)
|
([H.q| SELECT (? + ?) |] 'a' 'b' :: HB.Stmt X)
|
||||||
|
431
library/Hasql.hs
431
library/Hasql.hs
@ -4,265 +4,236 @@
|
|||||||
-- For an introduction to the package
|
-- For an introduction to the package
|
||||||
-- and links to more documentation please refer to
|
-- and links to more documentation please refer to
|
||||||
-- <../ the package's index page>.
|
-- <../ 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
|
module Hasql
|
||||||
(
|
(
|
||||||
-- * Session
|
-- * Pool
|
||||||
Session,
|
Pool,
|
||||||
session,
|
acquirePool,
|
||||||
sessionUnlifter,
|
releasePool,
|
||||||
|
|
||||||
-- ** Session Settings
|
-- ** Pool Settings
|
||||||
SessionSettings,
|
PoolSettings,
|
||||||
sessionSettings,
|
poolSettings,
|
||||||
|
|
||||||
|
-- * Statement
|
||||||
|
Bknd.Stmt,
|
||||||
|
q,
|
||||||
|
|
||||||
|
-- ** Statement Execution
|
||||||
|
unitTx,
|
||||||
|
countTx,
|
||||||
|
maybeTx,
|
||||||
|
vectorTx,
|
||||||
|
streamTx,
|
||||||
|
|
||||||
-- * Transaction
|
-- * Transaction
|
||||||
Tx,
|
Tx,
|
||||||
tx,
|
tx,
|
||||||
|
|
||||||
-- ** Transaction Settings
|
-- ** Transaction Settings
|
||||||
Mode,
|
Bknd.TxMode(..),
|
||||||
Backend.IsolationLevel(..),
|
Bknd.TxIsolationLevel(..),
|
||||||
|
Bknd.TxWriteMode(..),
|
||||||
|
|
||||||
-- * Statement Quasi-Quoter
|
-- ** Transaction Error
|
||||||
q,
|
TxError(..),
|
||||||
|
|
||||||
-- * Statement Execution
|
-- ** Result Stream
|
||||||
unit,
|
|
||||||
count,
|
|
||||||
single,
|
|
||||||
list,
|
|
||||||
stream,
|
|
||||||
|
|
||||||
-- * Results Stream
|
|
||||||
TxListT,
|
TxListT,
|
||||||
|
|
||||||
-- * Row parser
|
-- * Row Parser
|
||||||
RowParser.RowParser(..),
|
RowParser.RowParser,
|
||||||
|
|
||||||
-- * Error
|
|
||||||
Error(..),
|
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Hasql.Prelude hiding (Error)
|
import Hasql.Prelude
|
||||||
import Hasql.Backend (Backend)
|
import qualified Hasql.Backend as Bknd
|
||||||
import Hasql.RowParser (RowParser)
|
|
||||||
import qualified Hasql.Backend as Backend
|
|
||||||
import qualified Hasql.RowParser as RowParser
|
import qualified Hasql.RowParser as RowParser
|
||||||
import qualified Hasql.QParser as QParser
|
import qualified Hasql.QParser as QParser
|
||||||
import qualified ListT
|
import qualified ListT
|
||||||
import qualified Data.Pool as Pool
|
import qualified Data.Pool as Pool
|
||||||
import qualified Data.Vector as Vector
|
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 as TH
|
||||||
import qualified Language.Haskell.TH.Quote as TH
|
import qualified Language.Haskell.TH.Quote as TH
|
||||||
import qualified Hasql.TH as THUtil
|
import qualified Hasql.TH as THUtil
|
||||||
|
|
||||||
|
|
||||||
-- * Session
|
-- * Resources
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
-- |
|
-- |
|
||||||
-- A monad transformer,
|
-- A connection pool.
|
||||||
-- which executes transactions.
|
newtype Pool c =
|
||||||
--
|
Pool (Pool.Pool (Either (Bknd.CxError c) c))
|
||||||
-- * @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,
|
-- Given backend-specific connection settings and pool settings,
|
||||||
-- execute it in the inner monad.
|
-- acquire a backend connection pool,
|
||||||
--
|
-- which can then be used to work with the DB.
|
||||||
-- It uses the same trick as 'ST' with the anonymous @s@ type argument
|
acquirePool :: Bknd.Cx c => Bknd.CxSettings c -> PoolSettings -> IO (Pool c)
|
||||||
-- to prohibit the use of the result of
|
acquirePool cxSettings (PoolSettings size timeout) =
|
||||||
-- 'sessionUnlifter' outside of its creator session.
|
fmap Pool $
|
||||||
session ::
|
Pool.createPool (Bknd.acquireCx cxSettings)
|
||||||
(Backend.Backend b, MonadBaseControl IO m) =>
|
(either (const $ return ()) Bknd.releaseCx)
|
||||||
b -> SessionSettings -> (forall s. Session b s m r) -> m r
|
(1)
|
||||||
session backend (SessionSettings size timeout) s =
|
(fromIntegral timeout)
|
||||||
control $ \runInIO ->
|
(size)
|
||||||
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
|
|
||||||
|
|
||||||
-- |
|
-- |
|
||||||
-- Get a session unlifting function,
|
-- Release all connections acquired by the pool.
|
||||||
-- which allows to execute a session in the inner monad
|
releasePool :: Pool c -> IO ()
|
||||||
-- using the resources of the current session.
|
releasePool (Pool p) =
|
||||||
--
|
Pool.destroyAllResources p
|
||||||
-- Using this function in combination with 'lift'
|
|
||||||
-- you can interleave 'Session' with other monad transformers.
|
|
||||||
--
|
|
||||||
-- This function has the following property:
|
|
||||||
--
|
|
||||||
-- > (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
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
-- ** Session Settings
|
-- ** Pool Settings
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
-- |
|
-- |
|
||||||
-- Settings of a session.
|
-- Settings of a pool.
|
||||||
data SessionSettings =
|
data PoolSettings =
|
||||||
SessionSettings !Word32 !NominalDiffTime
|
PoolSettings !Int !Int
|
||||||
|
|
||||||
-- |
|
-- |
|
||||||
-- A smart constructor for session settings.
|
-- A smart constructor for pool settings.
|
||||||
sessionSettings ::
|
poolSettings ::
|
||||||
Word32
|
Int
|
||||||
-- ^
|
-- ^
|
||||||
-- The maximum number of connections to keep open.
|
-- The maximum number of connections to keep open.
|
||||||
-- The smallest acceptable value is 1.
|
-- The smallest acceptable value is 1.
|
||||||
-- Requests for connections will block if this limit is reached.
|
-- 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 amount of seconds for which an unused connection is kept open.
|
||||||
-- The smallest acceptable value is 0.5 seconds.
|
-- The smallest acceptable value is 1.
|
||||||
->
|
->
|
||||||
Maybe SessionSettings
|
Maybe PoolSettings
|
||||||
-- ^
|
-- ^
|
||||||
-- Maybe session settings, if they are correct.
|
-- Maybe pool settings, if they are correct.
|
||||||
sessionSettings size timeout =
|
poolSettings size timeout =
|
||||||
if size > 0 && timeout >= 0.5
|
if size > 0 && timeout >= 1
|
||||||
then Just $ SessionSettings size timeout
|
then Just $ PoolSettings size timeout
|
||||||
else Nothing
|
else Nothing
|
||||||
|
|
||||||
|
|
||||||
-- ** Error
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
-- |
|
|
||||||
-- 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 |
|
|
||||||
-- |
|
|
||||||
-- Incorrect statement template.
|
|
||||||
UnparsableTemplate Text |
|
|
||||||
-- |
|
|
||||||
-- An operation,
|
|
||||||
-- which requires a database transaction was executed without one.
|
|
||||||
NotInTransaction |
|
|
||||||
-- |
|
|
||||||
-- Attempt to parse a row into an incompatible type.
|
|
||||||
-- Indicates either a mismatching schema or an incorrect query.
|
|
||||||
UnparsableRow Text
|
|
||||||
deriving (Show, Typeable, Eq, Ord)
|
|
||||||
|
|
||||||
instance Exception Error
|
|
||||||
|
|
||||||
|
|
||||||
-- * Transaction
|
-- * Transaction
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
-- |
|
-- |
|
||||||
-- A transaction specialized for a backend @b@,
|
-- A transaction specialized for a backend connection @c@,
|
||||||
-- associated with its intermediate results using an anonymous type-argument @s@ (same as in 'ST')
|
-- associated with its intermediate results using an anonymous type-argument @s@ (same trick as in 'ST')
|
||||||
-- and producing a result @r@.
|
-- and producing a result @r@.
|
||||||
newtype Tx b s r =
|
--
|
||||||
Tx (ReaderT (Backend.Connection b) IO r)
|
-- Running `IO` in `Tx` is prohibited.
|
||||||
deriving (Functor, Applicative, Monad)
|
-- 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 (EitherT (TxError c) (Bknd.Tx c) r)
|
||||||
|
deriving (Functor, Applicative, Monad, MonadError (TxError c))
|
||||||
|
|
||||||
|
data TxError c =
|
||||||
|
-- |
|
||||||
|
-- A backend-specific connection acquisition error.
|
||||||
|
-- E.g., a failure to establish a connection.
|
||||||
|
BackendCxError (Bknd.CxError c) |
|
||||||
|
-- |
|
||||||
|
-- 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.
|
||||||
|
BackendTxError (Bknd.TxError c) |
|
||||||
|
-- |
|
||||||
|
-- Attempt to parse a result into an incompatible type.
|
||||||
|
-- Indicates either a mismatching schema or an incorrect query.
|
||||||
|
UnparsableResult Text
|
||||||
|
|
||||||
|
deriving instance (Show (Bknd.CxError c), Show (Bknd.TxError c)) => Show (TxError c)
|
||||||
|
deriving instance (Eq (Bknd.CxError c), Eq (Bknd.TxError c)) => Eq (TxError c)
|
||||||
|
|
||||||
-- |
|
-- |
|
||||||
-- A transaction mode defining how a transaction should be executed.
|
-- Execute a transaction on a connection pool.
|
||||||
--
|
|
||||||
-- * @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.
|
|
||||||
--
|
|
||||||
-- * @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)
|
|
||||||
|
|
||||||
-- |
|
|
||||||
-- Execute a transaction in a session.
|
|
||||||
--
|
--
|
||||||
-- This function ensures on the type level,
|
-- This function ensures on the type level,
|
||||||
-- that it's impossible to return @'TxListT' s m r@ from it.
|
-- that it's impossible to return @'TxListT' s m r@ from it.
|
||||||
tx ::
|
tx :: Bknd.CxTx c => Pool c -> Bknd.TxMode -> (forall s. Tx c s r) -> IO (Either (TxError c) r)
|
||||||
(Backend.Backend b, MonadBase IO m) =>
|
tx (Pool pool) mode (Tx m) =
|
||||||
Mode -> (forall s. Tx b s r) -> Session b s m r
|
Pool.withResource pool $ \e ->
|
||||||
tx m t =
|
runEitherT $ do
|
||||||
Session $ ReaderT $ \p -> liftBase $ Pool.withResource p $ \c -> runTx c m t
|
c <- hoistEither $ mapLeft BackendCxError e
|
||||||
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
|
let
|
||||||
io' =
|
attempt =
|
||||||
Backend.beginTransaction mode c *> io <* Backend.finishTransaction True c
|
do
|
||||||
in
|
r <- EitherT $ fmap (either (Left . BackendTxError) Right) $
|
||||||
try io' >>= \case
|
Bknd.runTx c mode $ runEitherT m
|
||||||
Left Backend.TransactionConflict -> do
|
maybe attempt hoistEither r
|
||||||
Backend.finishTransaction False c
|
in attempt
|
||||||
inTransaction mode c io
|
|
||||||
Left e -> throwIO e
|
|
||||||
Right r -> return r
|
|
||||||
|
|
||||||
|
|
||||||
-- * Results Stream
|
-- * Statements execution
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
-- |
|
||||||
|
-- Execute a statement without processing the result.
|
||||||
|
unitTx :: Bknd.Stmt c -> Tx c s ()
|
||||||
|
unitTx =
|
||||||
|
Tx . lift . Bknd.unitTx
|
||||||
|
|
||||||
|
-- |
|
||||||
|
-- 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 a single result row.
|
||||||
|
-- E.g.,
|
||||||
|
-- a @SELECT@
|
||||||
|
-- or an @INSERT@, which produces a generated value (e.g., an auto-incremented id),
|
||||||
|
-- or an @UPDATE@ or @DELETE@, counting the affected rows.
|
||||||
|
maybeTx :: RowParser.RowParser c r => Bknd.Stmt c -> Tx c s (Maybe r)
|
||||||
|
maybeTx s =
|
||||||
|
Tx $ do
|
||||||
|
r <- lift $ Bknd.maybeTx s
|
||||||
|
EitherT $ return $ traverse ((mapLeft UnparsableResult) . RowParser.parseRow) $ r
|
||||||
|
|
||||||
|
-- |
|
||||||
|
-- Execute a @SELECT@ 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 UnparsableResult) . RowParser.parseRow) $ r
|
||||||
|
|
||||||
|
-- |
|
||||||
|
-- Execute a @SELECT@ statement with a cursor,
|
||||||
|
-- and produce a result 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 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 UnparsableResult $ RowParser.parseRow $ row
|
||||||
|
|
||||||
|
|
||||||
|
-- * Result Stream
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
-- |
|
-- |
|
||||||
@ -273,7 +244,7 @@ tx m t =
|
|||||||
-- which uses the same trick as the 'ST' monad to associate with the
|
-- which uses the same trick as the 'ST' monad to associate with the
|
||||||
-- context transaction and become impossible to be used outside of it.
|
-- context transaction and become impossible to be used outside of it.
|
||||||
-- This lets the library ensure that it is safe to automatically
|
-- 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,
|
-- All the functions of the \"list-t\" library are applicable to this type,
|
||||||
-- amongst which are 'ListT.head', 'ListT.toList', 'ListT.fold', 'ListT.traverse_'.
|
-- amongst which are 'ListT.head', 'ListT.toList', 'ListT.fold', 'ListT.traverse_'.
|
||||||
@ -289,69 +260,18 @@ instance ListT.MonadTransUncons (TxListT s) where
|
|||||||
(unsafeCoerce :: TxListT s m r -> ListT.ListT m r)
|
(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
|
-- * Statements quasi-quotation
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
-- |
|
-- |
|
||||||
-- Produces a lambda-expression,
|
-- Produces a lambda-expression,
|
||||||
-- which takes as many parameters as there are placeholders in the quoted text
|
-- 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.:
|
-- E.g.:
|
||||||
--
|
--
|
||||||
-- >selectFive :: Statement b
|
-- >selectSum :: Int -> Int -> Stmt c
|
||||||
-- >selectFive = [q|SELECT (? + ?)|] 2 3
|
-- >selectSum = [q|SELECT (? + ?)|]
|
||||||
--
|
--
|
||||||
q :: TH.QuasiQuoter
|
q :: TH.QuasiQuoter
|
||||||
q =
|
q =
|
||||||
@ -364,18 +284,17 @@ q =
|
|||||||
parseExp s =
|
parseExp s =
|
||||||
do
|
do
|
||||||
n <- either (fail . showString "Parsing failure: ") return (QParser.parse (fromString s))
|
n <- either (fail . showString "Parsing failure: ") return (QParser.parse (fromString s))
|
||||||
return $ statementF s n
|
return $ statementF s (fromIntegral n)
|
||||||
statementF s n =
|
statementF s n =
|
||||||
TH.LamE
|
TH.LamE
|
||||||
(map TH.VarP argNames)
|
(map TH.VarP argNames)
|
||||||
(THUtil.purify [|(,,) $(pure statementE) $(pure argsE) True|])
|
(THUtil.purify [|Bknd.Stmt $(pure statementE) $(pure argsE) True|])
|
||||||
where
|
where
|
||||||
argNames =
|
argNames =
|
||||||
map (TH.mkName . ('_' :) . show) [1 .. n]
|
map (TH.mkName . ('_' :) . show) [1 .. n]
|
||||||
statementE =
|
statementE =
|
||||||
TH.LitE (TH.StringL s)
|
TH.LitE (TH.StringL s)
|
||||||
argsE =
|
argsE =
|
||||||
TH.ListE $ flip map argNames $ \x ->
|
THUtil.vectorE $
|
||||||
THUtil.purify
|
map (\x -> THUtil.purify [| Bknd.encodeValue $(TH.varE x) |]) $
|
||||||
[| Backend.renderValue $(TH.varE x) |]
|
argNames
|
||||||
|
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
module Hasql.Prelude
|
module Hasql.Prelude
|
||||||
(
|
(
|
||||||
module Exports,
|
module Exports,
|
||||||
bug,
|
|
||||||
bottom,
|
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
|
||||||
-- base-prelude
|
-- base-prelude
|
||||||
-------------------------
|
-------------------------
|
||||||
import BasePrelude as Exports
|
import BasePrelude as Exports hiding (left, right, isLeft, isRight)
|
||||||
|
|
||||||
-- transformers
|
-- transformers
|
||||||
-------------------------
|
-------------------------
|
||||||
@ -19,21 +17,18 @@ import Control.Monad.Trans.Class as Exports
|
|||||||
import Control.Monad.IO.Class as Exports
|
import Control.Monad.IO.Class as Exports
|
||||||
import Data.Functor.Identity as Exports
|
import Data.Functor.Identity as Exports
|
||||||
|
|
||||||
|
-- mtl
|
||||||
|
-------------------------
|
||||||
|
import Control.Monad.Error.Class as Exports
|
||||||
|
|
||||||
-- mmorph
|
-- mmorph
|
||||||
-------------------------
|
-------------------------
|
||||||
import Control.Monad.Morph as Exports
|
import Control.Monad.Morph as Exports
|
||||||
|
|
||||||
-- monad-control
|
-- either
|
||||||
-------------------------
|
-------------------------
|
||||||
import Control.Monad.Trans.Control as Exports hiding (embed, embed_)
|
import Control.Monad.Trans.Either as Exports
|
||||||
|
import Data.Either.Combinators as Exports
|
||||||
-- transformers-base
|
|
||||||
-------------------------
|
|
||||||
import Control.Monad.Base as Exports
|
|
||||||
|
|
||||||
-- safe
|
|
||||||
-------------------------
|
|
||||||
import Safe as Exports
|
|
||||||
|
|
||||||
-- list-t
|
-- list-t
|
||||||
-------------------------
|
-------------------------
|
||||||
@ -46,26 +41,3 @@ import Data.Vector as Exports (Vector)
|
|||||||
-- text
|
-- text
|
||||||
-------------------------
|
-------------------------
|
||||||
import Data.Text as Exports (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" |]
|
|
||||||
|
@ -2,23 +2,26 @@ module Hasql.RowParser where
|
|||||||
|
|
||||||
import Hasql.Prelude
|
import Hasql.Prelude
|
||||||
import Language.Haskell.TH
|
import Language.Haskell.TH
|
||||||
import qualified Hasql.Backend as Backend
|
import qualified Hasql.Backend as Bknd
|
||||||
import qualified Data.Vector as Vector
|
import qualified Data.Vector as Vector
|
||||||
import qualified Hasql.TH as THUtil
|
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 =
|
parseRow row =
|
||||||
if Vector.null row
|
if Vector.null row
|
||||||
then Right ()
|
then Right ()
|
||||||
else Left "Not an empty row"
|
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
|
parseRow row = do
|
||||||
Identity <$> Backend.parseResult (Vector.unsafeHead row)
|
Identity <$> Bknd.decodeValue (Vector.unsafeHead row)
|
||||||
|
|
||||||
-- Generate tuple instaces using Template Haskell:
|
-- Generate tuple instaces using Template Haskell:
|
||||||
return $ flip map [2 .. 24] $ \arity ->
|
return $ flip map [2 .. 24] $ \arity ->
|
||||||
@ -28,9 +31,9 @@ return $ flip map [2 .. 24] $ \arity ->
|
|||||||
varTypes =
|
varTypes =
|
||||||
map VarT varNames
|
map VarT varNames
|
||||||
connectionType =
|
connectionType =
|
||||||
VarT (mkName "b")
|
VarT (mkName "c")
|
||||||
constraints =
|
constraints =
|
||||||
map (\t -> ClassP ''Backend.Mapping [connectionType, t]) varTypes
|
map (\t -> ClassP ''Bknd.CxValue [connectionType, t]) varTypes
|
||||||
head =
|
head =
|
||||||
AppT (AppT (ConT ''RowParser) connectionType) (foldl AppT (TupleT arity) varTypes)
|
AppT (AppT (ConT ''RowParser) connectionType) (foldl AppT (TupleT arity) varTypes)
|
||||||
parseRowDec =
|
parseRowDec =
|
||||||
@ -54,7 +57,7 @@ return $ flip map [2 .. 24] $ \arity ->
|
|||||||
i <- [0 .. pred arity]
|
i <- [0 .. pred arity]
|
||||||
return $ THUtil.purify $
|
return $ THUtil.purify $
|
||||||
[|
|
[|
|
||||||
Backend.parseResult
|
Bknd.decodeValue
|
||||||
(Vector.unsafeIndex $(varE rowVarName) $(litE (IntegerL $ fromIntegral i)) )
|
(Vector.unsafeIndex $(varE rowVarName) $(litE (IntegerL $ fromIntegral i)) )
|
||||||
|]
|
|]
|
||||||
in InstanceD constraints head [parseRowDec]
|
in InstanceD constraints head [parseRowDec]
|
||||||
|
@ -4,6 +4,8 @@ module Hasql.TH where
|
|||||||
|
|
||||||
import Hasql.Prelude
|
import Hasql.Prelude
|
||||||
import Language.Haskell.TH
|
import Language.Haskell.TH
|
||||||
|
import qualified Data.Vector as Vector
|
||||||
|
import qualified Data.Vector.Mutable as MVector
|
||||||
|
|
||||||
|
|
||||||
applicativeE :: Exp -> [Exp] -> Exp
|
applicativeE :: Exp -> [Exp] -> Exp
|
||||||
@ -18,7 +20,50 @@ applicativeE head =
|
|||||||
\case
|
\case
|
||||||
e : o : t -> UInfixE e o (reduce t)
|
e : o : t -> UInfixE e o (reduce t)
|
||||||
e : [] -> e
|
e : [] -> e
|
||||||
_ -> $bug $ "Unexpected queue size. Exps: " <> show exps
|
_ -> error $ "Unexpected queue size. Exps: " <> show exps
|
||||||
|
|
||||||
purify :: Q a -> a
|
purify :: Q a -> a
|
||||||
purify = unsafePerformIO . runQ
|
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 =
|
||||||
|
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)))
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user