couchdb-conduit 0.4.2 → 0.5.0
raw patch · 18 files changed
+539/−411 lines, 18 filesdep −QuickCheckdep −test-framework-quickcheck2PVP ok
version bump matches the API change (PVP)
Dependencies removed: QuickCheck, test-framework-quickcheck2
API changes (from Hackage documentation)
- Database.CouchDB.Conduit: couchDB :: CouchConnection -> Path
- Database.CouchDB.Conduit: instance Default CouchConnection
- Database.CouchDB.Conduit: instance Exception CouchError
- Database.CouchDB.Conduit: instance ResourceIO m => MonadCouch (ReaderT CouchConnection m)
- Database.CouchDB.Conduit: instance Show CouchError
- Database.CouchDB.Conduit: instance Typeable CouchError
- Database.CouchDB.Conduit: mkPath :: [Path] -> Path
- Database.CouchDB.Conduit.Explicit: couchDelete :: MonadCouch m => Path -> Revision -> ResourceT m ()
- Database.CouchDB.Conduit.Explicit: couchRev :: MonadCouch m => Path -> ResourceT m Revision
- Database.CouchDB.Conduit.Explicit: couchRev' :: MonadCouch m => Path -> ResourceT m Revision
- Database.CouchDB.Conduit.Generic: couchDelete :: MonadCouch m => Path -> Revision -> ResourceT m ()
- Database.CouchDB.Conduit.Generic: couchRev :: MonadCouch m => Path -> ResourceT m Revision
- Database.CouchDB.Conduit.Generic: couchRev' :: MonadCouch m => Path -> ResourceT m Revision
- Database.CouchDB.Conduit.LowLevel: couch' :: MonadCouch m => Method -> Path -> RequestHeaders -> Query -> RequestBody m -> ResourceT m (CouchResponse m)
+ Database.CouchDB.Conduit: couchDelete :: MonadCouch m => Path -> Path -> Revision -> ResourceT m ()
+ Database.CouchDB.Conduit: couchRev :: MonadCouch m => Path -> Path -> ResourceT m Revision
+ Database.CouchDB.Conduit: couchRev' :: MonadCouch m => Path -> Path -> ResourceT m Revision
- Database.CouchDB.Conduit.DB: couchDeleteDB :: MonadCouch m => ResourceT m ()
+ Database.CouchDB.Conduit.DB: couchDeleteDB :: MonadCouch m => Path -> ResourceT m ()
- Database.CouchDB.Conduit.DB: couchPutDB :: MonadCouch m => ResourceT m ()
+ Database.CouchDB.Conduit.DB: couchPutDB :: MonadCouch m => Path -> ResourceT m ()
- Database.CouchDB.Conduit.DB: couchPutDB_ :: MonadCouch m => ResourceT m ()
+ Database.CouchDB.Conduit.DB: couchPutDB_ :: MonadCouch m => Path -> ResourceT m ()
- Database.CouchDB.Conduit.DB: couchSecureDB :: MonadCouch m => [ByteString] -> [ByteString] -> [ByteString] -> [ByteString] -> ResourceT m ()
+ Database.CouchDB.Conduit.DB: couchSecureDB :: MonadCouch m => Path -> [ByteString] -> [ByteString] -> [ByteString] -> [ByteString] -> ResourceT m ()
- Database.CouchDB.Conduit.Design: couchPutView' :: MonadCouch m => Path -> Path -> ByteString -> Maybe ByteString -> ResourceT m Revision
+ Database.CouchDB.Conduit.Design: couchPutView' :: MonadCouch m => Path -> Path -> Path -> ByteString -> Maybe ByteString -> ResourceT m Revision
- Database.CouchDB.Conduit.Design: couchPutView_ :: MonadCouch m => Path -> Path -> ByteString -> Maybe ByteString -> ResourceT m Revision
+ Database.CouchDB.Conduit.Design: couchPutView_ :: MonadCouch m => Path -> Path -> Path -> ByteString -> Maybe ByteString -> ResourceT m Revision
- Database.CouchDB.Conduit.Explicit: couchGet :: (MonadCouch m, FromJSON a) => Path -> Query -> ResourceT m (Revision, a)
+ Database.CouchDB.Conduit.Explicit: couchGet :: (MonadCouch m, FromJSON a) => Path -> Path -> Query -> ResourceT m (Revision, a)
- Database.CouchDB.Conduit.Explicit: couchPut :: (MonadCouch m, ToJSON a) => Path -> Revision -> Query -> a -> ResourceT m Revision
+ Database.CouchDB.Conduit.Explicit: couchPut :: (MonadCouch m, ToJSON a) => Path -> Path -> Revision -> Query -> a -> ResourceT m Revision
- Database.CouchDB.Conduit.Explicit: couchPut' :: (MonadCouch m, ToJSON a) => Path -> Query -> a -> ResourceT m Revision
+ Database.CouchDB.Conduit.Explicit: couchPut' :: (MonadCouch m, ToJSON a) => Path -> Path -> Query -> a -> ResourceT m Revision
- Database.CouchDB.Conduit.Explicit: couchPut_ :: (MonadCouch m, ToJSON a) => Path -> Query -> a -> ResourceT m Revision
+ Database.CouchDB.Conduit.Explicit: couchPut_ :: (MonadCouch m, ToJSON a) => Path -> Path -> Query -> a -> ResourceT m Revision
- Database.CouchDB.Conduit.Generic: couchGet :: (MonadCouch m, Data a) => Path -> Query -> ResourceT m (Revision, a)
+ Database.CouchDB.Conduit.Generic: couchGet :: (MonadCouch m, Data a) => Path -> Path -> Query -> ResourceT m (Revision, a)
- Database.CouchDB.Conduit.Generic: couchPut :: (MonadCouch m, Data a) => Path -> Revision -> Query -> a -> ResourceT m Revision
+ Database.CouchDB.Conduit.Generic: couchPut :: (MonadCouch m, Data a) => Path -> Path -> Revision -> Query -> a -> ResourceT m Revision
- Database.CouchDB.Conduit.Generic: couchPut' :: (MonadCouch m, Data a) => Path -> Query -> a -> ResourceT m Revision
+ Database.CouchDB.Conduit.Generic: couchPut' :: (MonadCouch m, Data a) => Path -> Path -> Query -> a -> ResourceT m Revision
- Database.CouchDB.Conduit.Generic: couchPut_ :: (MonadCouch m, Data a) => Path -> Query -> a -> ResourceT m Revision
+ Database.CouchDB.Conduit.Generic: couchPut_ :: (MonadCouch m, Data a) => Path -> Path -> Query -> a -> ResourceT m Revision
- Database.CouchDB.Conduit.LowLevel: couch :: MonadCouch m => Method -> (Path -> Path) -> RequestHeaders -> Query -> RequestBody m -> (CouchResponse m -> ResourceT m (CouchResponse m)) -> ResourceT m (CouchResponse m)
+ Database.CouchDB.Conduit.LowLevel: couch :: MonadCouch m => Method -> Path -> RequestHeaders -> Query -> RequestBody m -> (CouchResponse m -> ResourceT m (CouchResponse m)) -> ResourceT m (CouchResponse m)
- Database.CouchDB.Conduit.View: couchView :: MonadCouch m => Path -> Path -> Query -> ResourceT m (Source m Object)
+ Database.CouchDB.Conduit.View: couchView :: MonadCouch m => Path -> Path -> Path -> Query -> ResourceT m (Source m Object)
- Database.CouchDB.Conduit.View: couchView' :: MonadCouch m => Path -> Path -> Query -> Sink Object m a -> ResourceT m a
+ Database.CouchDB.Conduit.View: couchView' :: MonadCouch m => Path -> Path -> Path -> Query -> Sink Object m a -> ResourceT m a
Files
- couchdb-conduit.cabal +16/−16
- src/Database/CouchDB/Conduit.hs +38/−153
- src/Database/CouchDB/Conduit/DB.hs +27/−17
- src/Database/CouchDB/Conduit/Design.hs +16/−10
- src/Database/CouchDB/Conduit/Explicit.hs +38/−35
- src/Database/CouchDB/Conduit/Generic.hs +38/−35
- src/Database/CouchDB/Conduit/Implicit.hs +69/−0
- src/Database/CouchDB/Conduit/Internal/Connection.hs +163/−0
- src/Database/CouchDB/Conduit/Internal/Doc.hs +45/−40
- src/Database/CouchDB/Conduit/Internal/Parser.hs +4/−3
- src/Database/CouchDB/Conduit/Internal/View.hs +2/−1
- src/Database/CouchDB/Conduit/LowLevel.hs +4/−22
- src/Database/CouchDB/Conduit/View.hs +23/−21
- test/Database/CouchDB/Conduit/Test/Base.hs +5/−6
- test/Database/CouchDB/Conduit/Test/Explicit.hs +16/−16
- test/Database/CouchDB/Conduit/Test/Generic.hs +16/−16
- test/Database/CouchDB/Conduit/Test/Util.hs +4/−5
- test/Database/CouchDB/Conduit/Test/View.hs +15/−15
couchdb-conduit.cabal view
@@ -1,8 +1,8 @@ name: couchdb-conduit-version: 0.4.2+version: 0.5.0 cabal-version: >= 1.8 build-type: Simple-stability: Experimental+stability: Stable category: Database, Conduit license: BSD3 license-file: LICENSE@@ -45,18 +45,20 @@ data-default, blaze-builder >= 0.2.1 && < 0.4 ghc-options: -Wall- exposed-modules: - Database.CouchDB.Conduit,- Database.CouchDB.Conduit.DB,- Database.CouchDB.Conduit.Design,- Database.CouchDB.Conduit.Explicit,- Database.CouchDB.Conduit.Generic,- Database.CouchDB.Conduit.LowLevel,- Database.CouchDB.Conduit.View- other-modules: - Database.CouchDB.Conduit.Internal.Doc,- Database.CouchDB.Conduit.Internal.Parser,- Database.CouchDB.Conduit.Internal.View+ exposed-modules: + Database.CouchDB.Conduit, + Database.CouchDB.Conduit.DB, + Database.CouchDB.Conduit.Design, + Database.CouchDB.Conduit.Explicit, + Database.CouchDB.Conduit.Generic, + Database.CouchDB.Conduit.LowLevel, + Database.CouchDB.Conduit.View + other-modules: + Database.CouchDB.Conduit.Internal.Doc, + Database.CouchDB.Conduit.Internal.Parser, + Database.CouchDB.Conduit.Internal.View, + Database.CouchDB.Conduit.Internal.Connection, + Database.CouchDB.Conduit.Implicit test-suite test type: exitcode-stdio-1.0@@ -64,9 +66,7 @@ build-depends: base >= 4, HUnit >= 1.2 && < 2,- QuickCheck >= 2.4, test-framework >= 0.4.1,- test-framework-quickcheck2, test-framework-hunit, couchdb-conduit, aeson >= 0.6 && < 0.7,
src/Database/CouchDB/Conduit.hs view
@@ -1,20 +1,12 @@-{-# LANGUAGE DeriveDataTypeable #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE FlexibleContexts #-} +{- | CouchDB -{- | To work with concrete objects, use the following modules: - * "Database.CouchDB.Conduit.DB" - - * "Database.CouchDB.Conduit.View" - - * "Database.CouchDB.Conduit.Explicit" + * "Database.CouchDB.Conduit.DB" Database - * "Database.CouchDB.Conduit.Generic" + * "Database.CouchDB.Conduit.View" Views - * "Database.CouchDB.Conduit.LowLevel" + * "Database.CouchDB.Conduit.LowLevel" Low-level methods For complete documentation about The Couch DB HTTP API see <http://wiki.apache.org/couchdb/Complete_HTTP_API_Reference> @@ -22,9 +14,7 @@ module Database.CouchDB.Conduit ( -- * Document paths and revisions #path# - -- $path Path, - mkPath, Revision, -- * CouchDB Connection #connection# @@ -33,157 +23,52 @@ couchHost, couchPort, couchManager, - couchDB, couchLogin, couchPass, -- * Runtime enviroment and errors #runtime# - -- $runtime MonadCouch (..), CouchError (..), runCouch, withCouchConnection, + -- * Documents + -- + -- For accessing and storing document data, use one of following: + -- + -- * "Database.CouchDB.Conduit.Explicit" Explicit JSON methods + -- + -- * "Database.CouchDB.Conduit.Generic" Generic JSON methods + couchRev, + couchRev', + couchDelete + ) where -import Control.Monad.Trans.Reader (ReaderT, ask, runReaderT) -import Control.Exception (Exception) -import Control.Monad.Trans.Class (lift) - -import Data.Conduit (ResourceIO, ResourceT, runResourceT) - -import qualified Network.HTTP.Conduit as H -import qualified Network.HTTP.Types as HT - -import Data.Generics (Typeable) -import Data.Default (Default (def)) -import qualified Data.ByteString as B -import qualified Data.Text.Encoding as TE -import qualified Blaze.ByteString.Builder as BLB - ------------------------------------------------------------------------------ --- Paths ------------------------------------------------------------------------------ - --- $path --- As a rule, full path to document in CouchDB is just URL path. But there is --- one subtlety. For example, document ids /can/ contain slashes. But, --- to work with such objects, path fragments must be escaped. --- --- > database/doc%2Fname --- --- But, fo non-document items such as views, attachments e.t.c., slashes --- between path fragments /must not/ be escaped. While slashes in path --- fragments /must/ be escaped. --- --- > database/_design/my%2Fdesign/_view/my%2Fview - --- | Represents a path or path fragment. -type Path = B.ByteString - --- | Represents a revision of a CouchDB Document. -type Revision = B.ByteString - --- | Make correct path from escaped fragments. Filter empty fragments. --- --- > mkPath ["db", "", "doc/with/slashes"] --- > db/doc%2Fwith%2Fslashes -mkPath :: [Path] -- ^ Path fragments be escaped. - -> Path -mkPath = BLB.toByteString . HT.encodePathSegments . - map TE.decodeUtf8 . filter (/="") - ------------------------------------------------------------------------------ --- Connection ------------------------------------------------------------------------------ - --- | Represents a single connection to CouchDB server. The constructor for this --- data type is not exposed. Instead, you should use either the 'def' method --- to retrieve a default instance. -data CouchConnection = CouchConnection { - couchHost :: B.ByteString - -- ^ Hostname. Default value is \"localhost\" - , couchPort :: Int - -- ^ Port. 5984 by default. - , couchManager :: Maybe H.Manager - -- ^ Connection 'Manager'. 'Nothing' by default. If you need to use - -- your 'H.Manager' (for connection pooling for example), set it to - -- 'Just' 'H.Manager'. - , couchDB :: Path - -- ^ Database name. This value is prepended to 'Path' to form the full - -- path in all requests. - -- - -- By default is 'B.empty'. This makes it possible to access - -- different databases through a single connection. But, in this - -- case, all requests must be preceded by the database name with - -- unescaped slash. See 'Path' for details. - , couchLogin :: B.ByteString - -- ^ CouchDB login. By default is 'B.empty'. - , couchPass :: B.ByteString - -- ^ CouchDB password. By default is 'B.empty'. -} - -instance Default CouchConnection where - def = CouchConnection "localhost" 5984 Nothing B.empty B.empty B.empty - ------------------------------------------------------------------------------ --- Runtime ------------------------------------------------------------------------------ - --- $runtime --- All functions to access CouchDB require a 'MonadCouch' instance to --- access the connection information. 'ReaderT' is an instance of --- 'MonadCouch', and /runCouch/ runs a sequence of database actions using --- 'ReaderT' and 'ResourceT'. --- --- If your db code is part of a larger monad, it makes sense to just make the --- larger monad an instance of 'MonadCouch' and skip the intermediate ReaderT, --- since then performance is improved by eliminating one monad from the final --- transformer stack. - --- | A monad which allows access to the connection. -class ResourceIO m => MonadCouch m where - couchConnection :: m CouchConnection - -instance (ResourceIO m) => MonadCouch (ReaderT CouchConnection m) where - couchConnection = ask +import Data.Conduit (ResourceT) +import Database.CouchDB.Conduit.Internal.Connection +import qualified Database.CouchDB.Conduit.Internal.Doc as D --- | A CouchDB Error. -data CouchError - = CouchHttpError Int B.ByteString - -- ^ Error comes from http. - | CouchInternalError B.ByteString - -- ^ Non-http errors include things like errors - -- parsing the response. - | NotModified - -- ^ /Is not an error actually/. It is thrown when CouchDB returns - -- @304 - Not Modified@ response to the request. See - -- <http://wiki.apache.org/couchdb/HTTP_Document_API> - deriving (Show, Typeable) -instance Exception CouchError +-- | Get Revision of a document. +couchRev :: MonadCouch m => + Path -- ^ Database. + -> Path -- ^ Document path. + -> ResourceT m Revision +couchRev db p = D.couchRev (mkPath [db, p]) --- | Run a sequence of CouchDB actions. This function is a combination of --- 'withCouchConnection', 'runReaderT' and 'runResourceT'. --- --- If you create your own instance of 'MonadCouch', use 'withCouchConnection'. -runCouch :: ResourceIO m => - CouchConnection -- ^ Couch connection - -> ResourceT (ReaderT CouchConnection m) a -- ^ CouchDB actions - -> m a -runCouch c = withCouchConnection c . runReaderT . runResourceT +-- | Brain-free version of 'couchRev'. If document absent, +-- just return 'B.empty'. +couchRev' :: MonadCouch m => + Path -- ^ Database. + -> Path -- ^ Document path. + -> ResourceT m Revision +couchRev' db p = D.couchRev' (mkPath [db, p]) --- | Connect to a CouchDB server, call the supplied function, and then close --- the connection. --- --- > withCouchConnection def {couchDB = "db"} . runReaderT . runResourceT $ do --- > ... -- actions -withCouchConnection :: ResourceIO m => - CouchConnection -- ^ Couch connection - -> (CouchConnection -> m a) -- ^ Function to run - -> m a -withCouchConnection c@(CouchConnection _ _ mayMan _ _ _) f = - case mayMan of - -- Allocate manager with helper - Nothing -> H.withManager $ \m -> lift $ f $ c {couchManager = Just m} - _ -> f c +-- | Delete the given revision of the object. +couchDelete :: MonadCouch m => + Path -- ^ Database. + -> Path -- ^ Document path. + -> Revision -- ^ Revision + -> ResourceT m () +couchDelete db p = D.couchDelete (mkPath [db, p])
src/Database/CouchDB/Conduit/DB.hs view
@@ -18,6 +18,8 @@ couchReplicateDB ) where +import Control.Monad (void) + import qualified Data.ByteString as B import qualified Data.Aeson as A @@ -26,41 +28,50 @@ import qualified Network.HTTP.Conduit as H import qualified Network.HTTP.Types as HT -import Database.CouchDB.Conduit (MonadCouch(..)) +import Database.CouchDB.Conduit.Internal.Connection + (MonadCouch(..), Path, mkPath) import Database.CouchDB.Conduit.LowLevel (couch, protect, protect') -- | Create CouchDB database. -couchPutDB :: MonadCouch m => ResourceT m () -couchPutDB = couch HT.methodPut id [] [] - (H.RequestBodyBS B.empty) protect' - >> return () +couchPutDB :: MonadCouch m => + Path -- ^ Database + -> ResourceT m () +couchPutDB db = void $ couch HT.methodPut + (mkPath [db]) [] [] + (H.RequestBodyBS B.empty) + protect' -- | \"Don't care\" version of couchPutDb. Create CouchDB database only in its -- absence. For this it handles @412@ responses. -couchPutDB_ :: MonadCouch m => ResourceT m () -couchPutDB_ = couch HT.methodPut id [] [] +couchPutDB_ :: MonadCouch m => + Path -- ^ Database + -> ResourceT m () +couchPutDB_ db = void $ couch HT.methodPut + (mkPath [db]) [] [] (H.RequestBodyBS B.empty) (protect [200, 201, 202, 304, 412] return) - >> return () -- | Delete a database. -couchDeleteDB :: MonadCouch m => ResourceT m () -couchDeleteDB = couch HT.methodDelete id [] [] +couchDeleteDB :: MonadCouch m => + Path -- ^ Database + -> ResourceT m () +couchDeleteDB db = void $ couch HT.methodDelete + (mkPath [db]) [] [] (H.RequestBodyBS B.empty) protect' - >> return () -- | Maintain DB security. couchSecureDB :: MonadCouch m => - [B.ByteString] -- ^ Admin roles + Path -- ^ Database + -> [B.ByteString] -- ^ Admin roles -> [B.ByteString] -- ^ Admin names -> [B.ByteString] -- ^ Readers roles -> [B.ByteString] -- ^ Readers names -> ResourceT m () -couchSecureDB adminRoles adminNames readersRoles readersNames = - couch HT.methodPut (`B.append` "/_security") [] [] +couchSecureDB db adminRoles adminNames readersRoles readersNames = + void $ couch HT.methodPut + (mkPath [db, "_security"]) [] [] reqBody protect' - >> return () where reqBody = H.RequestBodyLBS $ A.encode $ A.object [ "admins" A..= A.object [ "roles" A..= adminRoles, @@ -80,9 +91,8 @@ -> Bool -- ^ Cancel flag -> ResourceT m () couchReplicateDB source target createTarget continuous cancel = - couch HT.methodPost (const "_replicate") [] [] + void $ couch HT.methodPost "_replicate" [] [] reqBody protect' - >> return () where reqBody = H.RequestBodyLBS $ A.encode $ A.object [ "source" A..= source,
src/Database/CouchDB/Conduit/Design.hs view
@@ -20,14 +20,16 @@ import qualified Data.Aeson as A import qualified Data.Aeson.Types as AT -import Database.CouchDB.Conduit (MonadCouch, CouchError, Path, mkPath, Revision) +import Database.CouchDB.Conduit.Internal.Connection + (MonadCouch, CouchError, Path, mkPath, Revision) import Database.CouchDB.Conduit.Internal.Doc (couchGetWith, couchPutWith_, couchPutWith') -- | Put view in design document if it not exists. If design document does -- not exist, it will be created. couchPutView_ :: MonadCouch m => - Path -- ^ Design document + Path -- ^ Database + -> Path -- ^ Design document -> Path -- ^ View name -> B.ByteString -- ^ Map function -> Maybe B.ByteString -- ^ Reduce function @@ -37,7 +39,8 @@ -- | Brute-force version of 'couchViewPut''. Put view in design document. -- If design document does not exist, it will be created. couchPutView' :: MonadCouch m => - Path -- ^ Design document + Path -- ^ Database + -> Path -- ^ Design document -> Path -- ^ View name -> B.ByteString -- ^ Map function -> Maybe B.ByteString -- ^ Reduce function @@ -49,19 +52,20 @@ ----------------------------------------------------------------------------- couchViewPutInt :: MonadCouch m => - Bool + Bool -- ^ Care flag + -> Path -- ^ Database -> Path -- ^ Design document -> Path -- ^ View name -> B.ByteString -- ^ Map function -> Maybe B.ByteString -- ^ Reduce function -> ResourceT m Revision -couchViewPutInt prot designName viewName mapF reduceF = do +couchViewPutInt prot db designName viewName mapF reduceF = do -- Get design or empty object (_, A.Object d) <- getDesignDoc path if prot then couchPutWith_ A.encode path [] $ inferViews (purge_ d) else couchPutWith' A.encode path [] $ inferViews (purge_ d) where - path = designDocPath designName + path = designDocPath db designName inferViews d = A.Object $ M.insert "views" (addView d) d addView d = A.Object $ M.insert (TE.decodeUtf8 viewName) @@ -71,13 +75,15 @@ constructView m (Just r) = A.object ["map" A..= m, "reduce" A..= r] constructView m Nothing = A.object ["map" A..= m] -getDesignDoc :: MonadCouch m => Path -> ResourceT m (Revision, AT.Value) +getDesignDoc :: MonadCouch m => + Path + -> ResourceT m (Revision, AT.Value) getDesignDoc designName = catch - (couchGetWith A.Success (designDocPath designName) []) + (couchGetWith A.Success designName []) (\(_ :: CouchError) -> return (B.empty, AT.emptyObject)) -designDocPath :: Path -> Path -designDocPath dn = mkPath ["_design", dn] +designDocPath :: Path -> Path -> Path +designDocPath db dn = mkPath [db, "_design", dn] -- | Purge underscore fields purge_ :: AT.Object -> AT.Object
src/Database/CouchDB/Conduit/Explicit.hs view
@@ -21,21 +21,21 @@ -- > instance ToJSON D where -- > toJSON (D f1 f2) = object ["f1" .= f1, "f2" .= f2] -- > --- > runCouch def {couchDB="mydb"} $ do +-- > runCouch def $ do -- > -- Put new doc and update it --- > rev1 <- couchPut "my-doc1" "" [] $ D 123 "str" --- > rev2 <- couchPut "my-doc1" rev1 [] $ D 1234 "another" +-- > rev1 <- couchPut "mydb" "my-doc1" "" [] $ D 123 "str" +-- > rev2 <- couchPut "mydb" "my-doc1" rev1 [] $ D 1234 "another" -- > -- > -- get it and print --- > (rev3, d1 :: D) <- couchGet "my-doc1" [] +-- > (rev3, d1 :: D) <- couchGet "mydb" "my-doc1" [] -- > liftIO $ print d1 -- > -- > -- update it in brute-force manner --- > couchPut' "my-doc1" [] $ D 12345 "third" -- notice - no rev +-- > couchPut' "mydb" "my-doc1" [] $ D 12345 "third" -- notice - no rev -- > -- > -- get revision and delete --- > rev3 <- couchRev "my-doc1" --- > couchDelete "my-doc1" rev3 +-- > rev3 <- couchRev "mydb" "my-doc1" +-- > couchDelete "mydb" "my-doc1" rev3 -- -- For details of types see "Data.Aeson". To work with documents in -- generic manner, look at "Database.CouchDB.Conduit.Generic". @@ -43,25 +43,24 @@ module Database.CouchDB.Conduit.Explicit ( -- * Accessing documents couchGet, - couchRev, - couchRev', -- * Manipulating documents couchPut, couchPut_, couchPut', - couchDelete, -- * Working with views #view# toType ) where -import qualified Data.Aeson as A -import Data.Conduit (ResourceT, Conduit(..), ResourceIO) +import qualified Data.Aeson as A +import Data.Conduit (ResourceT, Conduit(..), ResourceIO) -import Network.HTTP.Types as HT +import Network.HTTP.Types (Query) -import Database.CouchDB.Conduit (MonadCouch(..), Path, Revision) -import Database.CouchDB.Conduit.Internal.Doc -import Database.CouchDB.Conduit.Internal.View (toTypeWith) +import Database.CouchDB.Conduit.Internal.Connection (MonadCouch(..), + Path, Revision, mkPath) +import Database.CouchDB.Conduit.Internal.Doc (couchGetWith, couchPutWith, + couchPutWith_, couchPutWith') +import Database.CouchDB.Conduit.Internal.View (toTypeWith) ------------------------------------------------------------------------------ -- Document @@ -69,38 +68,42 @@ -- | Load a single 'A.ToJSON' object with 'Revision' from couch DB. couchGet :: (MonadCouch m, A.FromJSON a) => - Path -- ^ Document path - -> HT.Query -- ^ Query + Path -- ^ Database + -> Path -- ^ Document path + -> Query -- ^ Query -> ResourceT m (Revision, a) -couchGet = couchGetWith A.fromJSON +couchGet db p = couchGetWith A.fromJSON (mkPath [db, p]) -- | Put an 'A.FromJSON' object in Couch DB with revision, returning the -- new 'Revision'. couchPut :: (MonadCouch m, A.ToJSON a) => - Path -- ^ Document path. - -> Revision -- ^ Document revision. For new docs provide empty string. - -> HT.Query -- ^ Query arguments. - -> a -- ^ The object to store. - -> ResourceT m Revision -couchPut = couchPutWith A.encode + Path -- ^ Database + -> Path -- ^ Document path + -> Revision -- ^ Document revision. For new docs provide empty string. + -> Query -- ^ Query arguments. + -> a -- ^ The object to store. + -> ResourceT m Revision +couchPut db p = couchPutWith A.encode (mkPath [db, p]) -- | \"Don't care\" version of 'couchPut'. Creates document only in its -- absence. couchPut_ :: (MonadCouch m, A.ToJSON a) => - Path -- ^ Document path. - -> HT.Query -- ^ Query arguments. - -> a -- ^ The object to store. - -> ResourceT m Revision -couchPut_ = couchPutWith_ A.encode + Path -- ^ Database + -> Path -- ^ Document path + -> Query -- ^ Query arguments. + -> a -- ^ The object to store. + -> ResourceT m Revision +couchPut_ db p = couchPutWith_ A.encode (mkPath [db, p]) -- | Brute force version of 'couchPut'. Creates a document regardless of -- presence. couchPut' :: (MonadCouch m, A.ToJSON a) => - Path -- ^ Document path. - -> HT.Query -- ^ Query arguments. - -> a -- ^ The object to store. - -> ResourceT m Revision -couchPut' = couchPutWith' A.encode + Path -- ^ Database + -> Path -- ^ Document path + -> Query -- ^ Query arguments. + -> a -- ^ The object to store. + -> ResourceT m Revision +couchPut' db p = couchPutWith' A.encode (mkPath [db, p]) ------------------------------------------------------------------------------ -- View conduit
src/Database/CouchDB/Conduit/Generic.hs view
@@ -14,21 +14,21 @@ -- > -- | Our doc with instances -- > data D = D { f1 :: Int, f2 :: String } deriving (Show, Data, Typeable) -- > --- > runCouch def {couchDB="mydb"} $ do +-- > runCouch def $ do -- > -- Put new doc and update it --- > rev1 <- couchPut "my-doc1" "" [] $ D 123 "str" --- > rev2 <- couchPut "my-doc1" rev1 [] $ D 1234 "another" +-- > rev1 <- couchPut "mydb" "my-doc1" "" [] $ D 123 "str" +-- > rev2 <- couchPut "mydb" "my-doc1" rev1 [] $ D 1234 "another" -- > -- > -- get it and print --- > (rev3, d1 :: D) <- couchGet "my-doc1" [] +-- > (rev3, d1 :: D) <- couchGet "mydb" "my-doc1" [] -- > liftIO $ print d1 -- > -- > -- update it in brute-force manner --- > couchPut' "my-doc1" [] $ D 12345 "third" -- notice - no rev +-- > couchPut' "mydb" "my-doc1" [] $ D 12345 "third" -- notice - no rev -- > -- > -- get revision and delete --- > rev3 <- couchRev "my-doc1" --- > couchDelete "my-doc1" rev3 +-- > rev3 <- couchRev "mydb" "my-doc1" +-- > couchDelete "mydb" "my-doc1" rev3 -- -- The main advantage of this approach in the absence of tonns of -- boilerplate code. The main disadvantage is inability to influence the @@ -40,61 +40,64 @@ module Database.CouchDB.Conduit.Generic ( -- * Accessing documents couchGet, - couchRev, - couchRev', -- * Manipulating documents couchPut, couchPut_, couchPut', - couchDelete, -- * Working with views #view# toType ) where -import Data.Generics (Data) +import Data.Generics (Data) import qualified Data.Aeson as A import qualified Data.Aeson.Generic as AG -import Data.Conduit (ResourceT, Conduit(..), ResourceIO) +import Data.Conduit (ResourceT, Conduit(..), ResourceIO) -import qualified Network.HTTP.Types as HT +import Network.HTTP.Types (Query) -import Database.CouchDB.Conduit -import Database.CouchDB.Conduit.Internal.Doc -import Database.CouchDB.Conduit.Internal.View +import Database.CouchDB.Conduit.Internal.Connection + (MonadCouch(..), Path, mkPath, Revision) +import Database.CouchDB.Conduit.Internal.Doc + (couchGetWith, couchPutWith, couchPutWith_, couchPutWith') +import Database.CouchDB.Conduit.Internal.View (toTypeWith) -- | Load a single object from couch DB. couchGet :: (MonadCouch m, Data a) => - Path -- ^ Document path - -> HT.Query -- ^ Query + Path -- ^ Database + -> Path -- ^ Document path + -> Query -- ^ Query -> ResourceT m (Revision, a) -couchGet = couchGetWith AG.fromJSON +couchGet db p = couchGetWith AG.fromJSON (mkPath [db, p]) -- | Put an object in Couch DB with revision, returning the new Revision. couchPut :: (MonadCouch m, Data a) => - Path -- ^ Document path. - -> Revision -- ^ Document revision. For new docs provide empty string. - -> HT.Query -- ^ Query arguments. - -> a -- ^ The object to store. - -> ResourceT m Revision -couchPut = couchPutWith AG.encode + Path -- ^ Database + -> Path -- ^ Document path + -> Revision -- ^ Document revision. For new docs provide empty string. + -> Query -- ^ Query arguments. + -> a -- ^ The object to store. + -> ResourceT m Revision +couchPut db p = couchPutWith AG.encode (mkPath [db, p]) -- | \"Don't care\" version of 'couchPut'. Creates document only in its -- absence. couchPut_ :: (MonadCouch m, Data a) => - Path -- ^ Document path. - -> HT.Query -- ^ Query arguments. - -> a -- ^ The object to store. - -> ResourceT m Revision -couchPut_ = couchPutWith_ AG.encode + Path -- ^ Database + -> Path -- ^ Document path + -> Query -- ^ Query arguments. + -> a -- ^ The object to store. + -> ResourceT m Revision +couchPut_ db p = couchPutWith_ AG.encode (mkPath [db, p]) -- | Brute force version of 'couchPut'. Creates a document regardless of -- presence. couchPut' :: (MonadCouch m, Data a) => - Path -- ^ Document path. - -> HT.Query -- ^ Query arguments. - -> a -- ^ The object to store. - -> ResourceT m Revision -couchPut' = couchPutWith' AG.encode + Path -- ^ Database + -> Path -- ^ Document path + -> Query -- ^ Query arguments. + -> a -- ^ The object to store. + -> ResourceT m Revision +couchPut' db p = couchPutWith' AG.encode (mkPath [db, p]) ------------------------------------------------------------------------------ -- View conduit
+ src/Database/CouchDB/Conduit/Implicit.hs view
@@ -0,0 +1,69 @@+-- | Implicit methods for CouchDB documents. +-- +-- All implicit methods needs parser or encoder. + +module Database.CouchDB.Conduit.Implicit ( + -- * Accessing documents + couchGet, + -- * Manipulating documents + couchPut, + couchPut_, + couchPut', +) where + +import qualified Data.Aeson as A +import qualified Data.ByteString.Lazy as BL (ByteString) +import Data.Conduit (ResourceT) + +import Database.CouchDB.Conduit.Internal.Connection + (MonadCouch(..), Path, mkPath, Revision) +import Database.CouchDB.Conduit.Internal.Doc (couchGetWith, couchPutWith, + couchPutWith_, couchPutWith') + +import Network.HTTP.Types (Query) + +-- | Load CouchDB document and parse it with given parser. +-- +-- > (rev, rawJson) <- couchGet Success "mydb" mydoc [] +couchGet :: MonadCouch m => + (A.Value -> A.Result a) -- ^ Parser + -> Path -- ^ Document path. + -> Path -- ^ Document path. + -> Query -- ^ Query + -> ResourceT m (Revision, a) +couchGet f db p = couchGetWith f (mkPath [db, p]) + +-- | Put document, with given encoder +couchPut :: MonadCouch m => + (a -> BL.ByteString) -- ^ Encoder + -> Path -- ^ Document path. + -> Path -- ^ Document path. + -> Revision -- ^ Document revision. For new docs provide + -- ^ empty string. + -> Query -- ^ Query arguments. + -> a -- ^ The object to store. + -> ResourceT m Revision +couchPut f db p = couchPutWith f (mkPath [db, p]) + +-- | \"Don't care\" version of 'couchPut'. Creates document only in its +-- absence. +couchPut_ :: MonadCouch m => + (a -> BL.ByteString) -- ^ Encoder + -> Path -- ^ Document path. + -> Path -- ^ Document path. + -> Query -- ^ Query arguments. + -> a -- ^ The object to store. + -> ResourceT m Revision +couchPut_ f db p = couchPutWith_ f (mkPath [db, p]) + +-- | Brute force version of 'couchPut'. Creates a document regardless of +-- presence. +couchPut' :: MonadCouch m => + (a -> BL.ByteString) -- ^ Encoder + -> Path -- ^ Document path. + -> Path -- ^ Document path. + -> Query -- ^ Query arguments. + -> a -- ^ The object to store. + -> ResourceT m Revision +couchPut' f db p = couchPutWith' f (mkPath [db, p]) +
+ src/Database/CouchDB/Conduit/Internal/Connection.hs view
@@ -0,0 +1,163 @@+{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE FlexibleContexts #-} + +-- | CouchDB connection. + +module Database.CouchDB.Conduit.Internal.Connection ( + -- * Document paths and revisions #path# + Path, + mkPath, + Revision, + + -- * CouchDB Connection #connection# + CouchConnection, + def, + couchHost, + couchPort, + couchManager, + couchLogin, + couchPass, + + -- * Runtime enviroment and errors #runtime# + MonadCouch (..), + CouchError (..), + runCouch, + withCouchConnection + +) where + +import Control.Monad.Trans.Reader (ReaderT, ask, runReaderT) +import Control.Exception (Exception) +import Control.Monad.Trans.Class (lift) + +import Data.Conduit (ResourceIO, ResourceT, runResourceT) + +import qualified Network.HTTP.Conduit as H +import qualified Network.HTTP.Types as HT + +import Data.Generics (Typeable) +import Data.Default (Default (def)) +import qualified Data.ByteString as B +import qualified Data.Text.Encoding as TE +import qualified Blaze.ByteString.Builder as BLB + +----------------------------------------------------------------------------- +-- Paths +----------------------------------------------------------------------------- + +-- | Represents a path or path fragment. +-- +-- As a rule, full path to document in CouchDB is just URL path. But there is +-- one subtlety. For example, document ids /can/ contain slashes. But, +-- to work with such objects, path fragments must be escaped. +-- +-- > database/doc%2Fname +-- +-- But, fo non-document items such as views, attachments e.t.c., slashes +-- between path fragments /must not/ be escaped. While slashes in path +-- fragments /must/ be escaped. +-- +-- > database/_design/my%2Fdesign/_view/my%2Fview +-- +-- Except low-level functions, @couchdb-conduit@ escapes all segments in paths. +type Path = B.ByteString + +-- | Represents a revision of a CouchDB Document. +type Revision = B.ByteString + +-- | Make correct path and escape fragments. Filter empty fragments. +-- +-- > mkPath ["db", "", "doc/with/slashes"] +-- > db/doc%2Fwith%2Fslashes +mkPath :: [Path] -- ^ Path fragments be escaped. + -> Path +mkPath = BLB.toByteString . HT.encodePathSegments . + map TE.decodeUtf8 . filter (/="") + +----------------------------------------------------------------------------- +-- Connection +----------------------------------------------------------------------------- + +-- | Represents a single connection to CouchDB server. The constructor for this +-- data type is not exposed. Instead, you should use either the 'def' method +-- to retrieve a default instance. +data CouchConnection = CouchConnection { + couchHost :: B.ByteString + -- ^ Hostname. Default value is \"localhost\" + , couchPort :: Int + -- ^ Port. 5984 by default. + , couchManager :: Maybe H.Manager + -- ^ Connection 'Manager'. 'Nothing' by default. If you need to use + -- your 'H.Manager' (for connection pooling for example), set it to + -- 'Just' 'H.Manager'. + , couchLogin :: B.ByteString + -- ^ CouchDB login. By default is 'B.empty'. + , couchPass :: B.ByteString + -- ^ CouchDB password. By default is 'B.empty'. +} + +instance Default CouchConnection where + def = CouchConnection "localhost" 5984 Nothing B.empty B.empty + +----------------------------------------------------------------------------- +-- Runtime +----------------------------------------------------------------------------- + +-- | A monad which allows access to the connection. +-- +-- All functions to access CouchDB require a 'MonadCouch' instance to +-- access the connection information. 'ReaderT' is an instance of +-- 'MonadCouch', and /runCouch/ runs a sequence of database actions using +-- 'ReaderT' and 'ResourceT'. +-- +-- If your db code is part of a larger monad, it makes sense to just make the +-- larger monad an instance of 'MonadCouch' and skip the intermediate ReaderT, +-- since then performance is improved by eliminating one monad from the final +-- transformer stack. +class ResourceIO m => MonadCouch m where + couchConnection :: m CouchConnection + +instance (ResourceIO m) => MonadCouch (ReaderT CouchConnection m) where + couchConnection = ask + +-- | A CouchDB Error. +data CouchError + = CouchHttpError Int B.ByteString + -- ^ Error comes from http. + | CouchInternalError B.ByteString + -- ^ Non-http errors include things like errors + -- parsing the response. + | NotModified + -- ^ /Is not an error actually/. It is thrown when CouchDB returns + -- @304 - Not Modified@ response to the request. See + -- <http://wiki.apache.org/couchdb/HTTP_Document_API> + deriving (Show, Typeable) +instance Exception CouchError + +-- | Run a sequence of CouchDB actions. This function is a combination of +-- 'withCouchConnection', 'runReaderT' and 'runResourceT'. +-- +-- If you create your own instance of 'MonadCouch', use 'withCouchConnection'. +runCouch :: ResourceIO m => + CouchConnection -- ^ Couch connection + -> ResourceT (ReaderT CouchConnection m) a -- ^ CouchDB actions + -> m a +runCouch c = withCouchConnection c . runReaderT . runResourceT + +-- | Connect to a CouchDB server, call the supplied function, and then close +-- the connection. +-- +-- > withCouchConnection def {couchDB = "db"} . runReaderT . runResourceT $ do +-- > ... -- actions +withCouchConnection :: ResourceIO m => + CouchConnection -- ^ Couch connection + -> (CouchConnection -> m a) -- ^ Function to run + -> m a +withCouchConnection c@(CouchConnection _ _ mayMan _ _) f = + case mayMan of + -- Allocate manager with helper + Nothing -> H.withManager $ \m -> lift $ f $ c {couchManager = Just m} + _ -> f c +
src/Database/CouchDB/Conduit/Internal/Doc.hs view
@@ -1,6 +1,6 @@ {-# LANGUAGE OverloadedStrings #-} --- | Internal +-- | Internal doc. module Database.CouchDB.Conduit.Internal.Doc ( couchRev, couchRev', @@ -25,9 +25,10 @@ import qualified Network.HTTP.Conduit as H import Network.HTTP.Types as HT -import Database.CouchDB.Conduit -import Database.CouchDB.Conduit.LowLevel (couch') +import Database.CouchDB.Conduit.Internal.Connection +import Database.CouchDB.Conduit.LowLevel (couch, protect') import Database.CouchDB.Conduit.Internal.Parser +import Control.Monad (void) ------------------------------------------------------------------------------ -- Type-independent methods @@ -35,19 +36,20 @@ -- | Get Revision of a document. couchRev :: MonadCouch m => - Path -- ^ Path + Path -- ^ Correct 'Path' with escaped fragments. -> ResourceT m Revision couchRev p = do - (H.Response _ hs _) <- couch' HT.methodHead p [] [] - (H.RequestBodyBS B.empty) + (H.Response _ hs _) <- couch HT.methodHead + p [] [] + (H.RequestBodyBS B.empty) protect' return $ peekRev hs where peekRev = B.tail . B.init . fromJust . lookup "Etag" -- | Brain-free version of 'couchRev'. If document absent, -- just return 'B.empty'. -couchRev' :: MonadCouch m =>- Path +couchRev' :: MonadCouch m => + Path -- ^ Correct 'Path' with escaped fragments. -> ResourceT m Revision couchRev' p = catch (couchRev p) handler404 @@ -57,43 +59,46 @@ -- | Delete the given revision of the object. couchDelete :: MonadCouch m => - Path - -> Revision + Path -- ^ Correct 'Path' with escaped fragments.+ -> Revision -- ^ Revision -> ResourceT m () -couchDelete p r = couch' HT.methodDelete p [] [("rev", Just r)] - (H.RequestBodyBS B.empty) - >> return () +couchDelete p r = void $+ couch methodDelete p + [] [("rev", Just r)]+ (H.RequestBodyBS B.empty) protect' ------------------------------------------------------------------------------ -- with converter ------------------------------------------------------------------------------ -- | Load CouchDB document and parse it with given parser. -couchGetWith :: MonadCouch m =>- (A.Value -> A.Result a) -- ^ Parser - -> Path -- ^ Path - -> Query -- ^ Query +couchGetWith :: MonadCouch m => + (A.Value -> A.Result a) -- ^ Parser+ -> Path -- ^ Correct 'Path' with escaped fragments. + -> Query -- ^ Query -> ResourceT m (Revision, a) couchGetWith f p q = do - H.Response _ _ bsrc <- couch' HT.methodGet p [] q - (H.RequestBodyBS B.empty) + H.Response _ _ bsrc <- couch HT.methodGet + p [] q + (H.RequestBodyBS B.empty) protect' j <- bsrc $$ CA.sinkParser A.json A.String r <- lift $ either resourceThrow return $ extractField "_rev" j o <- lift $ jsonToTypeWith f j return (TE.encodeUtf8 r, o) -- | Put document, with given encoder -couchPutWith :: MonadCouch m =>- (a -> BL.ByteString) -- ^ Encoder- -> Path -- ^ Document path. - -> Revision -- ^ Document revision. For new docs provide - -- ^ empty string. - -> Query -- ^ Query arguments.- -> a -- ^ The object to store.- -> ResourceT m Revision-couchPutWith f p r q val = do - H.Response _ _ bsrc <- couch' HT.methodPut p (ifMatch r) q - (H.RequestBodyLBS $ f val) +couchPutWith :: MonadCouch m => + (a -> BL.ByteString) -- ^ Encoder+ -> Path -- ^ Correct 'Path' with escaped fragments.+ -> Revision -- ^ Document revision. For new docs provide + -- ^ empty string.+ -> Query -- ^ Query arguments. + -> a -- ^ The object to store.+ -> ResourceT m Revision+couchPutWith f p r q val = do+ H.Response _ _ bsrc <- couch HT.methodPut + p (ifMatch r) q + (H.RequestBodyLBS $ f val) protect' j <- bsrc $$ CA.sinkParser A.json lift $ either resourceThrow return $ extractRev j where @@ -103,11 +108,11 @@ -- | \"Don't care\" version of version of 'couchPutWith'. Stores -- document only if it not exists. couchPutWith_ :: MonadCouch m => - (a -> BL.ByteString) -- ^ Encoder - -> Path -- ^ Document path. - -> HT.Query -- ^ Query arguments. - -> a -- ^ The object to store. - -> ResourceT m Revision + (a -> BL.ByteString) -- ^ Encoder + -> Path -- ^ Correct 'Path' with escaped fragments. + -> HT.Query -- ^ Query arguments. + -> a -- ^ The object to store. + -> ResourceT m Revision couchPutWith_ f p q val = do rev <- couchRev' p if rev == "" @@ -116,11 +121,11 @@ -- | Brute force version of 'couchPutWith'. couchPutWith' :: MonadCouch m => - (a -> BL.ByteString) -- ^ Encoder - -> Path -- ^ Document path. - -> HT.Query -- ^ Query arguments. - -> a -- ^ The object to store. - -> ResourceT m Revision + (a -> BL.ByteString) -- ^ Encoder + -> Path -- ^ Correct 'Path' with escaped fragments. + -> HT.Query -- ^ Query arguments. + -> a -- ^ The object to store. + -> ResourceT m Revision couchPutWith' f p q val = do rev <- couchRev' p couchPutWith f p rev q val
src/Database/CouchDB/Conduit/Internal/Parser.hs view
@@ -9,7 +9,8 @@ import qualified Data.HashMap.Lazy as M import qualified Data.Aeson as A -import Database.CouchDB.Conduit +import Database.CouchDB.Conduit.Internal.Connection + (CouchError(..), Revision) extractField :: T.Text -> A.Value -> Either CouchError A.Value extractField s (A.Object o) = @@ -31,7 +32,7 @@ -> A.Value -> m a jsonToTypeWith f j = case f j of - A.Error e -> resourceThrow $ CouchInternalError $ BU8.fromString $ - ("Error parsing json: " ++ e) + A.Error e -> resourceThrow $ CouchInternalError $ + BU8.fromString ("Error parsing json: " ++ e) A.Success o -> return o
src/Database/CouchDB/Conduit/Internal/View.hs view
@@ -6,7 +6,8 @@ import qualified Data.Conduit.List as CL (mapM) import Data.ByteString.Char8 (pack) -import Database.CouchDB.Conduit +import Database.CouchDB.Conduit.Internal.Connection + (CouchError(..)) -- | Convert CouchDB view row or row value from 'Database.CouchDB.Conduit.View' -- to concrete type.
src/Database/CouchDB/Conduit/LowLevel.hs view
@@ -6,7 +6,6 @@ module Database.CouchDB.Conduit.LowLevel ( CouchResponse, couch, - couch', protect, protect' ) where @@ -32,7 +31,7 @@ import qualified Network.HTTP.Conduit as H import qualified Network.HTTP.Types as HT -import Database.CouchDB.Conduit +import Database.CouchDB.Conduit.Internal.Connection -- | CouchDB response type CouchResponse m = H.Response (Source m B.ByteString) @@ -43,21 +42,21 @@ -- attachments that are not in JSON format. couch :: MonadCouch m => HT.Method -- ^ Method - -> (Path -> Path) -- ^ Path creation function + -> Path -- ^ Correct 'Path' with escaped fragments. -> HT.RequestHeaders -- ^ Headers -> HT.Query -- ^ Query args -> H.RequestBody m -- ^ Request body -> (CouchResponse m -> ResourceT m (CouchResponse m)) -- ^ Protect function. See 'protect' -> ResourceT m (CouchResponse m) -couch meth pathFn hdrs qs reqBody protectFn = do +couch meth path hdrs qs reqBody protectFn = do conn <- lift couchConnection let req = H.def { H.method = meth , H.host = couchHost conn , H.requestHeaders = hdrs , H.port = couchPort conn - , H.path = pathFn $ couchDB conn + , H.path = path , H.queryString = HT.renderQuery False qs , H.requestBody = reqBody , H.checkStatus = const . const $ Nothing } @@ -66,23 +65,6 @@ (couchLogin conn) (couchPass conn) req res <- H.http req' (fromJust $ couchManager conn) protectFn res - --- | Simplified version of 'couch'. This version uses standart path --- creation and protect functions. -couch' :: MonadCouch m => - HT.Method -- ^ Method - -> Path -- ^ Path - -> HT.RequestHeaders -- ^ Headers - -> HT.Query -- ^ Query args - -> H.RequestBody m -- ^ Request body - -> ResourceT m (CouchResponse m) -couch' meth p hdrs qs reqBody = - couch meth - (\dbP -> B.intercalate "/" . filter (/="") $ [dbP, p]) - hdrs - qs - reqBody - protect' -- | Protect 'H.Response' from bad status codes. If status code in list -- of status codes - just return response. Otherwise - throw 'CouchError'.
src/Database/CouchDB/Conduit/View.hs view
@@ -31,8 +31,8 @@ import qualified Network.HTTP.Conduit as H import qualified Network.HTTP.Types as HT -import Database.CouchDB.Conduit -import Database.CouchDB.Conduit.LowLevel (couch') +import Database.CouchDB.Conduit.Internal.Connection +import Database.CouchDB.Conduit.LowLevel (couch, protect') ----------------------------------------------------------------------------- -- Running @@ -56,45 +56,47 @@ -- > runCouch def {couchDB="mydb"} $ do -- > -- > -- Print all upon receipt. --- > src <- couchView "mydesign" "myview" [] +-- > src <- couchView "mydb" "mydesign" "myview" [] -- > src $$ CL.mapM_ (liftIO . print) -- > -- > -- ... Or extract row value and consume --- > src' <- couchView "mydesign" "myview" [] +-- > src' <- couchView "mydb" "mydesign" "myview" [] -- > res <- src' $= rowValue $$ CL.consume couchView :: MonadCouch m => - Path -- ^ Design document - -> Path -- ^ View name+ Path -- ^ Database + -> Path -- ^ Design document + -> Path -- ^ View name -> HT.Query -- ^ Query parameters -> ResourceT m (Source m A.Object)-couchView designDocName viewName q = do - H.Response _ _ bsrc <- couch' HT.methodGet fullPath [] q - (H.RequestBodyBS B.empty) +couchView db designDocName viewName q = do+ H.Response _ _ bsrc <- couch HT.methodGet fullPath [] q + (H.RequestBodyBS B.empty) protect' return $ bsrc $= conduitCouchView where - fullPath = B.concat ["_design/", designDocName, "/_view/", viewName] + fullPath = mkPath [db, "_design", designDocName, "_view", viewName] --- | Brain-free version of 'couchView'. Takes 'Sink' to consume response.--- +-- | Brain-free version of 'couchView'. Takes 'Sink' to consume response. +-- -- > runCouch def {couchDB="mydb"} $ do --- >--- > -- Print all upon receipt. +-- > +-- > -- Print all upon receipt. -- > couchView' "mydesign" "myview" [] $ CL.mapM_ (liftIO . print) -- > -- > -- ... Or extract row value and consume --- > res <- couchView' "mydesign" "myview" [] $ rowValue =$ CL.consume-couchView' :: MonadCouch m => - Path -- ^ Design document+-- > res <- couchView' "mydesign" "myview" [] $ rowValue =$ CL.consume +couchView' :: MonadCouch m =>+ Path -- ^ Database + -> Path -- ^ Design document -> Path -- ^ View name -> HT.Query -- ^ Query parameters -> Sink A.Object m a -- ^ Sink for handle view rows. -> ResourceT m a -couchView' designDocName viewName q sink = do - H.Response _ _ bsrc <- couch' HT.methodGet fullPath [] q - (H.RequestBodyBS B.empty) +couchView' db designDocName viewName q sink = do + H.Response _ _ bsrc <- couch HT.methodGet fullPath [] q + (H.RequestBodyBS B.empty) protect' bsrc $= conduitCouchView $$ sink where - fullPath = mkPath ["_design", designDocName, "_view", viewName] + fullPath = mkPath [db, "_design", designDocName, "_view", viewName] -- | Conduit for extract \"value\" field from CouchDB view row. rowValue :: ResourceIO m => Conduit A.Object m A.Value
test/Database/CouchDB/Conduit/Test/Base.hs view
@@ -31,16 +31,15 @@ -- | Just connect case_justConnect :: Assertion case_justConnect = runCouch def $ do - H.Response (HT.Status sc _) _h _bsrc <- couch' HT.methodGet "" [] [] - (H.RequestBodyBS B.empty) + H.Response (HT.Status sc _) _h _bsrc <- couch HT.methodGet "" [] [] + (H.RequestBodyBS B.empty) protect' liftIO $ sc @=? 200 -- | Put and delete case_dbPut :: Assertion case_dbPut = runCouch def {couchLogin = login, - couchPass=pass, - couchDB="cdbc_dbputdel"} $ do - couchPutDB_ - couchDeleteDB + couchPass=pass} $ do + couchPutDB_ "cdbc_dbputdel" + couchDeleteDB "cdbc_dbputdel"
test/Database/CouchDB/Conduit/Test/Explicit.hs view
@@ -39,26 +39,26 @@ case_justPutGet :: Assertion case_justPutGet = bracket_ setup teardown $ - runCouch (conn dbName) $ do - rev <- couchPut "doc-just" "" [] $ TestDoc "doc" 1 "1" - rev' <- couchPut "doc-just" rev [] $ TestDoc "doc" 2 "2" - rev'' <- couchRev "doc-just" + runCouch conn $ do + rev <- couchPut dbName "doc-just" "" [] $ TestDoc "doc" 1 "1" + rev' <- couchPut dbName "doc-just" rev [] $ TestDoc "doc" 2 "2" + rev'' <- couchRev dbName "doc-just" liftIO $ rev' @=? rev'' - couchDelete "doc-just" rev'' + couchDelete dbName "doc-just" rev'' case_massFlow :: Assertion case_massFlow = bracket_ setup teardown $ - runCouch (conn dbName) $ do + runCouch conn $ do revs <- mapM (\n -> - couchPut (docn n) "" [] $ TestDoc "doc" n $ show n + couchPut dbName (docn n) "" [] $ TestDoc "doc" n $ show n ) [1..100] liftIO $ length revs @=? 100 - revs' <- mapM (\n -> couchRev $ docn n) [1..100] + revs' <- mapM (\n -> couchRev dbName $ docn n) [1..100] liftIO $ revs @=? revs' liftIO $ length revs' @=? 100 mapM_ (\(n,r) -> - couchDelete (docn n) r) $ zip [1..100] revs' + couchDelete dbName (docn n) r) $ zip [1..100] revs' where docn n = fromString $ "doc-" ++ show (n :: Int) @@ -66,17 +66,17 @@ case_massIter :: Assertion case_massIter = bracket_ setup teardown $ - runCouch (conn dbName) $ + runCouch conn $ mapM_ (\n -> do let name = docn n let d = TestDoc "doc" n $ show n - rev <- couchPut name "" [] d - couchDelete (docn n) rev - _ <- couchPut name "" [] d - rev'' <- couchPut' name [] d - (_, d') <- couchGet name [] + rev <- couchPut dbName name "" [] d + couchDelete dbName (docn n) rev + _ <- couchPut dbName name "" [] d + rev'' <- couchPut' dbName name [] d + (_, d') <- couchGet dbName name [] liftIO $ d @=? d' - couchDelete (docn n) rev'' + couchDelete dbName (docn n) rev'' ) [1..100] where docn n = fromString $ "doc-" ++ show (n :: Int)
test/Database/CouchDB/Conduit/Test/Generic.hs view
@@ -32,26 +32,26 @@ case_justPutGet :: Assertion case_justPutGet = bracket_ setup teardown $ - runCouch (conn dbName) $ do - rev <- couchPut "doc-just" "" [] $ TestDoc "doc" 1 "1" - rev' <- couchPut "doc-just" rev [] $ TestDoc "doc" 2 "2" - rev'' <- couchRev "doc-just" + runCouch conn $ do + rev <- couchPut dbName "doc-just" "" [] $ TestDoc "doc" 1 "1" + rev' <- couchPut dbName "doc-just" rev [] $ TestDoc "doc" 2 "2" + rev'' <- couchRev dbName "doc-just" liftIO $ rev' @=? rev'' - couchDelete "doc-just" rev'' + couchDelete dbName "doc-just" rev'' case_massFlow :: Assertion case_massFlow = bracket_ setup teardown $ - runCouch (conn dbName) $ do + runCouch conn $ do revs <- mapM (\n -> - couchPut (docn n) "" [] $ TestDoc "doc" n $ show n + couchPut dbName (docn n) "" [] $ TestDoc "doc" n $ show n ) [1..100] liftIO $ length revs @=? 100 - revs' <- mapM (\n -> couchRev $ docn n) [1..100] + revs' <- mapM (\n -> couchRev dbName $ docn n) [1..100] liftIO $ revs @=? revs' liftIO $ length revs' @=? 100 mapM_ (\(n,r) -> - couchDelete (docn n) r) $ zip [1..100] revs' + couchDelete dbName (docn n) r) $ zip [1..100] revs' where docn n = fromString $ "doc-" ++ show (n :: Int) @@ -59,17 +59,17 @@ case_massIter :: Assertion case_massIter = bracket_ setup teardown $ - runCouch (conn dbName) $ + runCouch conn $ mapM_ (\n -> do let name = docn n let d = TestDoc "doc" n $ show n - rev <- couchPut name "" [] d - couchDelete (docn n) rev - _ <- couchPut name "" [] d - rev'' <- couchPut' name [] d - (_, d') <- couchGet name [] + rev <- couchPut dbName name "" [] d + couchDelete dbName (docn n) rev + _ <- couchPut dbName name "" [] d + rev'' <- couchPut' dbName name [] d + (_, d') <- couchGet dbName name [] liftIO $ d @=? d' - couchDelete (docn n) rev'' + couchDelete dbName (docn n) rev'' ) [1..100] where docn n = fromString $ "doc-" ++ show (n :: Int)
test/Database/CouchDB/Conduit/Test/Util.hs view
@@ -10,14 +10,13 @@ import CouchDBAuth setupDB :: ByteString -> IO () -setupDB n = runCouch (conn n) $ couchPutDB_ +setupDB n = runCouch conn $ couchPutDB_ n tearDB :: ByteString -> IO () -tearDB n = runCouch (conn n) $ couchDeleteDB +tearDB n = runCouch conn $ couchDeleteDB n -- | Connection connection. See readme -conn :: Path -> CouchConnection -conn db = def { - couchDB = db, +conn :: CouchConnection +conn = def { couchLogin = login, couchPass = pass}
test/Database/CouchDB/Conduit/Test/View.hs view
@@ -52,24 +52,24 @@ case_createView :: Assertion case_createView = bracket_ (setupDB db) - (tearDB db) $ runCouch (conn db) $ do - rev <- couchPutView' "mydesign" "myview" + (tearDB db) $ runCouch conn $ do + rev <- couchPutView' db "mydesign" "myview" "function(doc){emit(null, doc);}" Nothing - rev' <- CCG.couchRev "_design/mydesign" + rev' <- couchRev db "_design/mydesign" liftIO $ rev @=? rev' where db = "cdbc_test_view_create" case_bigValues :: Assertion case_bigValues = bracket_ - (runCouch (conn db) $ do - couchPutDB_ - _ <- couchPutView' "mydesign" "myview" + (runCouch conn $ do + couchPutDB_ db + _ <- couchPutView' db "mydesign" "myview" "function(doc){emit(doc.intV, doc);}" Nothing - mapM_ (\n -> CCG.couchPut' (docName n) [] $ doc n) [1..20] + mapM_ (\n -> CCG.couchPut' db (docName n) [] $ doc n) [1..20] ) - (tearDB db) $ runCouch (conn db) $ do - res <- couchView' "mydesign" "myview" [] $ + (tearDB db) $ runCouch conn $ do + res <- couchView' db "mydesign" "myview" [] $ (rowValue =$= CCG.toType) =$ CL.consume mapM_ (\(a, b) -> liftIO $ a @=? doc b) $ zip res [1..20] where @@ -80,14 +80,14 @@ case_withReduce :: Assertion case_withReduce = bracket_ - (runCouch (conn db) $ do - couchPutDB_ - _ <- couchPutView' "mydesign" "myview" + (runCouch conn $ do + couchPutDB_ db + _ <- couchPutView' db "mydesign" "myview" "function(doc){emit(doc.intV, doc.intV);}" $ Just "function(keys, values){return sum(values);}" - mapM_ (\n -> CCG.couchPut' (docName n) [] $ doc n) [1..20]) - (tearDB db) $ runCouch (conn db) $ do - res <- couchView' "mydesign" "myview" [] $ + mapM_ (\n -> CCG.couchPut' db (docName n) [] $ doc n) [1..20]) + (tearDB db) $ runCouch conn $ do + res <- couchView' db "mydesign" "myview" [] $ (rowValue =$= CCG.toType) =$ CL.consume liftIO $ res @=? [ReducedView 210] where