packages feed

haskell-neo4j-client 0.3.1.0 → 0.3.1.1

raw patch · 2 files changed

+271/−2 lines, 2 filesPVP: minor bump suggested

API additions: PVP suggests at least a minor version bump

API changes (from Hackage documentation)

+ Database.Neo4j.Traversal: BreadthFirst :: TraversalOrder
+ Database.Neo4j.Traversal: DepthFirst :: TraversalOrder
+ Database.Neo4j.Traversal: In :: ConcreteDirection
+ Database.Neo4j.Traversal: NodeGlobal :: Uniqueness
+ Database.Neo4j.Traversal: NodePathUnique :: Uniqueness
+ Database.Neo4j.Traversal: Out :: ConcreteDirection
+ Database.Neo4j.Traversal: PathEnd :: !a -> Path a b
+ Database.Neo4j.Traversal: PathLink :: !a -> !b -> !(Path a b) -> Path a b
+ Database.Neo4j.Traversal: RelationshipGlobal :: Uniqueness
+ Database.Neo4j.Traversal: RelationshipPath :: Uniqueness
+ Database.Neo4j.Traversal: ReturnAll :: ReturnFilter
+ Database.Neo4j.Traversal: ReturnAllButStartNode :: ReturnFilter
+ Database.Neo4j.Traversal: TraversalDesc :: TraversalOrder -> [RelFilter] -> Maybe Uniqueness -> Either Integer Text -> Either ReturnFilter Text -> TraversalDesc
+ Database.Neo4j.Traversal: TraversalPaging :: Integer -> Integer -> TraversalPaging
+ Database.Neo4j.Traversal: data ConcreteDirection
+ Database.Neo4j.Traversal: data PagedTraversal a
+ Database.Neo4j.Traversal: data Path a b
+ Database.Neo4j.Traversal: data ReturnFilter
+ Database.Neo4j.Traversal: data TraversalDesc
+ Database.Neo4j.Traversal: data TraversalOrder
+ Database.Neo4j.Traversal: data TraversalPaging
+ Database.Neo4j.Traversal: data Uniqueness
+ Database.Neo4j.Traversal: getPagedValues :: PagedTraversal a -> [a]
+ Database.Neo4j.Traversal: instance (Eq a, Eq b) => Eq (Path a b)
+ Database.Neo4j.Traversal: instance (Ord a, Ord b) => Ord (Path a b)
+ Database.Neo4j.Traversal: instance (Show a, Show b) => Show (Path a b)
+ Database.Neo4j.Traversal: instance Default TraversalDesc
+ Database.Neo4j.Traversal: instance Default TraversalPaging
+ Database.Neo4j.Traversal: instance Eq ConcreteDirection
+ Database.Neo4j.Traversal: instance Eq ReturnFilter
+ Database.Neo4j.Traversal: instance Eq TraversalDesc
+ Database.Neo4j.Traversal: instance Eq TraversalOrder
+ Database.Neo4j.Traversal: instance Eq TraversalPaging
+ Database.Neo4j.Traversal: instance Eq Uniqueness
+ Database.Neo4j.Traversal: instance Eq a => Eq (PagedTraversal a)
+ Database.Neo4j.Traversal: instance FromJSON ConcreteDirection
+ Database.Neo4j.Traversal: instance FromJSON FullPath
+ Database.Neo4j.Traversal: instance FromJSON IdPath
+ Database.Neo4j.Traversal: instance Ord ConcreteDirection
+ Database.Neo4j.Traversal: instance Ord a => Ord (PagedTraversal a)
+ Database.Neo4j.Traversal: instance Show ConcreteDirection
+ Database.Neo4j.Traversal: instance Show ReturnFilter
+ Database.Neo4j.Traversal: instance Show TraversalDesc
+ Database.Neo4j.Traversal: instance Show TraversalOrder
+ Database.Neo4j.Traversal: instance Show TraversalPaging
+ Database.Neo4j.Traversal: instance Show Uniqueness
+ Database.Neo4j.Traversal: instance Show a => Show (PagedTraversal a)
+ Database.Neo4j.Traversal: instance ToJSON (Maybe Uniqueness)
+ Database.Neo4j.Traversal: instance ToJSON RelFilter
+ Database.Neo4j.Traversal: instance ToJSON ReturnFilter
+ Database.Neo4j.Traversal: instance ToJSON TraversalOrder
+ Database.Neo4j.Traversal: nextTraversalPage :: FromJSON a => PagedTraversal a -> Neo4j (PagedTraversal a)
+ Database.Neo4j.Traversal: pageLeaseSecs :: TraversalPaging -> Integer
+ Database.Neo4j.Traversal: pageSize :: TraversalPaging -> Integer
+ Database.Neo4j.Traversal: pagedTraversalDone :: PagedTraversal a -> Bool
+ Database.Neo4j.Traversal: pagedTraverseGetFullPath :: NodeIdentifier a => TraversalDesc -> TraversalPaging -> a -> Neo4j (PagedTraversal FullPath)
+ Database.Neo4j.Traversal: pagedTraverseGetNodes :: NodeIdentifier a => TraversalDesc -> TraversalPaging -> a -> Neo4j (PagedTraversal Node)
+ Database.Neo4j.Traversal: pagedTraverseGetPath :: NodeIdentifier a => TraversalDesc -> TraversalPaging -> a -> Neo4j (PagedTraversal IdPath)
+ Database.Neo4j.Traversal: pagedTraverseGetRels :: NodeIdentifier a => TraversalDesc -> TraversalPaging -> a -> Neo4j (PagedTraversal Relationship)
+ Database.Neo4j.Traversal: pathNodes :: Path a b -> [a]
+ Database.Neo4j.Traversal: pathRels :: Path a b -> [b]
+ Database.Neo4j.Traversal: travDepth :: TraversalDesc -> Either Integer Text
+ Database.Neo4j.Traversal: travNodeFilter :: TraversalDesc -> Either ReturnFilter Text
+ Database.Neo4j.Traversal: travOrder :: TraversalDesc -> TraversalOrder
+ Database.Neo4j.Traversal: travRelFilter :: TraversalDesc -> [RelFilter]
+ Database.Neo4j.Traversal: travUniqueness :: TraversalDesc -> Maybe Uniqueness
+ Database.Neo4j.Traversal: traverseGetFullPath :: NodeIdentifier a => TraversalDesc -> a -> Neo4j [FullPath]
+ Database.Neo4j.Traversal: traverseGetNodes :: NodeIdentifier a => TraversalDesc -> a -> Neo4j [Node]
+ Database.Neo4j.Traversal: traverseGetPath :: NodeIdentifier a => TraversalDesc -> a -> Neo4j [IdPath]
+ Database.Neo4j.Traversal: traverseGetRels :: NodeIdentifier a => TraversalDesc -> a -> Neo4j [Relationship]
+ Database.Neo4j.Traversal: type FullPath = Path Node Relationship
+ Database.Neo4j.Traversal: type IdPath = Path NodePath (RelPath, ConcreteDirection)
+ Database.Neo4j.Traversal: type RelFilter = (RelationshipType, Direction)

Files

haskell-neo4j-client.cabal view
@@ -1,5 +1,5 @@ name:                haskell-neo4j-client-version:             0.3.1.0+version:             0.3.1.1 synopsis:            A Haskell neo4j client description:              Library to interact with Neo4j databases. @@ -67,7 +67,7 @@ library   hs-source-dirs:      src   exposed-modules:     Database.Neo4j, Database.Neo4j.Graph, Database.Neo4j.Batch, Database.Neo4j.Cypher,-                       Database.Neo4j.Transactional.Cypher, Database.Neo4j.Types+                       Database.Neo4j.Transactional.Cypher, Database.Neo4j.Types, Database.Neo4j.Traversal   other-modules:       Database.Neo4j.Http, Database.Neo4j.Node, Database.Neo4j.Relationship,                        Database.Neo4j.Property, Database.Neo4j.Label, Database.Neo4j.Index, Database.Neo4j.Batch.Node,                        Database.Neo4j.Batch.Relationship, Database.Neo4j.Batch.Property,
+ src/Database/Neo4j/Traversal.hs view
@@ -0,0 +1,269 @@+{-# LANGUAGE OverloadedStrings  #-}+{-# LANGUAGE TypeSynonymInstances  #-}+{-# LANGUAGE FlexibleInstances  #-}++-- | Module to manage traversal operations++module Database.Neo4j.Traversal (+    -- * Types for configuring the traversal+    Uniqueness(..), TraversalOrder(..), ReturnFilter(..), RelFilter, TraversalDesc(..),+    -- * Additional types for configuring a paged traversal+    TraversalPaging(..),+    -- * Types used when returning paths+    ConcreteDirection(..), Path(..), IdPath, FullPath,+    -- * Types used when returning paged results+    PagedTraversal, pathNodes, pathRels, +    -- * Traversal operations+    traverseGetNodes, traverseGetRels, traverseGetPath, traverseGetFullPath,+    -- * Paged traversal operations+    pagedTraverseGetNodes, pagedTraverseGetRels, pagedTraverseGetPath, pagedTraverseGetFullPath,+    -- * Operations to handle paged results+    getPagedValues, nextTraversalPage, pagedTraversalDone) where++import Data.Default++import Control.Applicative+import Control.Exception.Base (throw, catch)+import Control.Monad (mzero)+import Data.Aeson ((.=), (.:), (.:?))+import Data.List (find)+import Data.Maybe (fromMaybe)+import Data.String (fromString)++import qualified Data.Aeson as J+import qualified Data.ByteString.Char8 as S+import qualified Data.ByteString.Lazy as L+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE+import qualified Network.HTTP.Types as HT+import qualified Network.URI as NU++import Database.Neo4j.Types+import Database.Neo4j.Http++-- | Different types of uniqueness calculations for a traversal+data Uniqueness = NodeGlobal | RelationshipGlobal | NodePathUnique | RelationshipPath deriving (Eq, Show)++-- | Traversal mode+data TraversalOrder = BreadthFirst | DepthFirst deriving (Eq, Show)++-- | Built-in return filters+data ReturnFilter = ReturnAll | ReturnAllButStartNode deriving (Eq, Show)++type RelFilter = (RelationshipType, Direction)++-- | Type containing all info describing a traversal request+data TraversalDesc = TraversalDesc {+    travOrder :: TraversalOrder,+    travRelFilter :: [RelFilter],+    travUniqueness :: Maybe Uniqueness,+    travDepth :: Either Integer T.Text,+    travNodeFilter :: Either ReturnFilter T.Text} deriving (Eq, Show)++-- | Get the body for a traversal request+traversalReqBody :: TraversalDesc -> L.ByteString+traversalReqBody (TraversalDesc ord relfilt uniq depth nodefilt) = J.encode $ J.object [+        "order" .= J.toJSON ord,+        "relationships" .= relfilt,+        "uniqueness" .= uniq,+        depthField depth,+        "return_filter" .= returnField nodefilt+        ]+    where depthField (Left lvl) = "max_depth" .= lvl+          depthField (Right desc) = "prune_evaluator" .= J.object ["language" .= ("javascript" :: T.Text),+                                                                   "body" .= desc]+          returnField (Left filt) = J.object ["language" .= ("builtin" :: T.Text), "name" .= filt]+          returnField (Right desc) = J.object ["language" .= ("javascript" :: T.Text), "body" .= desc]++-- | How to codify traversalDesc values into JSON+instance J.ToJSON TraversalOrder where+    toJSON BreadthFirst = J.String "breadth_first"+    toJSON DepthFirst = J.String "depth_first"++-- | How to codify ReturnFilter values into JSON+instance J.ToJSON ReturnFilter where+    toJSON ReturnAll = J.String "all"+    toJSON ReturnAllButStartNode = J.String "all_but_start_node"++-- | How to codify RelFilter values into JSON+instance J.ToJSON RelFilter where+    toJSON (rType, dir) = J.object ["direction" .= dirToJson dir, "type" .= J.toJSON rType]+        where dirToJson Outgoing = J.String "out"+              dirToJson Incoming = J.String "in"+              dirToJson Any = J.String "all"++-- | How to codify uniqueness values into JSON+instance J.ToJSON (Maybe Uniqueness) where+    toJSON Nothing = J.String "none"+    toJSON (Just NodeGlobal) = J.String "node_global"+    toJSON (Just RelationshipGlobal) = J.String "relationship_global"+    toJSON (Just NodePathUnique) = J.String "node_path"+    toJSON (Just RelationshipPath) = J.String "relationship_path"++instance Default TraversalDesc where+    def = TraversalDesc {travOrder = BreadthFirst, travRelFilter = [], travUniqueness = Nothing,+                         travDepth = Left 1, travNodeFilter = Left ReturnAll}++-- | Description of a traversal paging+data TraversalPaging = TraversalPaging {+    pageSize :: Integer,+    pageLeaseSecs :: Integer+    } deriving (Eq, Show)++instance Default TraversalPaging where+    def = TraversalPaging {pageSize = 50, pageLeaseSecs = 60}++-- | Direction without possibility of ambiguity+data ConcreteDirection = In | Out deriving (Eq, Show, Ord)++instance J.FromJSON ConcreteDirection where+    parseJSON (J.String "->" ) =  return Out+    parseJSON (J.String "<-" ) =  return In+    parseJSON _ = mzero++-- | Data type to describe a path in a graph, that is a single node or nodes interleaved with relationships+data Path a b = PathEnd !a | PathLink !a !b !(Path a b) deriving (Eq, Ord, Show)++-- | Get all the nodes of a path+pathNodes :: Path a b -> [a]+pathNodes (PathEnd n) = [n]+pathNodes (PathLink n _ p) = n : pathNodes p++-- | Get all the relationships of a path+pathRels :: Path a b -> [b]+pathRels (PathEnd _) = []+pathRels (PathLink _ r p) = r : pathRels p++-- | Path that its data are id's+type IdPath = Path NodePath (RelPath, ConcreteDirection)++-- | How to decodify an IdPath from JSON+instance J.FromJSON IdPath where+    parseJSON (J.Object v) =  do+                  nodes <- (map (getNodePath . NodeUrl)) <$> (v .: "nodes")+                  rels <- (map (getRelPath . RelUrl)) <$> (v .: "relationships")+                  -- Pre 2.2 neo4j versions don't provide the directions field, so we put a default value...+                  dirs <- fromMaybe (take (length rels) $ repeat Out) <$> v .:? "directions"+                  let correctPath = (length nodes) == (length rels + 1) && (length rels == length dirs) +                  if correctPath+                    then return $ buildPath nodes rels dirs+                    else fail $ "Wrong path nodes: " <> show nodes <> " rels: " <> show rels <> " dirs: " <> show dirs+        where buildPath [n] [] [] = PathEnd n+              buildPath (n:ns) (r:rs) (d:ds) = PathLink n (r, d) (buildPath ns rs ds)+              buildPath _ _ _ = undefined+    parseJSON _ = fail "wrong type for path"++-- | Path that its data are full nodes and relationships, not only their id's+type FullPath = Path Node Relationship++-- | How to decodify an IdPath from JSON+instance J.FromJSON FullPath where+    parseJSON (J.Object v) =  do+                  nodes <- v .: "nodes"+                  rels <- v .: "relationships"+                  let correctPath = length nodes == length rels + 1+                  if correctPath+                    then return $ buildPath nodes rels+                    else fail $ "Wrong path nodes: " <> show nodes <> " rels: " <> show rels+        where buildPath [n] [] = PathEnd n+              buildPath (n:ns) (r:rs) = PathLink n r (buildPath ns rs)+              buildPath _ _ = undefined+    parseJSON _ = fail "wrong type for path"++-- | Data type that holds a result for a paged traversal with the URI to get the rest of the pages+data PagedTraversal a = Done | More S.ByteString [a] deriving (Eq, Ord, Show)++-- | Get the path of a node in simple text+bsNodePath :: NodeIdentifier a => a -> S.ByteString+bsNodePath n = TE.encodeUtf8 $ (runNodePath $ getNodePath n)++-- | Get the path for a traversal starting from a node+traversalApi :: NodeIdentifier a => a -> S.ByteString+traversalApi n = bsNodePath n <> "/traverse"++-- | Get the path for a paged traversal starting from a node+pagedApi :: NodeIdentifier a => a -> S.ByteString+pagedApi n = bsNodePath n <> "/paged/traverse"++-- | Wrap 404 exception into Neo4jNoEntity exceptions+proc404 :: NodeIdentifier n => n -> Neo4jException -> a+proc404 n exc@(Neo4jUnexpectedResponseException s)+        | s == HT.status404 = throw (Neo4jNoEntityException $ bsNodePath n)+        | otherwise = throw exc+proc404 _ exc = throw exc++-- | Perform a traversal operation+traverse :: (NodeIdentifier a, J.FromJSON b) => S.ByteString -> TraversalDesc -> a -> Neo4j [b]+traverse path desc start = Neo4j $ \conn ->+                             httpCreate conn (traversalApi start <> path) (traversalReqBody desc) `catch` proc404 start++-- | Perform a traversal and get the resulting nodes+traverseGetNodes :: NodeIdentifier a => TraversalDesc -> a -> Neo4j [Node]+traverseGetNodes = traverse "/node"++-- | Perform a traversal and get the resulting relationship entities+traverseGetRels :: NodeIdentifier a => TraversalDesc -> a -> Neo4j [Relationship]+traverseGetRels = traverse "/relationship"++-- | Perform a traversal and get the resulting node and relationship paths+--   IMPORTANT! In pre 2.2 Neo4j versions the directions in each relationship ID returned have a default value+--   (The API does not provide them)+traverseGetPath :: NodeIdentifier a => TraversalDesc -> a -> Neo4j [IdPath]+traverseGetPath = traverse "/path"++-- | Perform a traversal and get the resulting node and relationship entities+traverseGetFullPath :: NodeIdentifier a => TraversalDesc -> a -> Neo4j [FullPath]+traverseGetFullPath = traverse "/fullpath"++-- | Get the values of a paged traversal result+getPagedValues :: PagedTraversal a -> [a]+getPagedValues Done = []+getPagedValues (More _ r)  = r++-- | Get the next page of values from a traversal result+nextTraversalPage :: J.FromJSON a => PagedTraversal a -> Neo4j (PagedTraversal a)+nextTraversalPage Done = return Done+nextTraversalPage (More pagingUri _) = Neo4j $ \conn -> do+    mr <- httpRetrieve conn pagingUri+    return $ case mr of+               Nothing -> Done+               Just r -> More pagingUri r++-- | Whether a paged traversal is done+pagedTraversalDone :: PagedTraversal a -> Bool+pagedTraversalDone Done = True+pagedTraversalDone _ = False++-- | Generate the query string associated to a paging description+pagingQs :: TraversalPaging -> S.ByteString+pagingQs (TraversalPaging pSize leaseSecs) = "?pageSize=" <> showBs pSize <> "&leaseTime=" <> showBs leaseSecs+    where showBs = fromString . show++-- | Perform a paged traversal and get the resulting nodes+pagedTraversal :: (NodeIdentifier a, J.FromJSON b) => S.ByteString -> TraversalDesc -> TraversalPaging -> a+               -> Neo4j (PagedTraversal b)+pagedTraversal path desc paging start = Neo4j $ \conn -> do+    (r, headers) <- httpCreateWithHeaders conn (pagedApi start <> path <> pagingQs paging) (+        traversalReqBody desc) `catch` proc404 start+    let location = fromMaybe "" $ do+        loc <- (S.unpack . snd) <$> find ((==HT.hLocation) . fst) headers+        (S.pack . NU.uriPath) <$> NU.parseURI loc+    return (More location r)++-- | Perform a paged traversal and get the resulting nodes+pagedTraverseGetNodes :: NodeIdentifier a => TraversalDesc -> TraversalPaging -> a -> Neo4j (PagedTraversal Node)+pagedTraverseGetNodes = pagedTraversal "/node"++-- | Perform a paged traversal and get the resulting relationships +pagedTraverseGetRels :: NodeIdentifier a => TraversalDesc -> TraversalPaging -> a -> Neo4j(PagedTraversal Relationship)+pagedTraverseGetRels = pagedTraversal "/relationship"++-- | Perform a paged traversal and get the resulting id paths,+--   IMPORTANT! In pre 2.2 Neo4j versions the directions in each relationship ID returned have a default value+--   (The API does not provide them)+pagedTraverseGetPath :: NodeIdentifier a => TraversalDesc -> TraversalPaging -> a -> Neo4j (PagedTraversal IdPath)+pagedTraverseGetPath = pagedTraversal "/path"++-- | Perform a paged traversal and get the resulting paths with full entities+pagedTraverseGetFullPath :: NodeIdentifier a => TraversalDesc -> TraversalPaging -> a -> Neo4j(PagedTraversal FullPath)+pagedTraverseGetFullPath = pagedTraversal "/fullpath"