packages feed

hasql (empty) → 0.1.0

raw patch · 8 files changed

+618/−0 lines, 8 filesdep +attoparsecdep +basedep +base-preludesetup-changed

Dependencies added: attoparsec, base, base-prelude, bytestring, ex-pool, hasql-backend, list-t, loch-th, mmorph, monad-control, mtl-prelude, placeholders, safe, template-haskell, text, time, transformers-base, vector

Files

+ LICENSE view
@@ -0,0 +1,22 @@+Copyright (c) 2014, Nikita Volkov++Permission is hereby granted, free of charge, to any person+obtaining a copy of this software and associated documentation+files (the "Software"), to deal in the Software without+restriction, including without limitation the rights to use,+copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the+Software is furnished to do so, subject to the following+conditions:++The above copyright notice and this permission notice shall be+included in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR+OTHER DEALINGS IN THE SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ hasql.cabal view
@@ -0,0 +1,101 @@+name:+  hasql+version:+  0.1.0+synopsis:+  A minimalistic general high level API for relational databases+description:+  A robust and concise yet powerful API for communication with arbitrary+  relational databases. Features:+  .+  * Concise and crisp API. Just a few functions and two monads doing all the+  boilerplate job for you.+  .+  * Automated management of resources related to connections, transactions and+  cursors.+  .+  * A built-in connections pool.+  .+  * Employment of prepared statements. Every statement you emit gets prepared+  and cached. This raises the performance of the backend.+  .+  * Support for cursors. Allows to fetch virtually limitless result sets in a+  constant memory using streaming.+  .+  * Type-level generation of templates. You just can't write a statement with an+  incorrect number of placeholders.+  .+  * Mapping to any types actually supported by the backend.+category:+  Database+homepage:+  https://github.com/nikita-volkov/hasql +bug-reports:+  https://github.com/nikita-volkov/hasql/issues +author:+  Nikita Volkov <nikita.y.volkov@mail.ru>+maintainer:+  Nikita Volkov <nikita.y.volkov@mail.ru>+copyright:+  (c) 2014, Nikita Volkov+license:+  MIT+license-file:+  LICENSE+build-type:+  Simple+cabal-version:+  >=1.10+++source-repository head+  type:+    git+  location:+    git://github.com/nikita-volkov/hasql.git+++library+  hs-source-dirs:+    library+  other-modules:+    Hasql.Prelude+    Hasql.QQ+    Hasql.QQ.Parser+    Hasql.RowParser+  exposed-modules:+    Hasql+  build-depends:+    hasql-backend == 0.1.*,+    -- template-haskell:+    template-haskell >= 2.8 && < 2.10,+    -- parsing:+    attoparsec == 0.12.*,+    -- database:+    ex-pool == 0.2.*,+    -- data:+    vector < 0.11,+    time >= 1.4 && < 1.6,+    bytestring == 0.10.*,+    text >= 1.1 && < 1.3,+    -- control:+    list-t >= 0.2.4 && < 0.3,+    monad-control == 0.3.*,+    transformers-base == 0.4.*,+    -- errors:+    loch-th == 0.2.*,+    placeholders == 0.1.*,+    -- general:+    monad-control == 0.3.*,+    transformers-base == 0.4.*,+    safe >= 0.3.8 && < 0.4,+    mmorph == 1.0.*,+    mtl-prelude == 2.*,+    base-prelude >= 0.1.3 && < 0.2,+    base >= 4.5 && < 4.8+  ghc-options:+    -funbox-strict-fields+  default-extensions:+    Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFunctor, DeriveGeneric, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, ImpredicativeTypes, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples+  default-language:+    Haskell2010
+ library/Hasql.hs view
@@ -0,0 +1,277 @@+module Hasql+(+  -- * Session+  Session,+  session,++  -- ** Session Settings+  SessionSettings,+  sessionSettings,++  -- ** Error+  Error(..),++  -- * Transaction+  Tx,+  Mode,+  Backend.IsolationLevel(..),+  tx,++  -- * Statement Quasi-Quoter+  QQ.q,++  -- * Statement Execution+  unit,+  count,+  single,+  list,+  stream,++  -- * Results Stream+  TxListT,++  -- * 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 qualified Hasql.RowParser as RowParser+import qualified Hasql.QQ as QQ+import qualified ListT+import qualified Data.Pool as Pool+import qualified Data.Vector as Vector+++-- * Session+-------------------------++-- |+-- A monad transformer,+-- which executes transactions.+type Session b =+  ReaderT (Pool.Pool (Backend.Connection b))++-- |+-- Given backend settings, session settings, and a session monad transformer,+-- execute it in the inner monad.+session :: +  Backend.Backend b => MonadBaseControl IO m =>+  b -> SessionSettings -> Session b m r -> m r+session backend (SessionSettings size timeout) reader =+  join $ liftM restoreM $ liftBaseWith $ \runInIO ->+    mask $ \unmask -> do+      p <- Pool.createPool (Backend.connect backend) Backend.disconnect 1 timeout size+      r <- try $ unmask $ runInIO $ runReaderT reader p+      Pool.purgePool p+      either onException return r+  where+    onException =+      \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+-------------------------++-- |+-- Settings of a session.+data SessionSettings =+  SessionSettings !Word32 !NominalDiffTime++-- | +-- A smart constructor for session settings.+sessionSettings :: +  Word32+  -- ^+  -- 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+  -- ^+  -- The amount of time for which an unused connection is kept open. +  -- The smallest acceptable value is 0.5 seconds.+  -> +  Maybe SessionSettings+  -- ^+  -- Maybe session settings, if they are correct.+sessionSettings size timeout =+  if size > 0 && timeout >= 0.5+    then Just $ SessionSettings size timeout+    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)++instance Exception Error+++-- * Transaction+-------------------------++-- |+-- A transaction specialized for backend @b@, +-- running on an anonymous state-thread @s@ +-- and producing a result @r@.+newtype Tx b s r =+  Tx (ReaderT (Backend.Connection b) IO r)+  deriving (Functor, Applicative, Monad)++-- |+-- A transaction mode defining how a transaction should be executed.+-- +-- * @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.+tx :: +  Backend.Backend b => MonadBase IO m =>+  Mode -> (forall s. Tx b s r) -> Session b m r+tx m t =+  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 =+          do+            Backend.beginTransaction mode c+            try io >>= \case+              Left Backend.TransactionConflict -> do+                Backend.finishTransaction False c+                inTransaction mode c io+              Left e -> throwIO e+              Right r -> do+                Backend.finishTransaction True c+                return r+++-- * Results Stream+-------------------------++-- |+-- A stream of results, +-- which fetches only those that you reach.+-- +-- It's a wrapper around 'ListT.ListT', +-- 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.+-- +-- All the functions of the \"list-t\" library are applicable to this type,+-- amongst which are 'ListT.head', 'ListT.toList', 'ListT.fold', 'ListT.traverse_'.+newtype TxListT s m r =+  TxListT (ListT.ListT m r)+  deriving (Functor, Applicative, Alternative, Monad, MonadTrans, MonadPlus, +            Monoid, ListT.ListMonad)++instance ListT.ListTrans (TxListT s) where+  uncons = +    unsafeCoerce +      (ListT.uncons :: ListT.ListT m r -> m (Maybe (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 vector 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
+ library/Hasql/Prelude.hs view
@@ -0,0 +1,67 @@+module Hasql.Prelude+( +  module Exports,+  bug,+  bottom,+)+where+++-- base-prelude+-------------------------+import BasePrelude as Exports++-- mtl-prelude+-------------------------+import MTLPrelude as Exports hiding (shift)++-- mmorph+-------------------------+import Control.Monad.Morph as Exports++-- monad-control+-------------------------+import Control.Monad.Trans.Control as Exports++-- transformers-base+-------------------------+import Control.Monad.Base as Exports++-- safe+-------------------------+import Safe as Exports++-- list-t+-------------------------+import ListT as Exports (ListT)++-- vector+-------------------------+import Data.Vector as Exports (Vector)++-- 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/QQ.hs view
@@ -0,0 +1,47 @@+module Hasql.QQ where++import Hasql.Prelude+import Language.Haskell.TH+import Language.Haskell.TH.Quote+import qualified Hasql.QQ.Parser as Parser+import qualified Hasql.Backend as Backend+++-- |+-- Produces a lambda-expression, +-- which takes as many parameters as there are placeholders in the quoted text+-- and results in an expression of type 'Backend.Statement'. +-- +-- E.g.:+-- +-- >selectFive :: Statement b+-- >selectFive = [q|SELECT (? + ?)|] 2 3+-- +q :: QuasiQuoter+q = +  QuasiQuoter+    parseExp+    (const $ fail "Pattern context is not supported")+    (const $ fail "Type context is not supported")+    (const $ fail "Declaration context is not supported")+++parseExp :: String -> Q Exp+parseExp s =+  do+    n <- either (fail . showString "Parsing failure: ") return (Parser.parse (fromString s))+    return $ statementF s n++-- |+-- An expression of a function with an arbitrary arity, +-- which produces a "Backend.Statement".+statementF :: String -> Word -> Exp+statementF s n =+  LamE pats exp+  where+    vars = map (mkName . ('_' :) . show) [1 .. n]+    pats = map VarP vars+    exp  = AppE (AppE (ConE '(,)) (LitE (StringL s))) (ListE exps)+      where+        exps = map (AppE (VarE 'Backend.renderValue) . VarE) vars+
+ library/Hasql/QQ/Parser.hs view
@@ -0,0 +1,38 @@+module Hasql.QQ.Parser where++import Hasql.Prelude hiding (takeWhile)+import Data.Attoparsec.Text hiding (Result)+import qualified Data.Text as Text+++-- |+-- The amount of placeholders.+type Result =+  (Word)++parse :: Text -> Either String Result+parse = +  parseOnly countPlaceholders+  where+    countPlaceholders =+      count <|> pure 0+      where+        count =+          do+            many $ void stringLit <|> void (notChar '?')+            char '?'+            fmap succ countPlaceholders++stringLit :: Parser Text+stringLit =+  do+    quote <- +      char '"' <|> char '\''+    text <- +      fmap mconcat $ many $ +        string "\\\\" <|> +        string (fromString ['\\', quote]) <|> +        (Text.singleton <$> notChar quote)+    char quote+    return text+
+ library/Hasql/RowParser.hs view
@@ -0,0 +1,64 @@+module Hasql.RowParser where++import Hasql.Prelude+import Language.Haskell.TH+import qualified Hasql.Backend as Backend+import qualified Data.Vector as Vector+++class RowParser b r where+  parseRow :: Vector.Vector (Backend.Result b) -> Either Text r++instance RowParser b () where+  parseRow row = +    if Vector.null row+      then Right ()+      else $bug "Not an empty row"++instance Backend.Mapping b v => RowParser b (Identity v) where+  parseRow row = do+    Identity <$> Backend.parseResult (Vector.unsafeHead row)++-- Generate tuple instaces using Template Haskell:+let+  inst :: Int -> Dec+  inst arity =+    InstanceD constraints head [parseRowDec]+    where+      varNames =+        [1 .. arity] >>= \i -> return (mkName ('_' : show i))+      varTypes =+        map VarT varNames+      backendType =+        VarT (mkName "b")+      constraints =+        map (\t -> ClassP ''Backend.Mapping [backendType, t]) varTypes+      head =+        AppT (AppT (ConT ''RowParser) backendType) (foldl AppT (TupleT arity) varTypes)+      parseRowDec =+        FunD 'parseRow [Clause [VarP n] (NormalB e) []]+        where+          n = mkName "row"+          e =+            foldQueue queue+            where+              lookups = do+                i <- [0 .. pred arity]+                return $ purify $+                  [|+                    Backend.parseResult $ +                    (Vector.unsafeIndex) $(varE n) $(litE (IntegerL $ fromIntegral i)) +                  |]+              queue =+                (ConE (tupleDataName arity) :) $+                (VarE '(<$>) :) $+                intersperse (VarE '(<*>)) $+                lookups+              foldQueue =+                \case+                  e : o : t -> UInfixE e o (foldQueue t)+                  e : [] -> e+                  _ -> $bug "Unexpected queue size"+      purify = unsafePerformIO . runQ+  in +    mapM (return . inst) [2 .. 24]