grakn 0.2.0 → 0.3.0
raw patch · 13 files changed
+503/−173 lines, 13 filesdep ~aesondep ~servantPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: aeson, servant
API changes (from Hackage documentation)
- Grakn: Graph :: BaseUrl -> String -> Graph
- Grakn: InsertResult :: [Text] -> Result
- Grakn: MatchResult :: [Map Var Concept] -> Result
- Grakn: data Graph
- Grakn: data MatchQuery
- Grakn: data Name
- Grakn: distinct :: MatchQuery -> MatchQuery
- Grakn: name :: Text -> Name
- Grakn: select :: [Var] -> MatchQuery -> MatchQuery
+ Grakn: AnswerResult :: (Map Var Concept) -> Result
+ Grakn: AnswersResult :: [Map Var Concept] -> Result
+ Grakn: Client :: BaseUrl -> String -> Client
+ Grakn: Options :: Bool -> Options
+ Grakn: [infer] :: Options -> Bool
+ Grakn: data Client
+ Grakn: data GetQuery
+ Grakn: data Label
+ Grakn: data Match
+ Grakn: data Options
+ Grakn: execute_ :: IsQuery q => Options -> Client -> q -> ExecuteResponse
+ Grakn: get :: [Var] -> Match -> GetQuery
+ Grakn: label :: Text -> Label
- Grakn: (-:) :: (Convert p Pattern, Convert a VarOrName) => p -> a -> Pattern
+ Grakn: (-:) :: (Convert p Pattern, Convert a VarOrLabel) => p -> a -> Pattern
- Grakn: (.:) :: Convert a VarOrName => a -> Var -> RolePlayer
+ Grakn: (.:) :: Convert a VarOrLabel => a -> Var -> RolePlayer
- Grakn: [keyspace] :: Graph -> String
+ Grakn: [keyspace] :: Client -> String
- Grakn: [url] :: Graph -> BaseUrl
+ Grakn: [url] :: Client -> BaseUrl
- Grakn: execute :: IsQuery q => Graph -> q -> IO (Either ServantError Result)
+ Grakn: execute :: IsQuery q => Client -> q -> ExecuteResponse
- Grakn: has :: (Convert p Pattern, Convert a (Either Value Var)) => p -> Name -> a -> Pattern
+ Grakn: has :: (Convert p Pattern, Convert a (Either Value Var)) => p -> Label -> a -> Pattern
- Grakn: hasText :: (Convert p Pattern) => p -> Name -> Text -> Pattern
+ Grakn: hasText :: (Convert p Pattern) => p -> Label -> Text -> Pattern
- Grakn: isa :: (Convert p Pattern, Convert a VarOrName) => p -> a -> Pattern
+ Grakn: isa :: (Convert p Pattern, Convert a VarOrLabel) => p -> a -> Pattern
- Grakn: limit :: Integer -> MatchQuery -> MatchQuery
+ Grakn: limit :: Integer -> Match -> Match
- Grakn: match :: Convert a Pattern => [a] -> MatchQuery
+ Grakn: match :: Convert a Pattern => [a] -> Match
Files
- features/ClientSpec.hs +21/−0
- features/Env.hs +42/−0
- features/GraqlQuerySpec.hs +117/−0
- features/Spec.hs +2/−0
- grakn.cabal +26/−8
- src/Grakn.hs +21/−15
- src/Grakn/Client.hs +65/−58
- src/Grakn/Pattern.hs +10/−9
- src/Grakn/Property.hs +37/−37
- src/Grakn/Query.hs +27/−28
- test/Example.lhs +18/−18
- test/Grakn/ClientSpec.hs +47/−0
- test/GraknSpec.hs +70/−0
+ features/ClientSpec.hs view
@@ -0,0 +1,21 @@+module ClientSpec+ ( spec+ ) where++import Data.Either (isLeft)+import Grakn+import Servant.Client (BaseUrl (BaseUrl), Scheme (Http))+import Test.Hspec++spec :: Spec+spec =+ describe+ "As a Grakn Developer I should be able to connect to a running Grakn Instance and use that instance to issue queries." $+ it "Issuing a query with a broken connection" $ do+ let client = aBrokenConnectionToTheDatabase+ result <- execute client "match $x sub entitty"+ result `shouldSatisfy` isLeft++aBrokenConnectionToTheDatabase :: Client+aBrokenConnectionToTheDatabase =+ Client (BaseUrl Http "1.2.3.4" 5678 "") "akeyspace"
+ features/Env.hs view
@@ -0,0 +1,42 @@+module Env where++import Data.String.Utils (rstrip)+import System.Exit (ExitCode (ExitSuccess))+import System.Process (CreateProcess, createProcess, proc,+ readCreateProcess, waitForProcess)++env :: [String] -> CreateProcess+env = proc "features/grakn-spec/env.sh"++envStart :: IO Bool+envStart = envSuccess ["start", "1.0.0"]++envStop :: IO ()+envStop = do+ _ <- envSuccess ["stop"]+ return ()++envKeyspace :: IO String+envKeyspace = rstrip <$> readCreateProcess (env ["keyspace"]) ""++envInsert :: String -> IO ()+envInsert patterns = do+ _ <- envSuccess ["insert", patterns]+ return ()++envDefine :: String -> IO ()+envDefine patterns = do+ _ <- envSuccess ["define", patterns]+ return ()++envCheckType :: String -> IO Bool+envCheckType label = envSuccess ["check", "type", label]++envCheckInstance :: String -> String -> IO Bool+envCheckInstance res value = envSuccess ["check", "instance", res, value]++envSuccess :: [String] -> IO Bool+envSuccess args = do+ (_, _, _, p) <- createProcess $ env args+ code <- waitForProcess p+ return $ code == ExitSuccess
+ features/GraqlQuerySpec.hs view
@@ -0,0 +1,117 @@+module GraqlQuerySpec+ ( spec+ ) where++import Control.Exception (Exception, displayException)+import Data.Either (isLeft)+import Data.Map (member)+import Data.Text (pack)+import Env+import Grakn+import Test.Hspec++aKnowledgeBaseContainingTypesAndInstances :: IO Client+aKnowledgeBaseContainingTypesAndInstances = do+ client <- givenAKnowledgeBase+ envDefine "person sub entity, has name; name sub attribute, datatype string;"+ envInsert "$alice isa person, has name \"Alice\";"+ return client++givenAKnowledgeBase :: IO Client+givenAKnowledgeBase = Client defaultUrl <$> envKeyspace++x :: Var+x = var $ pack "x"++spec :: Spec+spec =+ before aKnowledgeBaseContainingTypesAndInstances $+ describe+ "As a Grakn Developer, I should be able to interact with a Grakn knowledge base using Graql queries" $ do+ it "Valid Define Query" $ \client -> do+ response <- execute client "define $x label dog sub entity;"+ typeIsInTheKB "dog"+ response `hasVar` x+ it "Redundant Define Query" $ \client -> do+ response <- execute client "define $x label person sub entity;"+ response `hasVar` x+ it "Valid Insert Query" $ \client -> do+ response <- execute client "insert $bob isa person, has name \"Bob\";"+ instanceIsInTheKB "name" "Bob"+ response `hasAnswers` 1+ it "Invalid Insert Query" $ \client -> do+ response <-+ execute client "insert $dunstan isa dog, has name \"Dunstan\";"+ isAnError response+ it "Get Query With Empty Response" $ \client -> do+ response <- execute client "match $x isa person, has name \"Precy\"; get;"+ response `hasAnswers` 0+ it "Get Query With Non-Empty Response" $ \client -> do+ response <- execute client "match $x isa person, has name \"Alice\"; get;"+ response `hasAnswers` 1+ it "Aggregate Ask Query With False Response" $ \client -> do+ response <- execute client "match $x has name \"Precy\"; aggregate ask;"+ response `is` AskResult False+ it "Aggregate Ask Query With True Response" $ \client -> do+ response <- execute client "match $x has name \"Alice\"; aggregate ask;"+ response `is` AskResult True+ it "Aggregate Query" $ \client -> do+ response <- execute client "match $x isa person; aggregate count;"+ response `is` CountResult 1+ it "Compute Query" $ \client -> do+ response <- execute client "compute count in person;"+ response `is` CountResult 1+ it "Successful Undefine Query" $ \client -> do+ envDefine "dog sub entity;"+ response <- execute client "undefine dog sub entity;"+ response `is` DeleteResult+ it "Unsuccessful Undefine Query" $ \client -> do+ response <- execute client "undefine person sub entity;"+ isAnError response+ it "Delete Query for non Existent Concept" $ \client -> do+ response <- execute client "match $x has name \"Precy\"; delete $x;"+ response `is` DeleteResult+ it "Inference on by default" $ \client -> do+ envDefine+ "weird-rule sub rule when { $person has name \"Alice\"; } then { $person has name \"A\"; };"+ response <- execute client "match $x has name \"A\"; get;"+ response `hasAnswers` 1+ it "Inference can be disabled" $ \client -> do+ envDefine+ "weird-rule sub rule when { $person has name \"Alice\"; } then { $person has name \"A\"; };"+ response <-+ execute_ Options {infer = False} client "match $x has name \"A\"; get;"+ response `hasAnswers` 0++is :: (Exception e, Show a, Eq a) => Either e a -> a -> Expectation+response `is` expected =+ case response of+ Right answer -> answer `shouldBe` expected+ Left err -> expectationFailure (displayException err)++isAnError :: Either t a -> Expectation+isAnError response = isLeft response `shouldBe` True++typeIsInTheKB :: String -> Expectation+typeIsInTheKB label = do+ isIn <- envCheckType label+ isIn `shouldBe` True++instanceIsInTheKB :: String -> String -> Expectation+instanceIsInTheKB res val = do+ isIn <- envCheckInstance res val+ isIn `shouldBe` True++hasAnswers :: Exception e => Either e Result -> Int -> Expectation+response `hasAnswers` n =+ case response of+ Right (AnswersResult answers) -> length answers `shouldBe` n+ Left err -> expectationFailure (displayException err)+ x -> expectationFailure (show x)++hasVar :: Exception e => Either e Result -> Var -> Expectation+response `hasVar` var =+ case response of+ Right (AnswerResult answer) -> member var answer `shouldBe` True+ Left err -> expectationFailure (displayException err)+ x -> expectationFailure (show x)
+ features/Spec.hs view
@@ -0,0 +1,2 @@+{-# OPTIONS_GHC+ -F -pgmF hspec-discover -optF --module-name=Spec #-}
grakn.cabal view
@@ -1,7 +1,7 @@ name: grakn-version: 0.2.0-synopsis: A Haskell client for <http://grakn.ai Grakn>-description: A Haskell client for <http://grakn.ai Grakn>+version: 0.3.0+synopsis: A Haskell client for Grakn+description: See README.md license: Apache-2.0 license-file: LICENSE homepage: https://github.com/graknlabs/grakn-haskell@@ -24,7 +24,7 @@ , scientific == 0.3.* , text == 1.2.* , regex-posix == 0.95.*- , servant == 0.9.*+ , servant >= 0.9 , servant-client , http-client , mtl@@ -52,11 +52,11 @@ , grakn , containers == 0.5.* , process == 1.4.*- , aeson == 1.0.*+ , aeson == 1.* , scientific == 0.3.* , text == 1.2.* , regex-posix == 0.95.*- , servant == 0.9.*+ , servant >= 0.9 , servant-client , http-client , mtl@@ -66,6 +66,14 @@ , markdown-unlit , MissingH other-modules: Example+ , Grakn+ , GraknSpec+ , Grakn.Client+ , Grakn.ClientSpec+ , Grakn.Pattern+ , Grakn.Property+ , Grakn.Query+ , Grakn.Util default-language: Haskell2010 ghc-options: -pgmL markdown-unlit default-extensions: MultiParamTypeClasses@@ -82,11 +90,11 @@ , grakn , containers == 0.5.* , process == 1.4.*- , aeson == 1.0.*+ , aeson == 1.* , scientific == 0.3.* , text == 1.2.* , regex-posix == 0.95.*- , servant == 0.9.*+ , servant >= 0.9 , servant-client , http-client , mtl@@ -94,6 +102,16 @@ , hspec , markdown-unlit , MissingH+ other-modules: ClientSpec+ , Env+ , Spec+ , GraqlQuerySpec+ , Grakn+ , Grakn.Client+ , Grakn.Pattern+ , Grakn.Property+ , Grakn.Query+ , Grakn.Util default-language: Haskell2010 default-extensions: MultiParamTypeClasses , FlexibleInstances
src/Grakn.hs view
@@ -1,21 +1,23 @@ module Grakn- ( MatchQuery- , Graph(Graph, keyspace, url)+ ( Match+ , GetQuery+ , Client(Client, keyspace, url) , GraknError- , Result(MatchResult, InsertResult, AskResult, CountResult,+ , Result(AnswersResult, AnswerResult, AskResult, CountResult, DeleteResult)+ , Options(Options, infer) , Var- , Name- , Value(..)+ , Label+ , Value(ValueString, ValueNumber, ValueBool) , defaultUrl , defaultKeyspace , execute+ , execute_ , match- , select- , distinct+ , get , limit , var- , name+ , label , isa , (-:) , (.:)@@ -28,15 +30,19 @@ ) where import Data.Text (Text)-import Grakn.Client (GraknError, Graph (Graph, keyspace, url), Result (AskResult, CountResult, DeleteResult, InsertResult, MatchResult),- defaultKeyspace, defaultUrl, execute)-import Grakn.Pattern-import Grakn.Property-import Grakn.Query+import Grakn.Client (Client (Client, keyspace, url), GraknError,+ Options (Options, infer),+ Result (AnswerResult, AnswersResult, AskResult, CountResult, DeleteResult),+ defaultKeyspace, defaultUrl, execute, execute_)+import Grakn.Pattern (Pattern, has, isa, var_, (<:))+import Grakn.Property (Label, RolePlayer,+ Value (ValueBool, ValueNumber, ValueString),+ Var, VarOrLabel, label, rp, var, (.:))+import Grakn.Query (GetQuery, Match, get, limit, match) import Grakn.Util (Convert) -- |Specify a property has a particular type-(-:) :: (Convert p Pattern, Convert a VarOrName) => p -> a -> Pattern+(-:) :: (Convert p Pattern, Convert a VarOrLabel) => p -> a -> Pattern (-:) = isa -- |Shorthand to define a relation@@ -44,5 +50,5 @@ rel = (var_ <:) -- |Specify a property has a resource-hasText :: (Convert p Pattern) => p -> Name -> Text -> Pattern+hasText :: (Convert p Pattern) => p -> Label -> Text -> Pattern hasText = has
src/Grakn/Client.hs view
@@ -1,33 +1,36 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-} module Grakn.Client- ( Graph(Graph, keyspace, url)- , Concept(Concept, cid, cname, ctype, value)+ ( Client(Client, keyspace, url)+ , Concept(Concept, cid, clabel, ctype, value) , GraknError- , Result(MatchResult, InsertResult, AskResult, CountResult,+ , Result(AnswersResult, AnswerResult, AskResult, CountResult, DeleteResult)+ , Options(Options, infer) , defaultUrl , defaultKeyspace , execute+ , execute_ ) where -import Control.Applicative (empty)-import Data.Aeson (FromJSON, ToJSON, encode, parseJSON,- (.:), (.:?))-import qualified Data.Aeson as Aeson-import Data.Foldable (asum)-import Data.Map (Map)-import Data.Proxy (Proxy (Proxy))-import Data.Text (Text)-import Grakn.Property (Name, Value, Var)-import Grakn.Query (IsQuery (queryString))-import Network.HTTP.Client (defaultManagerSettings, newManager)-import Network.HTTP.Media ((//))-import Servant.API-import Servant.API.ContentTypes (eitherDecodeLenient)-import Servant.Client+import Data.Aeson (FromJSON, Object, parseJSON, withObject,+ (.:), (.:?))+import Data.Aeson.Types (Parser)+import Data.Foldable (asum)+import Data.Map (Map)+import Data.Proxy (Proxy (Proxy))+import Data.Text (Text)+import Grakn.Property (Label, Value, Var)+import Grakn.Query (IsQuery (queryString))+import Network.HTTP.Client (defaultManagerSettings, newManager)+import Servant.API ((:>), Capture, JSON, PlainText, Post,+ QueryParam, ReqBody)+import Servant.Client (BaseUrl (BaseUrl), ClientEnv (ClientEnv),+ ClientM, Scheme (Http), ServantError,+ client, runClientM) -data Graph = Graph+data Client = Client { url :: BaseUrl , keyspace :: String }@@ -36,21 +39,21 @@ GraknError String deriving (Eq, Show) --- |A result of a match query, binding variables to concepts+-- |A result of a query data Result- = MatchResult [Map Var Concept]- | InsertResult [Text]+ = AnswersResult [Map Var Concept]+ | AnswerResult (Map Var Concept)+ | DeleteResult | AskResult Bool | CountResult Integer- | DeleteResult deriving (Show, Eq) --- |A concept in the graph+-- |A concept in the knowledge base data Concept = Concept- { cid :: Text- , cname :: Maybe Name- , ctype :: Maybe Name- , value :: Maybe Value+ { cid :: Text+ , clabel :: Maybe Label+ , ctype :: Maybe Label+ , value :: Maybe Value } deriving (Show, Eq) -- |The default Grakn URL, accessing localhost@@ -61,55 +64,59 @@ defaultKeyspace :: String defaultKeyspace = "grakn" -execute :: IsQuery q => Graph -> q -> IO (Either ServantError Result)-execute (Graph u ks) query = do+data Options = Options+ { infer :: Bool+ }++type ExecuteResponse = IO (Either ServantError Result)++execute :: IsQuery q => Client -> q -> ExecuteResponse+execute = executeOpts Nothing++execute_ :: IsQuery q => Options -> Client -> q -> ExecuteResponse+execute_ opts = executeOpts (Just opts)++executeOpts :: IsQuery q => Maybe Options -> Client -> q -> ExecuteResponse+executeOpts opts (Client u ks) query = do manager <- newManager defaultManagerSettings let env = ClientEnv manager u- runClientM (graqlGet (queryString query) ks) env--type Keyspace = QueryParam "keyspace" String+ runClientM (graqlGet opts (queryString query) ks) env type Infer = QueryParam "infer" Bool -type Materialise = QueryParam "materialise" Bool--type Body = ReqBody '[ PlainText] String+type Query = ReqBody '[ PlainText] String -type GraknParams- = Keyspace :> Infer :> Materialise :> Body :> Post '[ GraqlJSON] Result+type Keyspace = Capture "keyspace" String -- |A type describing the REST API we use to execute queries-type GraknAPI = "graph" :> "graql" :> "execute" :> GraknParams+type GraknAPI+ = "kb" :> Keyspace :> "graql" :> Infer :> Query :> Post '[ JSON] Result graknAPI :: Proxy GraknAPI graknAPI = Proxy -graqlGet :: String -> String -> ClientM Result-graqlGet query ks = client graknAPI (Just ks) (Just False) (Just False) query+graqlGet :: Maybe Options -> String -> String -> ClientM Result+graqlGet opts query ks = client graknAPI ks (infer <$> opts) query +-- This is a helper method for chaining accessing optional JSON fields+(.:>) :: FromJSON a => Parser (Maybe Object) -> Text -> Parser (Maybe a)+maybeParser .:> key = traverse (.: key) =<< maybeParser+ instance FromJSON Concept where- parseJSON (Aeson.Object obj) =- Concept <$> (obj .: "id") <*> (obj .:? "name") <*> (obj .:? "isa") <*>- (obj .:? "value")- parseJSON _ = empty+ parseJSON =+ withObject "Concept" $ \obj -> do+ cid <- obj .: "id"+ clabel <- obj .:? "label"+ ctype <- obj .:? "type" .:> "label"+ value <- obj .:? "value"+ return Concept {..} instance FromJSON Result where parseJSON val = asum- [ MatchResult <$> parseJSON val- , InsertResult <$> parseJSON val+ [ AnswersResult <$> parseJSON val+ , AnswerResult <$> parseJSON val , AskResult <$> parseJSON val , CountResult <$> parseJSON val , pure DeleteResult ]--data GraqlJSON--instance Accept GraqlJSON where- contentType _ = "application" // "graql+json"--instance ToJSON a => MimeRender GraqlJSON a where- mimeRender _ = encode--instance FromJSON a => MimeUnrender GraqlJSON a where- mimeUnrender _ = eitherDecodeLenient
src/Grakn/Pattern.hs view
@@ -6,12 +6,13 @@ , has ) where -import Grakn.Property+import Grakn.Property (Label, Property (Has, Isa, Rel), RolePlayer,+ Value, Var, VarOrLabel) import Grakn.Util (Convert (convert), spaces, with) --- |A pattern to find in the graph+-- |A pattern to find in the knowledge base data Pattern =- Pattern (Maybe VarOrName)+ Pattern (Maybe VarOrLabel) [Property] -- |Create an anonymous variable@@ -19,7 +20,7 @@ var_ = Pattern Nothing [] -- |Specify a property has a particular type-isa :: (Convert p Pattern, Convert a VarOrName) => p -> a -> Pattern+isa :: (Convert p Pattern, Convert a VarOrLabel) => p -> a -> Pattern patt `isa` x = addProperty patt (Isa $ convert x) -- |Specify a property is a relation between other variables@@ -30,7 +31,7 @@ has :: (Convert p Pattern, Convert a (Either Value Var)) => p- -> Name+ -> Label -> a -> Pattern has patt rt v = addProperty patt (Has rt $ convert v)@@ -49,10 +50,10 @@ show (Pattern v props) = v `with` " " ++ showProps props ++ ";" instance Convert Var Pattern where- convert = convert . (convert :: Var -> VarOrName)+ convert = convert . (convert :: Var -> VarOrLabel) -instance Convert Name Pattern where- convert = convert . (convert :: Name -> VarOrName)+instance Convert Label Pattern where+ convert = convert . (convert :: Label -> VarOrLabel) -instance Convert VarOrName Pattern where+instance Convert VarOrLabel Pattern where convert v = Pattern (Just v) []
src/Grakn/Property.hs view
@@ -1,12 +1,12 @@ module Grakn.Property- ( Property(..)+ ( Property(Isa, Has, Rel) , RolePlayer , Var- , Name- , VarOrName- , Value(..)+ , Label+ , VarOrLabel+ , Value(ValueString, ValueNumber, ValueBool) , var- , name+ , label , (.:) , rp ) where@@ -23,26 +23,26 @@ -- |A property of a concept data Property- = Isa VarOrName- | NameProperty Name+ = Isa VarOrLabel+ | LabelProperty Label | Rel [RolePlayer]- | Has Name+ | Has Label (Either Value Var) --- |A variable name wildcard that will represent a concept in the results+-- |A variable that will represent a concept in the results newtype Var = Var Text deriving (Eq, Ord) --- |A name of something in the graph-newtype Name =- Name Text+-- |A label of something in the knowledge base+newtype Label =+ Label Text deriving (Eq) --- |Something that may be a variable name or a type name-data VarOrName+-- |Something that may be a variable or a type label+data VarOrLabel = VarName Var- | TypeName Name+ | TypeLabel Label -- |A value of a resource data Value@@ -53,33 +53,33 @@ -- |A casting, relating a role type and role player data RolePlayer =- RolePlayer (Maybe VarOrName)+ RolePlayer (Maybe VarOrLabel) Var -- |Create a variable var :: Text -> Var var = Var --- |Create a name of something in the graph-name :: Text -> Name-name = Name+-- |Create a label of something in the knowledge base+label :: Text -> Label+label = Label -- |A casting in a relation between a role type and a role player-(.:) :: Convert a VarOrName => a -> Var -> RolePlayer+(.:) :: Convert a VarOrLabel => a -> Var -> RolePlayer rt .: player = RolePlayer (Just $ convert rt) player -- |A casting in a relation without a role type rp :: Var -> RolePlayer rp = RolePlayer Nothing -nameRegex :: String-nameRegex = "^[a-zA-Z_][a-zA-Z0-9_-]*$"+labelRegex :: String+labelRegex = "^[a-zA-Z_][a-zA-Z0-9_-]*$" instance Show Property where- show (Isa varOrName) = "isa " ++ show varOrName- show (NameProperty n) = "type-name " ++ show n- show (Rel castings) = "(" ++ commas castings ++ ")"- show (Has rt value) = "has " ++ show rt ++ " " ++ showEither value+ show (Isa varOrLabel) = "isa " ++ show varOrLabel+ show (LabelProperty n) = "label " ++ show n+ show (Rel castings) = "(" ++ commas castings ++ ")"+ show (Has rt value) = "has " ++ show rt ++ " " ++ showEither value instance Show RolePlayer where show (RolePlayer roletype player) = roletype `with` ": " ++ show player@@ -89,9 +89,9 @@ show (ValueNumber num) = show num show (ValueBool bool) = show bool -instance Show Name where- show (Name text)- | str =~ nameRegex = str+instance Show Label where+ show (Label text)+ | str =~ labelRegex = str | otherwise = show text where str = unpack text@@ -99,15 +99,15 @@ instance Show Var where show (Var v) = '$' : unpack v -instance Show VarOrName where- show (VarName v) = show v- show (TypeName t) = show t+instance Show VarOrLabel where+ show (VarName v) = show v+ show (TypeLabel t) = show t -instance Convert Var VarOrName where+instance Convert Var VarOrLabel where convert = VarName -instance Convert Name VarOrName where- convert = TypeName+instance Convert Label VarOrLabel where+ convert = TypeLabel instance Convert Var RolePlayer where convert = rp@@ -130,8 +130,8 @@ parseJSON (Aeson.Bool b) = return $ ValueBool b parseJSON _ = empty -instance FromJSON Name where- parseJSON (Aeson.String s) = return $ name s+instance FromJSON Label where+ parseJSON (Aeson.String s) = return $ label s parseJSON _ = empty instance FromJSON Var where
src/Grakn/Query.hs view
@@ -1,51 +1,50 @@ module Grakn.Query ( IsQuery(queryString)- , MatchQuery+ , Match+ , GetQuery , match- , select+ , get , limit- , distinct ) where -import Grakn.Pattern-import Grakn.Property-import Grakn.Util+import Grakn.Pattern (Pattern)+import Grakn.Property (Var)+import Grakn.Util (Convert, commas, convert, spaces) class IsQuery q where queryString :: q -> String --- |A Graql 'match' query that finds a pattern in the graph-data MatchQuery+-- |A Graql 'match' part that finds a pattern in the knowledge base+data Match = Match [Pattern]- | Select MatchQuery- [Var]- | Limit MatchQuery+ | Limit Match Integer- | Distinct MatchQuery --- |Create a match query by providing a list of patterns-match :: Convert a Pattern => [a] -> MatchQuery+data GetQuery =+ GetQuery Match+ [Var]++-- |Create a match by providing a list of patterns+match :: Convert a Pattern => [a] -> Match match = Match . map convert --- |Select variables from a match query, intended to be used infix-select :: [Var] -> MatchQuery -> MatchQuery-select = flip Select+-- |Get variables from a match, intended to be used infix+get :: [Var] -> Match -> GetQuery+get = flip GetQuery --- |Limit a match query, intended to be used infix-limit :: Integer -> MatchQuery -> MatchQuery+-- |Limit a match, intended to be used infix+limit :: Integer -> Match -> Match limit = flip Limit --- |Retrieve only distinct results from a match query-distinct :: MatchQuery -> MatchQuery-distinct = Distinct+instance Show Match where+ show (Match patts) = "match " ++ spaces patts+ show (Limit mq lim) = show mq ++ " limit " ++ show lim ++ ";" -instance Show MatchQuery where- show (Match patts) = "match " ++ spaces patts- show (Select mq vars) = show mq ++ " select " ++ commas vars ++ ";"- show (Limit mq lim) = show mq ++ " limit " ++ show lim ++ ";"- show (Distinct mq) = show mq ++ " distinct;"+instance Show GetQuery where+ show (GetQuery match_ []) = show match_ ++ " get;"+ show (GetQuery match_ vars) = show match_ ++ " get " ++ commas vars ++ ";" -instance IsQuery MatchQuery where+instance IsQuery GetQuery where queryString = show instance IsQuery String where
test/Example.lhs view
@@ -2,7 +2,7 @@ A Haskell client for [Grakn](http://grakn.ai). -Requires Grakn 0.16.+Requires Grakn 1.0.0. # Installation @@ -30,20 +30,20 @@ import Data.Function ((&)) ``` -Define the type names:+Define the type labels: ```haskell-person :: Name-person = name "person"+person :: Label+person = label "person" -husband :: Name-husband = name "husband"+husband :: Label+husband = label "husband" -wife :: Name-wife = name "wife"+wife :: Label+wife = label "wife" -marriage :: Name-marriage = name "marriage"+marriage :: Label+marriage = label "marriage" ``` Define the variables:@@ -59,35 +59,35 @@ We can translate the following query into Haskell: ```graql-match $x isa person, (husband: $x, wife: $y) isa marriage; select $y;+match $x isa person, (husband: $x, wife: $y) isa marriage; get $y; ``` ```haskell-query :: MatchQuery+query :: GetQuery query = match [ x `isa` person , rel [husband .: x, wife .: y] `isa` marriage- ] & select [y]+ ] & get [y] ``` We can also use infix functions like `(-:)` instead of `isa`: ```haskell-otherQuery :: MatchQuery+otherQuery :: GetQuery otherQuery = match [ x -: person , rel [husband .: x, wife .: y] -: marriage- ] & select [y]+ ] & get [y] ``` To execute and print the results of our query: ```haskell-graph :: Graph-graph = Graph defaultUrl "my-keyspace"+client :: Client+client = Client defaultUrl "my-keyspace" main :: IO () main = do- result <- execute graph query+ result <- execute client query print result ```
+ test/Grakn/ClientSpec.hs view
@@ -0,0 +1,47 @@+{-# LANGUAGE OverloadedStrings #-}++module Grakn.ClientSpec+ ( spec+ ) where++import Data.Aeson (decode)+import Grakn (Value (ValueNumber), label)+import Grakn.Client+import qualified Servant.Client as S+import Test.Hspec+import Test.QuickCheck (Arbitrary, arbitrary, elements, property)++spec :: Spec+spec = do+ it "The client's keyspace is set correctly" $+ property $ \(BaseUrl u) k ->+ let client = Client {url = u, keyspace = k}+ in keyspace client `shouldBe` k+ it "The client's URI is set correctly" $+ property $ \(BaseUrl u) k ->+ let client = Client {url = u, keyspace = k}+ in url client `shouldBe` u+ it "type can be parsed from JSON" $ do+ let person = Just (label "person")+ let json = "{\"id\": \"V123\", \"label\": \"person\"}"+ let concept =+ Concept+ {cid = "V123", clabel = person, ctype = Nothing, value = Nothing}+ decode json `shouldBe` Just concept+ it "instance can be parsed from JSON" $ do+ let number = Just (label "number")+ let val = Just (ValueNumber 3)+ let json =+ "{\"id\": \"V123\", \"type\": {\"label\": \"number\"}, \"value\": 3}"+ let concept =+ Concept {cid = "V123", clabel = Nothing, ctype = number, value = val}+ decode json `shouldBe` Just concept++newtype BaseUrl =+ BaseUrl S.BaseUrl+ deriving (Show)++instance Arbitrary BaseUrl where+ arbitrary = do+ let scheme = elements [S.Http, S.Https]+ BaseUrl <$> (S.BaseUrl <$> scheme <*> arbitrary <*> arbitrary <*> arbitrary)
+ test/GraknSpec.hs view
@@ -0,0 +1,70 @@+{-# LANGUAGE OverloadedStrings #-}++module GraknSpec+ ( spec+ ) where++import Data.Function ((&))+import qualified Example+import Grakn+import Test.Hspec++spec :: Spec+spec = do+ it "a simple query string representation" $+ match [x `isa` person] & get [] ~= "match $x isa person; get;"+ it "a relation query string representation" $+ match [rel [x, y]] & get [] ~= "match ($x, $y); get;"+ it "a relation query string representation with types" $+ match [rel [husband .: x, wife .: y] -: marriage] & get [] ~=+ "match (husband: $x, wife: $y) isa marriage; get;"+ it "a resource query string representation" $+ match [x `hasText` firstName $ "Bob"] & get [] ~=+ "match $x has first-name \"Bob\"; get;"+ it "multiple patterns" $+ match [x -: person, y -: firstName] & get [] ~=+ "match $x isa person; $y isa first-name; get;"+ it "get query with type" $+ match [x -: y] & get [x, y] ~= "match $x isa $y; get $x, $y;"+ it "mix role types" $+ match [rel [husband .: x, rp y]] & get [] ~= "match (husband: $x, $y); get;"+ it "limit" $+ match [x -: person] & limit 10 & get [] ~=+ "match $x isa person; limit 10; get;"+ it "type of type" $+ match [person -: x] & get [] ~= "match person isa $x; get;"+ it "reify a relation" $+ match [x <: [y, z]] & get [] ~= "match $x ($y, $z); get;"+ it "match just a variable" $ match [x] & get [] ~= "match $x; get;"+ it "example query output" $+ Example.query ~=+ "match $x isa person; (husband: $x, wife: $y) isa marriage; get $y;"++x :: Var+x = var "x"++y :: Var+y = var "y"++z :: Var+z = var "z"++person :: Label+person = label "person"++firstName :: Label+firstName = label "first-name"++marriage :: Label+marriage = label "marriage"++husband :: Label+husband = label "husband"++wife :: Label+wife = label "wife"++infixr 0 ~=++(~=) :: GetQuery -> String -> Expectation+(~=) = shouldBe . show