packages feed

hypher 0.1.2 → 0.1.3

raw patch · 5 files changed

+70/−185 lines, 5 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Database.Neo4j.Cypher: ParamLiteral :: PropertyValue -> ParamValue
- Database.Neo4j.Cypher: ParamProperties :: Properties -> ParamValue
- Database.Neo4j.Cypher: ParamPropertiesArray :: [Properties] -> ParamValue
- Database.Neo4j.Cypher: Response :: [Text] -> [[Value]] -> Response
- Database.Neo4j.Cypher: cols :: Response -> [Text]
- Database.Neo4j.Cypher: cypher :: Text -> Params -> Neo4j (Either Text Response)
- Database.Neo4j.Cypher: data ParamValue
- Database.Neo4j.Cypher: data Response
- Database.Neo4j.Cypher: fromResult :: Response -> Either Text Response -> Response
- Database.Neo4j.Cypher: fromSuccess :: Either Text Response -> Response
- Database.Neo4j.Cypher: instance Eq ParamValue
- Database.Neo4j.Cypher: instance Eq Response
- Database.Neo4j.Cypher: instance FromJSON Response
- Database.Neo4j.Cypher: instance Show ParamValue
- Database.Neo4j.Cypher: instance Show Response
- Database.Neo4j.Cypher: instance ToJSON ParamValue
- Database.Neo4j.Cypher: isSuccess :: Either Text Response -> Bool
- Database.Neo4j.Cypher: newparam :: PropertyValueConstructor a => a -> ParamValue
- Database.Neo4j.Cypher: type Params = HashMap Text ParamValue
- Database.Neo4j.Cypher: vals :: Response -> [[Value]]
- Database.Neo4j.Graph: addCypher :: Response -> Graph -> Graph
+ Database.Neo4j.Transactional.Cypher: instance Eq ParamValue
+ Database.Neo4j.Transactional.Cypher: instance Show ParamValue
+ Database.Neo4j.Transactional.Cypher: instance ToJSON ParamValue

Files

hypher.cabal view
@@ -1,5 +1,5 @@ name:                hypher-version:             0.1.2+version:             0.1.3 synopsis:            A Haskell neo4j client description:              Library to interact with Neo4j databases. @@ -13,8 +13,9 @@        -Batch calls for CRUD operations.     .     .-    All code has been tested with Neo4j versions 2.0.3 and 2.1.5. -    This is a fork of: http://hackage.haskell.org/package/haskell-neo4j-client. Attempting to bring it up to date.+    All code has been tested with Neo4j versions 2.0.3 and 2.1.5.  +    This is a fork of: http://hackage.haskell.org/package/haskell-neo4j-client. Attempting to bring it up to date. +    Only supports new Transactional Cypher REST api.      homepage:            https://github.com/zoetic-community/hyper.git license:             MIT@@ -63,7 +64,7 @@  library   hs-source-dirs:      src-  exposed-modules:     Database.Neo4j, Database.Neo4j.Graph, Database.Neo4j.Batch, Database.Neo4j.Cypher,+  exposed-modules:     Database.Neo4j, Database.Neo4j.Graph, Database.Neo4j.Batch                        Database.Neo4j.Transactional.Cypher   other-modules:       Database.Neo4j.Types, Database.Neo4j.Http, Database.Neo4j.Node, Database.Neo4j.Relationship,                        Database.Neo4j.Property, Database.Neo4j.Label, Database.Neo4j.Index, Database.Neo4j.Batch.Node,
− src/Database/Neo4j/Cypher.hs
@@ -1,91 +0,0 @@-{-# LANGUAGE OverloadedStrings  #-}---- | IMPORTANT! MODULE DEPRECATED, better use the code in this same library that uses the Neo4j transactional endpoint---   in "Database.Neo4j.Transactional.Cypher"---  Module to provide Cypher support.---  Currently we allow sending queries with parameters, the result is a collection of column headers---  and JSON data values, the Graph object has the function addCypher that tries to find---  nodes and relationships in a cypher query result and insert them in a "Database.Neo4j.Graph" object------ > import qualified Database.Neo4j.Cypher as C--- >--- > withConnection host port $ do--- >    ...--- >    -- Run a cypher query with parameters--- >    res <- C.cypher "CREATE (n:Person { name : {name} }) RETURN n" M.fromList [("name", C.newparam ("Pep" :: T.Text))]--- >--- >    -- Get all nodes and relationships that this query returned and insert them in a Graph object--- >    let graph = G.addCypher (C.fromSuccess res) G.empty--- >--- >    -- Get the column headers--- >    let columnHeaders = C.cols $ C.fromSuccess res--- >--- >    -- Get the rows of JSON values received--- >    let values = C.vals $ C.fromSuccess res-module Database.Neo4j.Cypher (-    -- * Types-    Response(..), ParamValue(..), Params, newparam,-    -- * Sending queries-    cypher, fromResult, fromSuccess, isSuccess-    ) where--import Data.Aeson ((.=), (.:))-import Control.Applicative ((<$>), (<*>))-import Control.Monad (mzero)--import qualified Data.Aeson as J-import qualified Data.ByteString as S-import qualified Data.HashMap.Lazy as M-import qualified Data.Text as T--import Database.Neo4j.Types-import Database.Neo4j.Http---- | Type for a Cypher response with tuples containing column name and their values-data Response = Response {cols :: [T.Text], vals :: [[J.Value]]} deriving (Show, Eq)---- | How to create a response object from a cypher JSON response-instance J.FromJSON Response where-    parseJSON (J.Object o) = Response <$> (o .: "columns" >>= J.parseJSON) <*> (o .: "data" >>= J.parseJSON)-    parseJSON _ = mzero-                        -cypherAPI :: S.ByteString-cypherAPI = "/db/data/cypher"---- | Value for a cypher parmeter value, might be a literal, a property map or a list of property maps-data ParamValue = ParamLiteral PropertyValue | ParamProperties Properties | ParamPropertiesArray [Properties]-     deriving (Show, Eq)--newparam :: PropertyValueConstructor a => a -> ParamValue-newparam = ParamLiteral . newval---- | Instance toJSON for param values so we can serialize them in queries-instance J.ToJSON ParamValue where-    toJSON (ParamLiteral l) = J.toJSON l-    toJSON (ParamProperties p) = J.toJSON p-    toJSON (ParamPropertiesArray ps) = J.toJSON ps---- | We use hashmaps to represent Cypher parameters-type Params = M.HashMap T.Text ParamValue---- | Run a cypher query-cypher :: T.Text -> Params -> Neo4j (Either T.Text Response)-cypher cmd params = Neo4j $ \conn -> httpCreate4XXExplained conn cypherAPI body-    where body = J.encode $ J.object ["query" .= cmd, "params" .= J.toJSON params]---- | Get the result of the response or a default value-fromResult :: Response -> Either T.Text Response -> Response-fromResult def (Left _) = def-fromResult _ (Right resp) = resp---- | Get the result of the response or a default value-fromSuccess :: Either T.Text Response -> Response-fromSuccess (Left _) = error "Cypher.fromSuccess but is Error"-fromSuccess (Right resp) = resp---- | True if the operation succeeded-isSuccess :: Either T.Text Response -> Bool-isSuccess (Left _) = False-isSuccess (Right _) = True--{-# DEPRECATED cypher, fromResult, fromSuccess, isSuccess "Use Database.Neo4j.Transactional.Cypher instead" #-} 
src/Database/Neo4j/Graph.hs view
@@ -19,8 +19,6 @@     setProperties, setProperty, deleteProperties, deleteProperty,     -- * Handling labels     setNodeLabels, addNodeLabel, getNodeLabels, deleteNodeLabel,-    -- * Handling Cypher results-    addCypher,     -- * Graph filtering functions     nodeFilter, relationshipFilter,     -- * Graph operations@@ -38,7 +36,6 @@ import qualified Data.Text as T  import Database.Neo4j.Types-import qualified Database.Neo4j.Cypher as C  type NodeIndex = M.HashMap NodePath Node type RelIndex = M.HashMap RelPath Relationship@@ -257,16 +254,3 @@ intersection ga gb = relationshipFilter relFilterFunc (nodeFilter nodeFilterFunc ga)     where relFilterFunc r = hasRelationship r gb           nodeFilterFunc n = hasNode n 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-addCypher (C.Response _ vals) ginit = foldl tryAdd ginit (concat vals)-    where tryAdd :: Graph -> J.Value -> Graph-          -- Try first to parse it as a relationship and then as a node, otherwise leave the graph as it is-          tryAdd g v = fromMaybe g $ L.find parseSuccess (map ($ v) [relParser g, nodeParser g]) >>= fromResult-          relParser g v = flip addRelationship g <$> J.fromJSON v-          nodeParser g v = flip addNode g <$> J.fromJSON v-          parseSuccess (J.Success _) = True-          parseSuccess (J.Error _) = False-          fromResult (J.Error _) = Nothing-          fromResult (J.Success g) = Just g
src/Database/Neo4j/Transactional/Cypher.hs view
@@ -29,6 +29,7 @@     loneQuery,  runTransaction, cypher, rollback, commit, keepalive, commitWith, rollbackAndLeave,     -- * Aux functions     isSuccess, fromResult, fromSuccess+     ) where  import Control.Monad.Reader@@ -47,15 +48,34 @@ import qualified Data.Acquire as A import qualified Data.ByteString as S import qualified Data.ByteString.Lazy as L+import qualified Data.List as LI import qualified Data.Text as T import qualified Data.Vector as V import qualified Network.HTTP.Types as HT+import qualified Data.HashMap.Lazy as M  import Database.Neo4j.Types import Database.Neo4j.Http-import Database.Neo4j.Cypher (ParamValue(..), Params, newparam) import qualified Database.Neo4j.Graph as G +cypherAPI :: S.ByteString+cypherAPI = "/db/data/cypher"++-- | Value for a cypher parmeter value, might be a literal, a property map or a list of property maps+data ParamValue = ParamLiteral PropertyValue | ParamProperties Properties | ParamPropertiesArray [Properties]+     deriving (Show, Eq)++newparam :: PropertyValueConstructor a => a -> ParamValue+newparam = ParamLiteral . newval++-- | Instance toJSON for param values so we can serialize them in queries+instance J.ToJSON ParamValue where+    toJSON (ParamLiteral l) = J.toJSON l+    toJSON (ParamProperties p) = J.toJSON p+    toJSON (ParamPropertiesArray ps) = J.toJSON ps++-- | We use hashmaps to represent Cypher parameters+type Params = M.HashMap T.Text ParamValue  -- | Holds the connection stats data Stats = Stats {containsUpdates :: Bool,
tests/IntegrationTests.hs view
@@ -23,7 +23,6 @@  import Database.Neo4j import qualified Database.Neo4j.Batch as B-import qualified Database.Neo4j.Cypher as C import qualified Database.Neo4j.Graph as G import qualified Database.Neo4j.Transactional.Cypher as TC @@ -50,8 +49,7 @@  -- | handy assertBool inside a Neo4j monad neo4jBool :: Bool -> Neo4j ()-neo4jBool f = liftIO $ assertBool "" f-+neo4jBool f = liftIO $ assertBool "Expected true" f  main :: IO () main = $(defaultMainGenerator)@@ -1191,24 +1189,23 @@             n2 <- B.createNode $ M.fromList ["name" |: ("you" :: T.Text)]             B.createRelationship "know" M.empty n1 n2         -- actual test-        res <- C.cypher query params-        neo4jBool $ C.isSuccess res-        neo4jEqual ["TYPE(r)"] (C.cols $ C.fromSuccess res)-        neo4jBool $ ["know"] `elem` (C.vals $ C.fromSuccess res)+        res <- TC.loneQuery query params+        neo4jBool $ TC.isSuccess res+        neo4jEqual ["TYPE(r)"] (TC.cols $ TC.fromSuccess res)+        neo4jBool $ ["know"] `elem` (TC.vals $ TC.fromSuccess res)         -- cleanup         _ <- B.runBatch $ mapM_ B.deleteRelationship (G.getRelationships gp)         _ <- B.runBatch $ mapM_ B.deleteNode (G.getNodes gp)         return ()     where query = "MATCH (x {name: {startName}})-[r]-(friend) WHERE friend.name = {name} RETURN TYPE(r)"-          params = M.fromList [("startName", C.newparam ("I" :: T.Text)), ("name", C.newparam ("you" :: T.Text))]+          params = M.fromList [("startName", TC.newparam ("I" :: T.Text)), ("name", TC.newparam ("you" :: T.Text))]   -- | Test cypher create a node case_cypherCreateNode :: Assertion case_cypherCreateNode = withConnection host port $ do-        res <- C.cypher query params-        neo4jBool $ C.isSuccess res-        let gp = G.addCypher (C.fromSuccess res) G.empty+        res <- TC.loneQuery query params+        let gp = (head . TC.graph) (TC.fromSuccess res)         let props = M.fromList ["name" |: ("Andres" :: T.Text)]         neo4jEqual 1 (length $ G.getNodes gp)         neo4jBool $ props `elem` map getNodeProperties (G.getNodes gp)@@ -1216,14 +1213,14 @@         _ <- B.runBatch $ mapM_ B.deleteNode (G.getNodes gp)         return ()     where query = "CREATE (n:Person { name : {name} }) RETURN n"-          params = M.fromList [("name", C.newparam ("Andres" :: T.Text))]+          params = M.fromList [("name", TC.newparam ("Andres" :: T.Text))]  -- | Test cypher create a node with multiple properties case_cypherCreateNodeMultipleProperties :: Assertion case_cypherCreateNodeMultipleProperties = withConnection host port $ do-        res <- C.cypher query params-        neo4jBool $ C.isSuccess res-        let gp = G.addCypher (C.fromSuccess res) G.empty+        res <- TC.loneQuery query params+        neo4jBool $ TC.isSuccess res+        let gp = (head . TC.graph) (TC.fromSuccess res)         neo4jEqual 1 (length $ G.getNodes gp)         neo4jBool $ props `elem` map getNodeProperties (G.getNodes gp)         -- cleanup@@ -1232,31 +1229,14 @@     where query = "CREATE (n:Person { props }) RETURN n"           props = M.fromList ["name" |: ("Michael" :: T.Text), "position" |: ("Developer" :: T.Text),                               "awesome" |: True, "children" |: (3 :: Int64)]-          params = M.fromList [("props", C.ParamProperties props)]---- | Test cypher create multiple nodes-case_cypherCreateMultipleNodes :: Assertion-case_cypherCreateMultipleNodes = withConnection host port $ do-        res <- C.cypher query params-        neo4jBool $ C.isSuccess res-        let gp = G.addCypher (C.fromSuccess res) G.empty-        neo4jEqual 2 (length $ G.getNodes gp)-        neo4jBool $ propsA `elem` map getNodeProperties (G.getNodes gp)-        neo4jBool $ propsB `elem` map getNodeProperties (G.getNodes gp)-        -- cleanup-        _ <- B.runBatch $ mapM_ B.deleteNode (G.getNodes gp)-        return ()-    where query = "CREATE (n:Person { props }) RETURN n"-          propsA = M.fromList ["name" |: ("Andres" :: T.Text), "position" |: ("Developer" :: T.Text)]-          propsB = M.fromList ["name" |: ("Michael" :: T.Text), "position" |: ("Developer" :: T.Text)]-          params = M.fromList [("props", C.ParamPropertiesArray [propsA, propsB])]+          params = M.fromList [("props", TC.ParamProperties props)]  -- | Test cypher set all properties on a node case_cypherSetAllNodeProperties :: Assertion case_cypherSetAllNodeProperties = withConnection host port $ do-        res <- C.cypher query params-        neo4jBool $ C.isSuccess res-        let gp = G.addCypher (C.fromSuccess res) G.empty+        res <- TC.loneQuery query params+        neo4jBool $ TC.isSuccess res+        let gp = (head . TC.graph) (TC.fromSuccess res)         neo4jEqual 1 (length $ G.getNodes gp)         neo4jBool $ props `elem` map getNodeProperties (G.getNodes gp)         -- cleanup@@ -1265,7 +1245,7 @@     where query = "CREATE (n:Person { name: 'this property is to be deleted' } ) SET n = { props } RETURN n"           props = M.fromList ["name" |: ("Michael" :: T.Text), "position" |: ("Developer" :: T.Text),                               "awesome" |: True, "children" |: (3 :: Int64)]-          params = M.fromList [("props", C.ParamProperties props)]+          params = M.fromList [("props", TC.ParamProperties props)]  -- | Test cypher basic query case_cypherBasicQuery :: Assertion@@ -1278,11 +1258,11 @@             _ <- B.createRelationship "know" M.empty n1 n2             B.createRelationship "know" M.empty n1 n3         -- actual test-        res <- C.cypher query params-        neo4jBool $ C.isSuccess res-        neo4jEqual ["type(r)", "n.name", "n.age"] (C.cols $ C.fromSuccess res)-        neo4jBool $ ["know", "him", J.Number 25] `elem` (C.vals $ C.fromSuccess res)-        neo4jBool $ ["know", "you", J.Null] `elem` (C.vals $ C.fromSuccess res)+        res <- TC.loneQuery query params+        neo4jBool $ TC.isSuccess res+        neo4jEqual ["type(r)", "n.name", "n.age"] (TC.cols $ TC.fromSuccess res)+        neo4jBool $ ["know", "him", J.Number 25] `elem` (TC.vals $ TC.fromSuccess res)+        neo4jBool $ ["know", "you", J.Null] `elem` (TC.vals $ TC.fromSuccess res)         -- cleanup         _ <- B.runBatch $ mapM_ B.deleteRelationship (G.getRelationships gp)         _ <- B.runBatch $ mapM_ B.deleteNode (G.getNodes gp)@@ -1298,18 +1278,23 @@             n1 <- B.createNode $ M.fromList ["name" |: ("I" :: T.Text)]             n2 <- B.createNode $ M.fromList ["name" |: ("you" :: T.Text)]             n3 <- B.createNode $ M.fromList ["name" |: ("him" :: T.Text), "age" |: (25 :: Int64)]-            _ <- B.createRelationship "know" M.empty n1 n2-            B.createRelationship "know" M.empty n1 n3+            B.createRelationship "KNOW" M.empty n1 n2+            B.createRelationship "KNOW" M.empty n1 n3+         -- actual test-        res <- C.cypher query params-        neo4jBool $ C.isSuccess res-        let gres = G.addCypher (C.fromSuccess res) G.empty-        neo4jBool $ (length $ G.getRelationships gres) >= 2-        neo4jBool $ all (\r -> getRelType r == "know") (G.getRelationships gres)-        neo4jEqual ["r"] (C.cols $ C.fromSuccess res)+        res <- TC.loneQuery query params+        TC.loneQuery "MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r" M.empty++        neo4jBool $ TC.isSuccess res+        let row1 = (head . TC.graph . TC.fromSuccess) res+        let row2 = (head . tail . TC.graph . TC.fromSuccess) res++        neo4jEqual 1 (length $ G.getRelationships row1)+        neo4jEqual 1 (length $ G.getRelationships row2)+        neo4jBool $ all (\r -> getRelType r == "KNOW") (G.getRelationships row1)+        neo4jBool $ all (\r -> getRelType r == "KNOW") (G.getRelationships row2)+        neo4jEqual ["r"] (TC.cols $ TC.fromSuccess res)         -- cleanup-        _ <- B.runBatch $ mapM_ B.deleteRelationship (G.getRelationships gp)-        _ <- B.runBatch $ mapM_ B.deleteNode (G.getNodes gp)         return ()     where query = "MATCH (x {name: 'I'})-[r]->(n) RETURN r"           params = M.empty@@ -1323,10 +1308,10 @@             n2 <- B.createNode $ M.fromList ["name" |: ("you" :: T.Text)]             B.createRelationship "know" M.empty n1 n2         -- actual test-        res <- C.cypher query params-        neo4jBool $ C.isSuccess res-        neo4jEqual ["collect(n.name)"] (C.cols $ C.fromSuccess res)-        let v = case (head $ head $ C.vals $ C.fromSuccess res) of+        res <- TC.loneQuery query params+        neo4jBool $ TC.isSuccess res+        neo4jEqual ["collect(n.name)"] (TC.cols $ TC.fromSuccess res)+        let v = case (head $ head $ TC.vals $ TC.fromSuccess res) of                  J.Array vec -> vec                  _ -> V.empty         neo4jBool $ "you" `V.elem` v@@ -1338,21 +1323,6 @@     where query = "MATCH (n) WHERE n.name in ['I', 'you'] RETURN collect(n.name)"           params = M.empty --- | Test cypher errors-case_cypherError :: Assertion-case_cypherError = withConnection host port $ do-        -- create initial data-        gp <- B.runBatch $ do-            B.createNode $ M.fromList ["age" |: (26 :: Int64)]-        -- actual test-        res <- C.cypher query params-        neo4jBool $ res `elem` [Left "BadInputException", Left "ArithmeticException"]-        -- cleanup-        _ <- B.runBatch $ mapM_ B.deleteNode (G.getNodes gp)-        return ()-    where query = "MATCH (x) WHERE has(x.age) RETURN x.age / 0"-          params = M.empty- -- | Test lone transaction cypher basic query case_loneTransactionBasicQuery :: Assertion case_loneTransactionBasicQuery = withConnection host port $ do@@ -1395,8 +1365,9 @@ case_cypherTransaction :: Assertion case_cypherTransaction = withConnection host port $ do         res <- TC.runTransaction $ do-            result <- TC.cypher "CREATE (pere: PERSON {age: {age}}) CREATE (pau: PERSON {props}) \-                              \CREATE p1 = (pere)-[:KNOWS]->(pau) RETURN pere, pau, p1, pere.age" $+            result <- TC.cypher "CREATE (pere: PERSON {age: {age}}) \ +                                \CREATE (pau: PERSON {props}) \+                                \CREATE p1 = (pere)-[:KNOWS]->(pau) RETURN pere, pau, p1, pere.age" $                                 M.fromList [("age", TC.newparam (78 :: Int64)),                                             ("props", TC.ParamProperties $ M.fromList["age" |: (99 :: Int64)])]             voidIO $ assertEqual "" ["pere", "pau", "p1", "pere.age"] (TC.cols result)