packages feed

haskell-neo4j-client 0.3.0.15 → 0.3.1.0

raw patch · 7 files changed

+458/−19 lines, 7 filesdep +network-uriPVP ok

version bump matches the API change (PVP)

Dependencies added: network-uri

API changes (from Hackage documentation)

+ Database.Neo4j.Batch: createNamedNode :: String -> Properties -> Batch (BatchFuture Node)
+ Database.Neo4j.Batch: createNamedRelationship :: (NodeBatchIdentifier a, NodeBatchIdentifier b) => String -> RelationshipType -> Properties -> a -> b -> Batch (BatchFuture Relationship)
+ Database.Neo4j.Batch: getNamedNode :: NodeBatchIdentifier a => String -> a -> Batch (BatchFuture Node)
+ Database.Neo4j.Batch: getNamedRelationship :: RelBatchIdentifier r => String -> r -> Batch (BatchFuture Relationship)
+ Database.Neo4j.Graph: addNamedNode :: String -> Node -> Graph -> Graph
+ Database.Neo4j.Graph: addNamedRelationship :: String -> Relationship -> Graph -> Graph
+ Database.Neo4j.Graph: getNamedNode :: String -> Graph -> Maybe Node
+ Database.Neo4j.Graph: getNamedRelationship :: String -> Graph -> Maybe Relationship
+ Database.Neo4j.Types: instance Eq Direction
+ Database.Neo4j.Types: instance Ord Node
+ Database.Neo4j.Types: instance Ord NodePath
+ Database.Neo4j.Types: instance Ord RelPath
+ Database.Neo4j.Types: instance Ord Relationship
+ Database.Neo4j.Types: instance Show Direction

Files

haskell-neo4j-client.cabal view
@@ -1,5 +1,5 @@ name:                haskell-neo4j-client-version:             0.3.0.15+version:             0.3.1.0 synopsis:            A Haskell neo4j client description:              Library to interact with Neo4j databases. @@ -12,8 +12,9 @@     .        -Batch calls for CRUD operations.     .+       -Traversal API     .-    All code has been tested with Neo4j versions 2.0.3,  2.1.5, 2.1.7 and 2.2.0+    All code has been tested with Neo4j versions 2.0.3, 2.1.5, 2.1.7, 2.2.0 and 2.2.1     .     2.2.0 added authentication to the API this driver uses, this is not supported yet by this driver but it will be soon.     In the meantime you will have to disable it.@@ -61,6 +62,7 @@                    , lifted-base                 == 0.2.*                    , hashable                    == 1.2.*                    , mtl                         >= 2.1        && < 2.3+                   , network-uri                 == 2.6.*  library   hs-source-dirs:      src@@ -89,3 +91,4 @@                      , hashable              == 1.2.*                      , transformers-base     == 0.4.*                      , mtl                   >= 2.1        && < 2.3+                     , network-uri           == 2.6.*
src/Database/Neo4j/Batch.hs view
@@ -8,9 +8,10 @@     -- * General     Batch, runBatch, BatchFuture(..), NodeBatchIdentifier, RelBatchIdentifier, BatchEntity,     -- * Nodes-    createNode, getNode, deleteNode,+    createNode, createNamedNode, getNode, getNamedNode, deleteNode,     -- * Relationships-    createRelationship, getRelationship, getRelationshipFrom, getRelationshipTo, deleteRelationship, getRelationships,+    createRelationship, createNamedRelationship, getRelationship, getNamedRelationship, getRelationshipFrom,+    getRelationshipTo, deleteRelationship, getRelationships,     -- * Properties     setProperties, setProperty, deleteProperties, deleteProperty,     -- * Labels
src/Database/Neo4j/Batch/Node.hs view
@@ -35,11 +35,23 @@     where cmd = defCmd{cmdMethod = HT.methodPost, cmdPath = "/node", cmdBody = J.toJSON props, cmdParse = parser}           parser n = G.addNode (tryParseBody n) +-- | Batch operation to create a node and assign it a name to easily retrieve it from the resulting graph of the batch+createNamedNode :: String -> Properties -> Batch (BatchFuture Node)+createNamedNode name props = nextState cmd+    where cmd = defCmd{cmdMethod = HT.methodPost, cmdPath = "/node", cmdBody = J.toJSON props, cmdParse = parser}+          parser n = G.addNamedNode name (tryParseBody n)+ -- | Batch operation to get a node from the DB getNode :: NodeBatchIdentifier a => a -> Batch (BatchFuture Node) getNode n = nextState cmd     where cmd = defCmd{cmdMethod = HT.methodGet, cmdPath = getNodeBatchId n, cmdBody = "", cmdParse = parser}           parser jn = G.addNode (tryParseBody jn)++-- | Batch operation to get a node from the DB and assign it a name+getNamedNode :: NodeBatchIdentifier a => String -> a -> Batch (BatchFuture Node)+getNamedNode name n = nextState cmd+    where cmd = defCmd{cmdMethod = HT.methodGet, cmdPath = getNodeBatchId n, cmdBody = "", cmdParse = parser}+          parser jn = G.addNamedNode name (tryParseBody jn)  -- | Batch operation to delete a node deleteNode :: NodeBatchIdentifier a => a -> Batch (BatchFuture ())
src/Database/Neo4j/Batch/Relationship.hs view
@@ -40,11 +40,26 @@           body = J.object ["to" .= getNodeBatchId nodeto, "type" .= t, "data" .= J.toJSON props]           parser r = G.addRelationship (tryParseBody r) +-- | Create a new relationship with a type and a set of properties and assign it an identifier+createNamedRelationship :: (NodeBatchIdentifier a, NodeBatchIdentifier b) => String -> RelationshipType -> Properties+                        -> a -> b -> Batch (BatchFuture Relationship)+createNamedRelationship name t props nodefrom nodeto = nextState cmd+    where cmd = defCmd{cmdMethod = HT.methodPost, cmdPath = path, cmdBody = J.toJSON body, cmdParse = parser}+          path = getNodeBatchId nodefrom <> "/relationships"+          body = J.object ["to" .= getNodeBatchId nodeto, "type" .= t, "data" .= J.toJSON props]+          parser r = G.addNamedRelationship name (tryParseBody r)+ -- | Refresh a relationship entity with the contents in the DB getRelationship :: RelBatchIdentifier r => r -> Batch (BatchFuture Relationship) getRelationship rel = nextState cmd     where cmd = defCmd{cmdMethod = HT.methodGet, cmdPath = getRelBatchId rel, cmdBody = "", cmdParse = parser}           parser jr = G.addRelationship (tryParseBody jr)++-- | Refresh a relationship entity with the contents in the DB and assign it an identifier+getNamedRelationship :: RelBatchIdentifier r => String -> r -> Batch (BatchFuture Relationship)+getNamedRelationship name rel = nextState cmd+    where cmd = defCmd{cmdMethod = HT.methodGet, cmdPath = getRelBatchId rel, cmdBody = "", cmdParse = parser}+          parser jr = G.addNamedRelationship name (tryParseBody jr)  -- | Get the "node from" from a relationship from the DB getRelationshipFrom :: Relationship -> Batch (BatchFuture Node)
src/Database/Neo4j/Graph.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE OverloadedStrings  #-} + -- | Module to handle 'Graph' objects. These have information about a group of nodes, relationships, --  and information about labels per node and nodes per label. --  Notice a graph can have relationships and at the same time not have some of the nodes of those@@ -9,10 +10,10 @@     -- * General objects     Graph, LabelSet, empty,     -- * Handling nodes in the graph object-    addNode, hasNode, deleteNode, getNodes, getNode, getNodeFrom, getNodeTo,+    addNode, addNamedNode, hasNode, deleteNode, getNodes, getNode, getNamedNode, getNodeFrom, getNodeTo,     -- * Handling properties in the graph object-    getRelationships, hasRelationship, addRelationship, deleteRelationship, getRelationshipNodeFrom,-    getRelationshipNodeTo, getRelationship,+    getRelationships, hasRelationship, addRelationship, addNamedRelationship, deleteRelationship,+    getRelationshipNodeFrom, getRelationshipNodeTo, getRelationship, getNamedRelationship,     -- ** Handling orphaned relationships #orphan#     getOrphansFrom, getOrphansTo, cleanOrphanRelationships,     -- * Handling properties@@ -50,18 +51,27 @@ type NodeRelIndex = M.HashMap NodePath RelSet  data Graph = Graph {nodes :: NodeIndex, labels :: LabelNodeIndex, rels :: RelIndex,-                    nodeLabels :: NodeLabelIndex, nodeFromRels :: NodeRelIndex, nodeToRels :: NodeRelIndex+                    nodeLabels :: NodeLabelIndex, nodeFromRels :: NodeRelIndex, nodeToRels :: NodeRelIndex,+                    namedNodes :: M.HashMap String NodePath, namedRels :: M.HashMap String RelPath,+                    nodeNames :: M.HashMap NodePath String, relNames :: M.HashMap RelPath String                     } deriving (Eq, Show)  -- | Create an empty graph empty :: Graph empty = Graph {nodes = M.empty, labels = M.empty, rels = M.empty, nodeLabels = M.empty, nodeFromRels = M.empty,-               nodeToRels = M.empty}+               nodeToRels = M.empty, namedNodes = M.empty, namedRels = M.empty, nodeNames = M.empty,+               relNames = M.empty}  -- | Add a node to the graph addNode :: Node -> Graph -> Graph addNode n g = g {nodes = M.insert (getNodePath n) n (nodes g)} +-- | Add a node to the graph with an identifier+addNamedNode :: String -> Node -> Graph -> Graph+addNamedNode name n g = addNode n $ g {nodeNames = M.insert _nodePath name $ nodeNames g,+                                       namedNodes = M.insert name _nodePath $ namedNodes g}+    where _nodePath = getNodePath n+ -- | Set the properties of a node or relationship in the graph, if not present it won't do anything setProperties :: EntityIdentifier a => a -> Properties -> Graph -> Graph setProperties ei props g = fromMaybe g $ (addEntity . newEntity) <$> entity@@ -118,6 +128,12 @@ getNode :: NodeIdentifier a => a -> Graph -> Maybe Node getNode n g = getNodePath n `M.lookup` nodes g +-- | Get a node by name in the graph, if any+getNamedNode :: String -> Graph -> Maybe Node+getNamedNode name g = do+    nodepath <- getNodePath <$> name `M.lookup` namedNodes g+    nodepath `M.lookup` nodes g+ -- | Get outgoing relationships from a node getNodeFrom :: NodeIdentifier a => a -> Graph -> Maybe [Relationship] getNodeFrom n g = join $ sequence <$> filter (/=Nothing) <$> map getRel <$> fromrels@@ -142,11 +158,23 @@ deleteNode :: NodeIdentifier a => a -> Graph -> Graph deleteNode n g = g {nodes = M.delete nodeLoc (nodes g),                  labels = cleanLabelNodeIndex $ removeNodeFromLabels (labels g) labelsForNode,-                 nodeLabels = M.delete nodeLoc (nodeLabels g)}+                 nodeLabels = M.delete nodeLoc (nodeLabels g), nodeNames = newNodeNames, namedNodes = newNamedNodes}     where labelsForNode = M.lookupDefault HS.empty nodeLoc (nodeLabels g)+          nodepath = getNodePath n           nodeLoc = getNodePath n           removeNodeFromLabels = HS.foldl' (\acc x -> M.insertWith (\_ -> HS.delete nodeLoc) x HS.empty acc)           cleanLabelNodeIndex = M.filter (/=HS.empty) +          nodenames = nodeNames g+          (mNodeName, newNodeNames) = fromMaybe (Nothing, nodenames) $ do+              name <- nodepath `M.lookup` nodenames+              return (Just name, nodepath `M.delete` nodenames)+          namednodes = namedNodes g+          newNamedNodes = fromMaybe namednodes $ do+              nodename <- mNodeName+              _nodepath <- nodename `M.lookup` namednodes+              return $ if nodepath == _nodepath+                          then nodename `M.delete` namednodes+                          else namednodes  -- | Add a relationship to the graph addRelationship :: Relationship -> Graph -> Graph@@ -157,6 +185,12 @@           pathTo = getNodePath . relTo           relpath = getRelPath r +-- | Add a relationship to the graph with an identified+addNamedRelationship :: String -> Relationship -> Graph -> Graph+addNamedRelationship name r g = addRelationship r $ g {relNames = M.insert _relPath name $ relNames g,+                                                       namedRels = M.insert name _relPath $ namedRels g}+    where _relPath = getRelPath r+ -- | Get a list with all the relationships in the graph getRelationships :: Graph -> [Relationship] getRelationships g = M.elems $ rels g@@ -165,6 +199,12 @@ getRelationship :: RelIdentifier a => a -> Graph -> Maybe Relationship getRelationship r g = getRelPath r `M.lookup` rels g +-- | Get a relationship by name in the graph, if any+getNamedRelationship :: String -> Graph -> Maybe Relationship+getNamedRelationship name g = do+    relpath <- getRelPath <$> name `M.lookup` namedRels g+    relpath `M.lookup` rels g+ -- | Get relationships missing their "from" node getOrphansFrom :: Graph -> [Relationship] getOrphansFrom g = M.elems $ M.filter noNode (rels g)@@ -183,14 +223,26 @@ deleteRelationship :: RelIdentifier a => a -> Graph -> Graph deleteRelationship r g = g {rels = M.delete (getRelPath r) (rels g),                             nodeFromRels = removeNodeRef (nodeFromRels g) pathFrom,-                            nodeToRels = removeNodeRef (nodeToRels g) pathTo}+                            nodeToRels = removeNodeRef (nodeToRels g) pathTo,+                            relNames = newRelNames, namedRels = newNamedRels}     where updNodeRel = const $ HS.delete (getRelPath r)-          rel = M.lookup (getRelPath r) (rels g)+          relpath = getRelPath r+          rel = M.lookup relpath (rels g)           pathFrom = (getNodePath . relFrom) <$> rel           pathTo = (getNodePath . relTo) <$> rel           removeNodeRef nodeRel (Just nodepath) = M.insertWith updNodeRel nodepath HS.empty nodeRel           removeNodeRef nodeRel Nothing = nodeRel-+          relnames = relNames g+          (mRelName, newRelNames) = fromMaybe (Nothing, relnames) $ do+              name <- relpath `M.lookup` relnames+              return (Just name, relpath `M.delete` relnames)+          namedrels = namedRels g+          newNamedRels = fromMaybe namedrels $ do+              relname <- mRelName+              _relpath <- relname `M.lookup` namedrels+              return $ if relpath == _relpath+                 then  relname `M.delete` namedrels+                 else namedrels  -- | Get the "node from" from a relationship getRelationshipNodeFrom :: Relationship -> Graph -> Maybe Node@@ -241,22 +293,36 @@ -- | Add two graphs resulting in a graph with all the nodes, labels and relationships of both -- | If a node/entity is present in both the second one will be chosen union :: Graph -> Graph -> Graph-union ga gb = addLabels (addRels (addNodes ga gb) gb) gb+union ga gb = mergeRelNames (mergeNodeNames (addLabels (addRels (addNodes ga gb) gb) gb) gb) gb     where addRels g1 g2 = foldl (\gacc r -> addRelationship r gacc) g1 (getRelationships g2)           addNodes g1 g2 = foldl (flip addNode) g1 (getNodes g2)           addLabels g1 g2 = foldl (\gacc n -> setNodeLabels n (HS.toList $ getNodeLabels n g2) gacc) g1 (getNodes g2)+          mergeNodeNames g1 g2 = g1 {nodeNames = nodeNames g2 `M.union` nodeNames g1,+                                     namedNodes = namedNodes g2 `M.union` namedNodes g1}+          mergeRelNames g1 g2 = g1 {relNames = relNames g2 `M.union` relNames g1,+                                    namedRels = namedRels g2 `M.union` namedRels g1}  -- | Remove the nodes and relationships in the first graph that appear in the second difference :: Graph -> Graph -> Graph-difference ga gb = relationshipFilter relFilterFunc (nodeFilter nodeFilterFunc ga)+difference ga gb = relationshipFilter relFilterFunc (nodeFilter nodeFilterFunc ga) {+        nodeNames = newNodeNames, namedNodes = newNamedNodes, relNames = newRelNames, namedRels = newNamedRels}     where relFilterFunc r = not $ hasRelationship r gb           nodeFilterFunc n = not $ hasNode n gb+          newNodeNames = nodeNames ga `M.difference` nodeNames gb+          newNamedNodes = namedNodes ga `M.difference` namedNodes gb+          newRelNames = relNames ga `M.difference` relNames gb+          newNamedRels = namedRels ga `M.difference` namedRels gb  -- | Have a graph that only has nodes and relationships that are present in both intersection :: Graph -> Graph -> Graph-intersection ga gb = relationshipFilter relFilterFunc (nodeFilter nodeFilterFunc ga)+intersection ga gb = relationshipFilter relFilterFunc (nodeFilter nodeFilterFunc ga) {+        nodeNames = newNodeNames, namedNodes = newNamedNodes, relNames = newRelNames, namedRels = newNamedRels}     where relFilterFunc r = hasRelationship r gb           nodeFilterFunc n = hasNode n gb+          newNodeNames = nodeNames ga `M.intersection` nodeNames gb+          newNamedNodes = namedNodes ga `M.intersection` namedNodes gb+          newRelNames = relNames ga `M.intersection` relNames gb+          newNamedRels = namedRels ga `M.intersection` namedRels gb  -- | Feed a cypher result (from the old API) into a graph (looks for nodes and relationships and inserts them) addCypher :: C.Response -> Graph -> Graph
src/Database/Neo4j/Types.hs view
@@ -137,6 +137,8 @@  -- | Representation of a Neo4j node, has a location URI and a set of properties data Node = Node {nodePath :: NodePath, nodeProperties :: Properties} deriving (Show, Eq)+instance Ord Node where+    a <= b = nodePath a <= nodePath b  -- | Get the properties of a node getNodeProperties :: Node -> Properties@@ -160,7 +162,7 @@ nodeAPITxt :: T.Text nodeAPITxt = "/db/data/node" -newtype NodePath = NodePath {runNodePath :: T.Text} deriving (Show, Eq, Generic)+newtype NodePath = NodePath {runNodePath :: T.Text} deriving (Show, Ord, Eq, Generic) instance Hashable NodePath  class NodeIdentifier a where@@ -188,7 +190,7 @@ type RelationshipType = T.Text  -- | Relationship direction-data Direction = Outgoing | Incoming | Any+data Direction = Outgoing | Incoming | Any deriving (Eq, Show)  -- | Type for a relationship location newtype RelUrl = RelUrl {runRelUrl :: T.Text} deriving (Show, Eq, Generic)@@ -200,6 +202,8 @@                                   relProperties :: Properties,                                   relFrom :: NodePath,                                   relTo :: NodePath} deriving (Show, Eq)+instance Ord Relationship where+    a <= b = relPath a <= relPath b  -- | Get the properties of a relationship getRelProperties :: Relationship -> Properties@@ -240,7 +244,7 @@ relationshipAPITxt :: T.Text relationshipAPITxt = "/db/data/relationship" -newtype RelPath = RelPath {runRelPath :: T.Text} deriving (Show, Eq, Generic)+newtype RelPath = RelPath {runRelPath :: T.Text} deriving (Show, Eq, Ord, Generic) instance Hashable RelPath  class RelIdentifier a where
tests/IntegrationTests.hs view
@@ -2,11 +2,14 @@ {-# LANGUAGE FlexibleInstances, TemplateHaskell #-} module Main where +import Control.Applicative ((<$>)) import Control.Monad (void, join) import Control.Monad.IO.Class (MonadIO, liftIO)+import Data.Default import Data.Monoid (Monoid, mappend) import Data.Int (Int64) import Data.Maybe (fromJust)+import Data.String (fromString)  import qualified Data.Aeson as J import qualified Data.ByteString as S@@ -15,6 +18,8 @@ import qualified Data.Text as T import qualified Data.Text.Encoding as TE import qualified Data.Vector as V+import qualified Data.List as L+import qualified Network.HTTP.Types as HT  import Control.Exception (handle) import Test.Framework.TH (defaultMainGenerator)@@ -26,6 +31,7 @@ import qualified Database.Neo4j.Cypher as C import qualified Database.Neo4j.Graph as G import qualified Database.Neo4j.Transactional.Cypher as TC+import qualified Database.Neo4j.Traversal as T  (<>) :: Monoid a => a -> a -> a (<>) = mappend@@ -1590,3 +1596,335 @@                 neo4jEqual 1 (length $ G.getNodes g4)                 neo4jEqual (head $ G.getRelationships g) (head $ G.getRelationships g4)                 neo4jEqual (head $ G.getNodes g) (head $ G.getNodes g4)++-- | Environment for traversal tests+setUpTraversalTest :: Neo4j G.Graph+setUpTraversalTest = B.runBatch $ do+                            root <- _createNode "Root"+                            johan <- _createNode "Johan"+                            mattias <- _createNode "Mattias"+                            emil <- _createNode "Emil"+                            peter <- _createNode "Peter"+                            tobias <- _createNode "Tobias"+                            sara <- _createNode "Sara"+                            gumersindo <- _createNode "Gumersindo"+                            _createRel "Root-Johan" "knows" root johan+                            _createRel "Root-Mattias" "knows" root mattias +                            _createRel "Johan-Emil" "knows" johan emil +                            _createRel "Emil-Peter" "knows" emil peter+                            _createRel "Emil-Tobias" "knows" emil tobias+                            _createRel "Tobias-Sara" "loves" tobias sara+                            _createRel "Gumersindo-Tobias" "hates" gumersindo tobias+                            _createRel "Gumersindo-Sara" "admires" sara gumersindo+    where _createNode name = B.createNamedNode name $ M.fromList ["name" |: (fromString name :: T.Text)]+          _createRel name t from to = void $ B.createNamedRelationship name t M.empty from to++-- | clean up a traversal test+cleanUpTraversalTest :: G.Graph -> Neo4j ()+cleanUpTraversalTest g = void $ B.runBatch $ do+                            mapM_ B.deleteRelationship (G.getRelationships g)+                            mapM_ B.deleteNode (G.getNodes g)+++-- | Test a traversal with an unexisting start+case_traversalNotFound :: Assertion+case_traversalNotFound = do+        g <- withConnection host port $ setUpTraversalTest+        let start = fromJust $ G.getNamedNode "Root" g+        let expException = Neo4jNoEntityException $ (TE.encodeUtf8 . runNodePath . nodePath) start+        assertException expException $ withConnection host port $ do+            let rels = map (\n -> fromJust $ G.getNamedRelationship n g) ["Root-Johan", "Root-Mattias"]+            mapM_ deleteRelationship rels+            deleteNode start+            void $ T.traverseGetNodes def start+        withConnection host port $ cleanUpTraversalTest g++-- | Test a default traversal returning nodes+case_defaultTraversalNodes :: Assertion+case_defaultTraversalNodes = withConnection host port $ do+    g <- setUpTraversalTest+    let start = fromJust $ G.getNamedNode "Root" g+    ns <- T.traverseGetNodes def start+    let expected = map (\n -> fromJust $ G.getNamedNode n g) ["Root", "Mattias", "Johan"]+    neo4jEqual (L.sort expected) (L.sort ns)+    cleanUpTraversalTest g++-- | Test a default traversal returning relationships+case_defaultTraversalRels :: Assertion+case_defaultTraversalRels = withConnection host port $ do+    g <- setUpTraversalTest+    let start = fromJust $ G.getNamedNode "Root" g+    rs <- T.traverseGetRels def start+    let expected = map (\n -> fromJust $ G.getNamedRelationship n g) ["Root-Mattias", "Root-Johan"]+    neo4jEqual (L.sort expected) (L.sort rs)+    cleanUpTraversalTest g++-- | Test a default traversal returning id paths+case_defaultTraversalIdPath :: Assertion+case_defaultTraversalIdPath = withConnection host port $ do+    g <- setUpTraversalTest+    let start = fromJust $ G.getNamedNode "Root" g+    ps <- L.sort <$> T.traverseGetPath def start+    neo4jEqual 3 (length ps)+    -- check we've received the expected paths+    let _getNodePath = \name -> nodePath $ fromJust $ G.getNamedNode name g+    let _getRelPath = \name -> relPath $ fromJust $ G.getNamedRelationship name g+    let checkPath = \p nodes rels -> do+        neo4jEqual (map _getNodePath nodes) (T.pathNodes p)+        let resRels = T.pathRels p+        neo4jEqual (map _getRelPath (map fst rels)) (map fst resRels)+        neo4jEqual (map snd rels) (map snd resRels)+    checkPath (ps !! 0) ["Root"] []+    checkPath (ps !! 1) ["Root", "Johan"] [("Root-Johan", T.Out)]+    checkPath (ps !! 2) ["Root", "Mattias"] [("Root-Mattias", T.Out)]+    cleanUpTraversalTest g++-- | Test a default traversal returning full paths+case_defaultTraversalFullPath :: Assertion+case_defaultTraversalFullPath = withConnection host port $ do+    g <- setUpTraversalTest+    let start = fromJust $ G.getNamedNode "Root" g+    ps <- L.sort <$> T.traverseGetFullPath def start+    neo4jEqual 3 (length ps)+    -- check we've received the expected paths+    let _getNode = \name -> fromJust $ G.getNamedNode name g+    let _getRel = \name -> fromJust $ G.getNamedRelationship name g+    let checkPath = \p nodes rels -> do+        neo4jEqual (L.sort $ map _getNode nodes) (L.sort $ T.pathNodes p)+        neo4jEqual (L.sort $ map _getRel rels) (L.sort $ T.pathRels p)+    checkPath (ps !! 0) ["Root"] []+    checkPath (ps !! 1) ["Root", "Johan"] ["Root-Johan"]+    checkPath (ps !! 2) ["Root", "Mattias"] ["Root-Mattias"]+    cleanUpTraversalTest g++-- | Test a traversal with an unexisting start+case_pagedTraversalNotFound :: Assertion+case_pagedTraversalNotFound = do+        g <- withConnection host port $ setUpTraversalTest+        let start = fromJust $ G.getNamedNode "Root" g+        let expException = Neo4jNoEntityException $ (TE.encodeUtf8 . runNodePath . nodePath) start+        assertException expException $ withConnection host port $ do+            let rels = map (\n -> fromJust $ G.getNamedRelationship n g) ["Root-Johan", "Root-Mattias"]+            mapM_ deleteRelationship rels+            deleteNode start+            void $ T.pagedTraverseGetNodes def def start+        withConnection host port $ cleanUpTraversalTest g++-- | Test a paged traversal returning nodes+case_pagedTraversalNodes :: Assertion+case_pagedTraversalNodes = withConnection host port $ do+    g <- setUpTraversalTest+    let start = fromJust $ G.getNamedNode "Root" g+    let desc = def {T.travDepth = Left 2}+    let paging = def {T.pageSize = 1}+    pg <- T.pagedTraverseGetNodes desc paging start+    let _getNode = \name -> fromJust $ G.getNamedNode name g+    neo4jEqual False (T.pagedTraversalDone pg)+    neo4jEqual [_getNode "Root"] (T.getPagedValues pg)+    pg2 <- T.nextTraversalPage pg+    neo4jEqual False (T.pagedTraversalDone pg2)+    pg3 <- T.nextTraversalPage pg2+    neo4jEqual False (T.pagedTraversalDone pg3)+    neo4jEqual (L.sort $ map _getNode ["Johan", "Mattias"]) (L.sort $ T.getPagedValues pg2 ++ T.getPagedValues pg3)+    pg4 <- T.nextTraversalPage pg3+    neo4jEqual False (T.pagedTraversalDone pg4)+    neo4jEqual [_getNode "Emil"] (T.getPagedValues pg4)+    pg5 <- T.nextTraversalPage pg4+    neo4jEqual True (T.pagedTraversalDone pg5)+    pg6 <- T.nextTraversalPage pg5+    neo4jEqual True (T.pagedTraversalDone pg6)+    cleanUpTraversalTest g++-- | Test a paged traversal returning relationships+case_pagedTraversalRels :: Assertion+case_pagedTraversalRels = withConnection host port $ do+    g <- setUpTraversalTest+    let start = fromJust $ G.getNamedNode "Root" g+    let desc = def+    let paging = def {T.pageSize = 1}+    pg <- T.pagedTraverseGetRels desc paging start+    let _getRel = \name -> fromJust $ G.getNamedRelationship name g+    neo4jEqual False (T.pagedTraversalDone pg)+    pg2 <- T.nextTraversalPage pg+    neo4jEqual False (T.pagedTraversalDone pg2)+    pg3 <- T.nextTraversalPage pg2+    neo4jEqual False (T.pagedTraversalDone pg3)+    neo4jEqual (L.sort $ map _getRel ["Root-Johan", "Root-Mattias"]) (+        L.sort $ T.getPagedValues pg ++ T.getPagedValues pg2 ++ T.getPagedValues pg3)+    pg4 <- T.nextTraversalPage pg3+    neo4jEqual True (T.pagedTraversalDone pg4)+    cleanUpTraversalTest g++-- | Test a paged traversal returning id paths +case_pagedTraversalIdPaths :: Assertion+case_pagedTraversalIdPaths = withConnection host port $ do+    g <- setUpTraversalTest+    let start = fromJust $ G.getNamedNode "Root" g+    let desc = def+    let paging = def {T.pageSize = 1}+    let _getNodePath = \name -> nodePath $ fromJust $ G.getNamedNode name g+    let _getRelPath = \name -> relPath $ fromJust $ G.getNamedRelationship name g+    let checkPath = \p nodes rels -> do+        neo4jEqual (map _getNodePath nodes) (T.pathNodes p)+        let resRels = T.pathRels p+        neo4jEqual (map _getRelPath (map fst rels)) (map fst resRels)+        neo4jEqual (map snd rels) (map snd resRels)+    pg <- T.pagedTraverseGetPath desc paging start+    neo4jEqual False (T.pagedTraversalDone pg)+    checkPath (T.getPagedValues pg !! 0) ["Root"] []+    pg2 <- T.nextTraversalPage pg+    neo4jEqual False (T.pagedTraversalDone pg2)+    pg3 <- T.nextTraversalPage pg2+    neo4jEqual False (T.pagedTraversalDone pg3)+    let ps = L.sort $ T.getPagedValues pg2 ++ T.getPagedValues pg3+    checkPath (ps !! 0) ["Root", "Johan"] [("Root-Johan", T.Out)]+    checkPath (ps !! 1) ["Root", "Mattias"] [("Root-Mattias", T.Out)]+    pg4 <- T.nextTraversalPage pg3+    neo4jEqual True (T.pagedTraversalDone pg4)+    cleanUpTraversalTest g++-- | Test a paged traversal returning full paths +case_pagedTraversalFullPaths :: Assertion+case_pagedTraversalFullPaths = withConnection host port $ do+    g <- setUpTraversalTest+    let start = fromJust $ G.getNamedNode "Root" g+    let desc = def+    let paging = def {T.pageSize = 1}+    let _getNode = \name -> fromJust $ G.getNamedNode name g+    let _getRel = \name -> fromJust $ G.getNamedRelationship name g+    let checkPath = \p nodes rels -> do+        neo4jEqual (L.sort $ map _getNode nodes) (L.sort $ T.pathNodes p)+        neo4jEqual (L.sort $ map _getRel rels) (L.sort $ T.pathRels p)+    pg <- T.pagedTraverseGetFullPath desc paging start+    neo4jEqual False (T.pagedTraversalDone pg)+    checkPath (T.getPagedValues pg !! 0) ["Root"] []+    pg2 <- T.nextTraversalPage pg+    neo4jEqual False (T.pagedTraversalDone pg2)+    pg3 <- T.nextTraversalPage pg2+    neo4jEqual False (T.pagedTraversalDone pg3)+    let ps = L.sort $ T.getPagedValues pg2 ++ T.getPagedValues pg3+    checkPath (ps !! 0) ["Root", "Johan"] ["Root-Johan"]+    checkPath (ps !! 1) ["Root", "Mattias"] ["Root-Mattias"]+    pg4 <- T.nextTraversalPage pg3+    neo4jEqual True (T.pagedTraversalDone pg4)+    cleanUpTraversalTest g++-- | Test traversal depth first search+case_dfsTraversalNodes :: Assertion+case_dfsTraversalNodes = withConnection host port $ do+    g <- setUpTraversalTest+    let start = fromJust $ G.getNamedNode "Root" g+    let desc = def {T.travDepth = Left 2, T.travOrder = T.DepthFirst}+    ns <- T.traverseGetNodes desc start+    let expected = map (\n -> fromJust $ G.getNamedNode n g) ["Root", "Johan", "Emil", "Mattias"]+    neo4jEqual (L.sort expected) (L.sort ns)+    cleanUpTraversalTest g++-- | Test traversal with a relation filter+case_relFilterTraversalNodes :: Assertion+case_relFilterTraversalNodes = withConnection host port $ do+    g <- setUpTraversalTest+    let start = fromJust $ G.getNamedNode "Tobias" g+    let desc = def {T.travRelFilter = [("loves", Outgoing)]}+    ns <- T.traverseGetNodes desc start+    let expected = map (\n -> fromJust $ G.getNamedNode n g) ["Tobias", "Sara"]+    neo4jEqual (L.sort expected) (L.sort ns)+    let desc2 = def {T.travRelFilter = [("hates", Incoming)]}+    ns2 <- T.traverseGetNodes desc2 start+    let expected2 = map (\n -> fromJust $ G.getNamedNode n g) ["Tobias", "Gumersindo"]+    neo4jEqual (L.sort expected2) (L.sort ns2)+    let desc3 = def {T.travRelFilter = [("hates", Any)]}+    ns3 <- T.traverseGetNodes desc3 start+    let expected3 = map (\n -> fromJust $ G.getNamedNode n g) ["Tobias", "Gumersindo"]+    neo4jEqual (L.sort expected3) (L.sort ns3)+    let desc4 = def {T.travRelFilter = [("hates", Incoming), ("loves", Outgoing)]}+    ns4 <- T.traverseGetNodes desc4 start+    let expected4 = map (\n -> fromJust $ G.getNamedNode n g) ["Tobias", "Sara", "Gumersindo"]+    neo4jEqual (L.sort expected4) (L.sort ns4)+    let desc5 = def {T.travRelFilter = [("htes", Incoming)]}+    ns5 <- T.traverseGetNodes desc5 start+    let expected5 = map (\n -> fromJust $ G.getNamedNode n g) ["Tobias"]+    neo4jEqual (L.sort expected5) (L.sort ns5)+    cleanUpTraversalTest g++-- | Test traversal with a uniqueness constraint+case_uniquenessTraversalNodes :: Assertion+case_uniquenessTraversalNodes = withConnection host port $ do+    g <- setUpTraversalTest+    let start = fromJust $ G.getNamedNode "Tobias" g+    let desc = def {T.travDepth = Left 2, T.travRelFilter = [("loves", Any), ("hates", Any), ("admires", Any)]}+    ns <- T.traverseGetNodes desc start+    let expected = map (\n -> fromJust $ G.getNamedNode n g) ["Tobias", "Sara", "Gumersindo", "Sara", "Gumersindo"]+    neo4jEqual (L.sort expected) (L.sort ns)+    let desc2 = desc {T.travUniqueness = Just T.NodeGlobal}+    ns2 <- T.traverseGetNodes desc2 start+    let expected2 = map (\n -> fromJust $ G.getNamedNode n g) ["Tobias", "Sara", "Gumersindo"]+    neo4jEqual (L.sort expected2) (L.sort ns2)+    let desc3 = desc {T.travUniqueness = Just T.RelationshipGlobal}+    ns3 <- T.traverseGetNodes desc3 start+    let expected3 = map (\n -> fromJust $ G.getNamedNode n g) ["Tobias", "Gumersindo", "Sara", "Gumersindo"]+    neo4jEqual (L.sort expected3) (L.sort ns3)+    let desc4 = desc {T.travUniqueness = Just T.NodePathUnique}+    ns4 <- T.traverseGetNodes desc4 start+    let expected4 = map (\n -> fromJust $ G.getNamedNode n g) ["Tobias", "Sara", "Gumersindo", "Sara", "Gumersindo"]+    neo4jEqual (L.sort expected4) (L.sort ns4)+    let desc5 = desc {T.travUniqueness = Just T.RelationshipPath}+    ns5 <- T.traverseGetNodes desc5 start+    let expected5 = map (\n -> fromJust $ G.getNamedNode n g) ["Tobias", "Sara", "Gumersindo", "Sara", "Gumersindo"]+    neo4jEqual (L.sort expected5) (L.sort ns5)+    cleanUpTraversalTest g++-- | Test traversal with a javascript depth expression+case_progDepthTraversalNodes :: Assertion+case_progDepthTraversalNodes = withConnection host port $ do+    g <- setUpTraversalTest+    let start = fromJust $ G.getNamedNode "Root" g+    let desc = def {T.travDepth = Right "position.length() >= 2;"}+    ns <- T.traverseGetNodes desc start+    let expected = map (\n -> fromJust $ G.getNamedNode n g) ["Root", "Johan", "Mattias", "Emil"]+    neo4jEqual (L.sort expected) (L.sort ns)+    cleanUpTraversalTest g++-- | Test traversal with a wrong javascript depth expression+case_wrongProgDepthTraversalNodes :: Assertion+case_wrongProgDepthTraversalNodes = do+        g <- withConnection host port $ setUpTraversalTest+        assertException expException $ withConnection host port $ do+            let start = fromJust $ G.getNamedNode "Root" g+            let desc = def {T.travDepth = Right "positionlength() >= 2;"}+            void $ T.traverseGetNodes desc start+        withConnection host port $ cleanUpTraversalTest g+      where expException = Neo4jUnexpectedResponseException HT.status400++-- | Test traversal without initial node+case_returnButFirstTraversal :: Assertion+case_returnButFirstTraversal = withConnection host port $ do+    g <- setUpTraversalTest+    let start = fromJust $ G.getNamedNode "Root" g+    let desc = def {T.travNodeFilter = Left T.ReturnAllButStartNode}+    ns <- T.traverseGetNodes desc start+    let expected = map (\n -> fromJust $ G.getNamedNode n g) ["Johan", "Mattias"]+    neo4jEqual (L.sort expected) (L.sort ns)+    cleanUpTraversalTest g++-- | Test filter nodes without initial node+case_filterNodesTraversal :: Assertion+case_filterNodesTraversal = withConnection host port $ do+    g <- setUpTraversalTest+    let start = fromJust $ G.getNamedNode "Root" g+    let desc = def {T.travNodeFilter = Right "position.endNode().getProperty('name').toLowerCase().contains('t');"}+    ns <- T.traverseGetNodes desc start+    let expected = map (\n -> fromJust $ G.getNamedNode n g) ["Root", "Mattias"]+    neo4jEqual (L.sort expected) (L.sort ns)+    cleanUpTraversalTest g++-- | Test traversal with a filter expression+case_wrongFilterNodesTraversal :: Assertion+case_wrongFilterNodesTraversal = do+        g <- withConnection host port $ setUpTraversalTest+        assertException expException $ withConnection host port $ do+            let start = fromJust $ G.getNamedNode "Root" g+            let desc = def {T.travNodeFilter = Right "posiiiitionlength() >= 2;"}+            void $ T.traverseGetNodes desc start+        withConnection host port $ cleanUpTraversalTest g+      where expException = Neo4jUnexpectedResponseException HT.status400