Source

snaplet-mongoDB / src / Snap / Snaplet / MongoDB.hs

Kamil Ciemniewsk… 7795a1e 






Kamil Ciemniewsk… c9bd997 


Kamil Ciemniewsk… 7795a1e 

root df0ea16 




















Kamil Ciemniewsk… c9bd997 





root df0ea16 







Kamil Ciemniewsk… 7795a1e 




root df0ea16 







Kamil Ciemniewsk… c9bd997 






root df0ea16 









Kamil Ciemniewsk… 7795a1e 
Kamil Ciemniewsk… a5adfaf 


Kamil Ciemniewsk… 7795a1e 
















Kamil Ciemniewsk… a5adfaf 
Kamil Ciemniewsk… 7795a1e 


root df0ea16 
Kamil Ciemniewsk… 7795a1e 





Kamil Ciemniewsk… 4d99e54 

Kamil Ciemniewsk… 7795a1e 
Kamil Ciemniewsk… ae922eb 

Kamil Ciemniewsk… 7795a1e 
root df0ea16 
Kamil Ciemniewsk… 7795a1e 

root df0ea16 
Kamil Ciemniewsk… 7795a1e 

root df0ea16 

Kamil Ciemniewsk… 7795a1e 
root df0ea16 

Kamil Ciemniewsk… 7795a1e 
root df0ea16 

Kamil Ciemniewsk… 7795a1e 
root df0ea16 

Kamil Ciemniewsk… 7795a1e 


Kamil Ciemniewsk… 4d99e54 
Kamil Ciemniewsk… ae922eb 

root df0ea16 
Kamil Ciemniewsk… ae922eb 






root df0ea16 
Kamil Ciemniewsk… ae922eb 



Kamil Ciemniewsk… 7795a1e 

root df0ea16 



























































































Kamil Ciemniewsk… 7795a1e 
root df0ea16 






Kamil Ciemniewsk… 7795a1e 

root df0ea16 


Kamil Ciemniewsk… 7795a1e 

root df0ea16 






























Kamil Ciemniewsk… c9bd997 

































root df0ea16 



module Snap.Snaplet.MongoDB
  ( 
    MonadMongoDB(..)
    
  , MongoDBSnaplet(..)
  , HasMongoDBState(..)

  , MongoValue (..)
  , MongoEntity (..)

  , mongoDBInit

  , insert
  , insertMany
  , insertWith
  , get
  , getMany
  , delete
  , deleteWhere
  , select
  , selectOne
  , count
  , save
  , update
  , updateWhere
   
  , filters
  , updates
   
  , offset
  , limit
  , orderAsc
  , orderDesc

  , objid2bs
  , bs2objid
  , bs2objid'
  , bs2cs
  , getObjId
   
  , module Snap.Snaplet.MongoDB.FilterOps
  , module Snap.Snaplet.MongoDB.MongoEntity
  , module Snap.Snaplet.MongoDB.MongoValue
  , module Snap.Snaplet.MongoDB.Template
  , module Snap.Snaplet.MongoDB.Parse
  , MongoDB.Action
  , MongoDB.MonadIO'
  ) where

import           Snap.Core
import           Snap.Snaplet

import           Prelude hiding (lookup, or)
import           Control.Applicative
import           Control.Monad
import           Control.Monad.MVar
import           Control.Monad.Error
import           Data.Bson ((=:))
import qualified Data.Bson as BSON
import qualified Data.UString as US
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as B8
import qualified Data.CompactString.Internal as CSI
import           Data.Maybe (catMaybes, fromJust)
import qualified Data.Map as Map
import           Numeric (showHex, readHex)
import           Safe
import           Snap.Snaplet.MongoDB.FilterOps
import           Snap.Snaplet.MongoDB.MongoEntity
import           Snap.Snaplet.MongoDB.MongoValue
import           Snap.Snaplet.MongoDB.Template
import           Snap.Snaplet.MongoDB.Parse
import qualified Database.MongoDB as MongoDB
import qualified Database.MongoDB.Internal.Util as MongoDB
import           Database.MongoDB.Query (Action, Failure(..), Database, access, master, AccessMode(..))
import qualified Database.MongoDB.Connection as MongoDB
import qualified System.IO.Pool as MPool

import           Control.Monad.CatchIO hiding (Handler)

instance Exception String

------------------------------------------------------------------------------
-- | The 'MonadMongoDB' class. Minimal complete definition:
class MonadIO m => MonadMongoDB m where

  ----------------------------------------------------------------------------
  -- | Run given MongoDB action against the database
  withDB       :: Action IO a -> m (Either Failure a)
  withDBUnsafe :: Action IO a -> m (Either Failure a)



  ----------------------------------------------------------------------------
  -- | Same as 'withDB' but calls 'error' if there is an exception
  withDB' :: Action IO a -> m a
  withDB' run = do
    r <- withDB run 
    either (throw . show) return r


data MongoDBSnaplet = MongoDBSnaplet {
  connPoll :: MPool.Pool IOError MongoDB.Pipe,
  appDatabase :: Database
}

------------------------------------------------------------------------------
-- |
class HasMongoDBState s where
    getMongoDBState :: Handler a s MongoDBSnaplet
    setMongoDBState :: MongoDBSnaplet -> Handler a s ()

    --modifyMongoDBState :: (MongoDBSnaplet -> MongoDBSnaplet) -> s -> s
    --modifyMongoDBState s = setMongoDBState s getMongoDBState

mongoDBInit :: MongoDB.Host -> Int -> US.UString -> SnapletInit b MongoDBSnaplet
mongoDBInit h n db =
  makeSnaplet "mongoDB" "MongoDB abstraction" Nothing $ do
    pool <- liftIO $ MPool.newPool (factoryForHost h) n
    return $ MongoDBSnaplet pool (db)
  where
    factoryForHost :: MongoDB.Host -> MPool.Factory IOError MongoDB.Pipe
    factoryForHost host = MPool.Factory (newRes h) (killRes) (isResExpired)
    
    newRes :: MongoDB.Host -> ErrorT IOError IO MongoDB.Pipe
    newRes = MongoDB.connect
    
    killRes :: MongoDB.Pipe -> IO ()
    killRes = MongoDB.close
    
    isResExpired :: MongoDB.Pipe -> IO Bool
    isResExpired = MongoDB.isClosed

------------------------------------------------------------------------------
-- |
instance HasMongoDBState s => MonadMongoDB (Handler a s) where
  withDB run = do
    (MongoDBSnaplet pool db) <- getMongoDBState
    epipe <- liftIO $ runErrorT $ MPool.aResource pool
    case epipe of
      Left err -> return $ Left $ ConnectionFailure err
      Right pipe -> do
		liftIO (access pipe master db run)

  withDBUnsafe run = do
    (MongoDBSnaplet pool db) <- getMongoDBState
    epipe <- liftIO $ runErrorT $ MPool.aResource pool
    case epipe of
      Left err -> return $ Left $ ConnectionFailure err
      Right pipe -> do
		liftIO (access pipe UnconfirmedWrites db run)


-- | Given a type in the type class 'MongoEntity', insert this as a new document in the database. The function will
-- yield the unique key for the new document.
insert :: (MongoDB.MonadIO' m, MongoEntity a) => a -> MongoDB.Action m (Key a)
insert obj = do
  (BSON.ObjId objId) <- MongoDB.insert (collectionName obj) (fromDocument $ encodeDocument obj)
  pure $! toKey objId


-- | Similar to 'insert', this function inserts multiple documents into the databsae, yielding a unique key for each.
insertMany :: (Functor m, MongoDB.MonadIO' m, MongoEntity a) => [a] -> MongoDB.Action m [Key a]
insertMany []   = pure []
insertMany objs = do
  ids <- MongoDB.insertMany (collectionName $ head objs) (map (fromDocument . encodeDocument) objs)
  forM ids $ \x -> case x of
                    BSON.ObjId objId -> pure $! toKey objId
                    _                -> throwError $ MongoDB.QueryFailure 1000 "Expected object ID as result of 'insertMany'"

-- | Similar to 'insert', but allows you to specify the ID with which the object is to be created.
insertWith :: (Functor m, MongoDB.MonadIO' m, MongoEntity a) => Key a -> a -> MongoDB.Action m (Key a)
insertWith key obj = do
  (BSON.ObjId objId) <- MongoDB.insert (collectionName obj) (("_id" =: fromKey key) : (fromDocument $ encodeDocument obj))
  pure $! toKey objId


-- | Given a unique key for a 'MongoEntity', yield that entity. If no document with the specified ID was found, the
-- function will yield @Nothing@.
get :: (Functor m, MongoDB.MonadIO' m, MongoEntity a) => Key a -> MongoDB.Action m (Maybe a)
get key = do
  let collection = collectionName (dummyFromKey key)
  result <- MongoDB.findOne (MongoDB.select ["_id" =: fromKey key] collection)
  case result of
    Just document -> do
      eObj <- runErrorT (decodeDocument $ toDocument document)
      case eObj of
        Left message -> throwError $ MongoDB.QueryFailure 1001 message
        Right    obj -> pure (Just obj)
    Nothing ->
      pure Nothing

-- | The application of 'get' to many keys.
getMany :: (Functor m, MongoDB.MonadIO' m, MongoEntity a) => [Key a] -> MongoDB.Action m [Maybe a]
getMany = mapM get

-- | Delete the document with the specified key.
delete :: (Functor m, MongoDB.MonadIO' m, MongoEntity a) => Key a -> MongoDB.Action m ()
delete key = do
  let collection = collectionName (dummyFromKey key)
  MongoDB.deleteOne (MongoDB.select ["_id" =: fromKey key] collection)

deleteWhere :: (Functor m, MongoDB.MonadIO' m, MongoEntity a) => Document a -> MongoDB.Action m ()
deleteWhere doc = do
  MongoDB.delete (MongoDB.select (fromDocument doc) (collectionName (dummyFromDocument doc)))

count :: (Functor m, MongoDB.MonadIO' m, MongoEntity a) => Document a -> MongoDB.Action m Int
count doc = do
  MongoDB.count (MongoDB.select (fromDocument doc) (collectionName (dummyFromDocument doc)))

type SelectorOption = MongoDB.Query -> MongoDB.Query

offset :: Int -> SelectorOption
offset n q = q { MongoDB.skip = fromIntegral n }

limit :: Int -> SelectorOption
limit n q = q { MongoDB.limit = fromIntegral n }

orderAsc :: (MongoEntity a, MongoValue v) => (v -> Filter a) -> SelectorOption
orderAsc f q = q { MongoDB.sort = (filterFieldName (f undefined) =: (1 :: Int)) : MongoDB.sort q }

orderDesc :: (MongoEntity a, MongoValue v) => (v -> Filter a) -> SelectorOption
orderDesc f q = q { MongoDB.sort = (filterFieldName (f undefined) =: ((-1) :: Int)) : MongoDB.sort q }


-- | Select all matching documents from a collection in the database. This function yields a list of pairs of the unique
-- key and the 'MongoEntity' for each matching document in the collection.
--
-- The query can be made using either the quasi-quoted mongoDB query format:
--
-- @select [mongo| { userName: #{name}, userPassword: #{password} |] []@
--
-- Or using the filter operations:
--
-- @select (filters [UserName `eq` name, UserPassword `eq` password]) []@
--
-- /Note/: in the quasi-quotation the names of the fields correspond to the fields stored in the /database/, rather than
-- the fields of the record structure.
--
select :: (Functor m, MonadControlIO m, MongoEntity a) => Document a -> [SelectorOption] -> MongoDB.Action m [(Key a, a)]
select document options = do
  cursor  <- MongoDB.find (foldr ($) (MongoDB.select (fromDocument document) (collectionName (dummyFromDocument document))) options)
  objects <- MongoDB.rest cursor
  values  <- mapM fetchKeyValue objects
  return $ catMaybes values

-- | Similar to 'select', only yielding a single result.
selectOne :: (Functor m, MonadControlIO m, MongoEntity a) => Document a -> [SelectorOption] -> MongoDB.Action m (Maybe (Key a, a))
selectOne document options = do
  result <- MongoDB.findOne (foldr ($) (MongoDB.select (fromDocument document) (collectionName (dummyFromDocument document))) options)
  case result of
    Just obj -> fetchKeyValue obj
    Nothing  -> pure Nothing


save :: (Functor m, MongoDB.MonadIO' m, MongoEntity a) => Key a -> a -> MongoDB.Action m ()
save key obj = do
  MongoDB.save (collectionName obj) (("_id" := BSON.ObjId (fromKey key)) : (fromDocument $ encodeDocument obj))


update :: (Functor m, MongoDB.MonadIO' m, MongoEntity a) => Key a -> Document a -> MongoDB.Action m ()
update key updateDoc = do
  MongoDB.modify (MongoDB.select [ "_id" =: (fromKey key) ] (collectionName (dummyFromKey key))) (fromDocument updateDoc)

updateWhere :: (Functor m, MongoDB.MonadIO' m, MongoEntity a) => Document a -> Document a -> MongoDB.Action m ()
updateWhere document updateDoc = do
  MongoDB.modify (MongoDB.select (fromDocument document) (collectionName (dummyFromDocument document))) (fromDocument updateDoc)


filters :: (MongoEntity a) => [FilterOp] -> Document a
filters = toDocument

updates :: (MongoEntity a) => [UpdateOp] -> Document a
updates = toDocument


fetchKeyValue :: (Functor m, MonadControlIO m, MongoEntity a) => BSON.Document -> MongoDB.Action m (Maybe (Key a, a))
fetchKeyValue doc = do
  case BSON.look "_id" doc of
    Just i ->
      case i of
        BSON.ObjId objId -> do
          eObj <- runErrorT (decodeDocument $ toDocument doc)
          case eObj of
            Left message -> throwError $ MongoDB.QueryFailure 1001 message
            Right    obj -> pure $ Just (toKey objId, obj)
        _ -> throwError $ MongoDB.QueryFailure 1000 "Expected _id field to be an ObjectId in result of selection"
    Nothing ->
      throwError $ MongoDB.QueryFailure 1000 "Expected to find an _id field in result of selection"


------------------------------------------------------------------------------
-- | Convert 'ObjectId' into 'ByteString'
objid2bs :: ObjectId -> BS.ByteString
objid2bs (Oid a b) = B8.pack . showHex a . showChar '-' . showHex b $ ""


------------------------------------------------------------------------------
-- | Convert 'ByteString' into 'ObjectId'
bs2objid :: BS.ByteString -> Maybe ObjectId
bs2objid bs = do
  case B8.split '-' bs of
    (a':b':_) -> do
      a <- fmap fst . headMay . readHex . B8.unpack $ a'
      b <- fmap fst . headMay . readHex . B8.unpack $ b'
      return $ Oid a b
    _ -> Nothing

------------------------------------------------------------------------------
-- | Like 'bs2objid', but may blow with an error if the 'ByteString' can't be
-- converted to an 'ObjectId'
bs2objid' :: BS.ByteString -> ObjectId
bs2objid' = fromJust . bs2objid

bs2cs :: BS.ByteString -> US.UString
bs2cs = CSI.CS


------------------------------------------------------------------------------
-- | If the 'Document' has an 'ObjectId' in the given field, return it as
-- 'ByteString'
getObjId :: US.UString -> BSON.Document -> Maybe BS.ByteString
getObjId v d = MongoDB.lookup v d >>= fmap objid2bs


dummyFromKey :: Key a -> a
dummyFromKey _ = undefined

dummyFromDocument :: Document a -> a
dummyFromDocument _ = undefined
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.