greskell-1.2.0.0: test/Data/Greskell/GTraversalSpec.hs
{-# LANGUAGE OverloadedStrings #-}
module Data.Greskell.GTraversalSpec (main,spec) where
import Control.Category ((>>>), (<<<))
import Data.Aeson (ToJSON(..), Value(Number))
import Data.Function ((&))
import Data.Text (Text)
import System.IO (stderr, hPutStrLn)
import Test.Hspec
import Data.Greskell.AsLabel (AsLabel)
import Data.Greskell.Gremlin
( oIncr, oDecr, oShuffle,
pEq, pNeq, pInside, pGte
)
import Data.Greskell.Graph
( Element, ElementID(..), AVertex,
Key, key,
tLabel, tId
)
import Data.Greskell.GraphSON (nonTypedGValue, GValueBody(..))
import Data.Greskell.Greskell
( toGremlin, Greskell, gvalueInt)
import Data.Greskell.GTraversal
( Walk, Transform, Filter,
source, (&.), ($.), sV', sE',
gHas1, gHas2, gHas2', gHas2P, gHasLabelP, gHasIdP, gIs, gIs',
gOut', gRange, gValues, gNot, gIn',
gOrder,
gProperties, gHasKeyP, gHasValueP,
ByComparator(..), gBy2, gBy1, gBy,
gRepeat, gTimes, gUntilHead, gUntilTail,
gEmitHead, gEmitTail, gEmitHeadT, gEmitTailT,
gLoops,
gWhereP1, gAs, gLabel, gWhereP2,
gMatch, mPattern
)
import Data.Greskell.Logic (Logic(..))
main :: IO ()
main = hspec spec
spec :: Spec
spec = do
spec_GraphTraversalSource
spec_order_by
spec_compose_steps
spec_has
spec_repeat
spec_where
spec_match
spec_GraphTraversalSource :: Spec
spec_GraphTraversalSource = describe "GraphTraversalSource" $ do
specify "g.V()" $ do
(toGremlin $ sV' [] $ source "g") `shouldBe` ("g.V()")
specify "g.V(1,2,3)" $ do
let ids = map (fmap ElementID . gvalueInt) $ ([1,2,3] :: [Int])
(toGremlin $ sV' ids $ source "g") `shouldBe` ("g.V(1,2,3)")
spec_order_by :: Spec
spec_order_by = describe "gOrder" $ do
let gv = source "g" & sV' []
specify "no arg" $ do
toGremlin (gv &. gOrder []) `shouldBe` "g.V().order()"
specify "empty projection" $ do
-- This case is relatively rare (I think), so the API is not so convenient for now.
toGremlin (gv &. gOrder [ByComparatorComp oIncr]) `shouldBe` "g.V().order().by(Order.incr)"
specify "traversal projection" $ do
toGremlin (gv &. gOrder [gBy2 (gOut' ["foo"] >>> gIn' ["bar"]) oShuffle])
`shouldBe` "g.V().order().by(__.out(\"foo\").in(\"bar\"),Order.shuffle)"
specify "value projection" $ do
let nameKey :: Key e Text
nameKey = "name"
toGremlin (gv &. gOrder [gBy2 nameKey oDecr]) `shouldBe` "g.V().order().by(\"name\",Order.decr)"
specify "T token projection" $ do
toGremlin (gv &. gOrder [gBy2 tLabel oIncr]) `shouldBe` "g.V().order().by(T.label,Order.incr)"
specify "multiple .by steps of different comparison types" $ do
let ageKey :: Key e Int
ageKey = "age"
toGremlin (gv &. gOrder [gBy2 ageKey oDecr, gBy2 tId oDecr, gBy1 (gOut' ["foo"])])
`shouldBe` "g.V().order().by(\"age\",Order.decr).by(T.id,Order.decr).by(__.out(\"foo\"))"
specify "gBy1" $ do
toGremlin (gv &. gOrder [gBy1 (key "name")]) `shouldBe` "g.V().order().by(\"name\")"
specify "IsString instance of ByComparator" $ do
toGremlin (gv &. gOrder ["age"]) `shouldBe` "g.V().order().by(\"age\")"
specify "gBy" $ do
toGremlin (gv &. gOrder [ByComparatorProj $ gBy tLabel]) `shouldBe` "g.V().order().by(T.label)"
specify "IsString instance of ByProjection" $ do
toGremlin (gv &. gOrder [ByComparatorProjComp ("name") oIncr])
`shouldBe` "g.V().order().by(\"name\",Order.incr)"
spec_compose_steps :: Spec
spec_compose_steps = describe "DSL to compose steps" $ do
specify "(&) and (&.)" $ do
let gt = source "g" & sV' [] &. gHas2P ("x" :: Key e Int) (pEq 100) &. gOut' [] &. gRange 0 100
toGremlin gt `shouldBe` "g.V().has(\"x\",P.eq(100)).out().range(0,100)"
specify "(&) and (&.) and (>>>)" $ do
let vid = fmap ElementID $ gvalueInt (200 :: Int)
gt = source "g" & sV' [vid] &. (gOut' [] >>> gOut' ["friends_to"] >>> gValues ["name"])
toGremlin gt `shouldBe` "g.V(200).out().out(\"friends_to\").values(\"name\")"
specify "($) and ($.)" $ do
let gt = gRange 20 30 $. gNot (gOut' ["friends_to"]) $. sV' [] $ source "g"
toGremlin gt `shouldBe` "g.V().not(__.out(\"friends_to\")).range(20,30)"
specify "($) and ($.) and (<<<)" $ do
let gt = gHas2P (key "name" :: Key e Text) (pEq "hoge") <<< gIn' ["foo", "bar"] <<< gIn' [] $. sV' [] $ source "g"
toGremlin gt `shouldBe` "g.V().in().in(\"foo\",\"bar\").has(\"name\",P.eq(\"hoge\"))"
spec_has :: Spec
spec_has = do
describe "gHas1" $ do
specify "IsString Key" $ do
toGremlin (source "g" & sV' [] &. gHas1 "foo") `shouldBe` "g.V().has(\"foo\")"
describe "gHas2" $ do
specify "simple equality" $ do
toGremlin (source "g" & sV' [] &. gHas2 "name" ("hoge" :: Greskell Text))
`shouldBe` "g.V().has(\"name\",\"hoge\")"
describe "gHas2P" $ do
specify "IsString Key and P" $ do
toGremlin (source "g" & sV' [] &. gHas2P ("name" :: Key e Text) (pNeq "hoge"))
`shouldBe` "g.V().has(\"name\",P.neq(\"hoge\"))"
describe "gHasLabelP" $ do
specify "P" $ do
toGremlin (source "g" & sE' [] &. gHasLabelP (pNeq "friends_to"))
`shouldBe` "g.E().hasLabel(P.neq(\"friends_to\"))"
describe "gHasIdP" $ do
specify "P" $ do
let toID :: Int -> Greskell (ElementID AVertex)
toID = fmap ElementID . gvalueInt
toGremlin (source "g" & sV' [] &. gHasIdP (pInside (toID 10) (toID 20)))
`shouldBe` "g.V().hasId(P.inside(10,20))"
describe "gHasKeyP, gProperties" $ do
specify "P" $ do
toGremlin (source "g" & sV' [] &. gProperties [] &. gHasKeyP (pEq "hoge"))
`shouldBe` "g.V().properties().hasKey(P.eq(\"hoge\"))"
describe "gHasValueP, gProperties" $ do
specify "P" $ do
toGremlin (source "g" & sV' [] &. gProperties ["age" :: Key e Int] &. gHasValueP (pGte 20))
`shouldBe` "g.V().properties(\"age\").hasValue(P.gte(20))"
spec_repeat :: Spec
spec_repeat = do
let hasName :: Greskell Text -> Walk Filter AVertex AVertex
hasName v = gHas2 keyName v
keyName :: Key AVertex Text
keyName = "name"
describe "gRepeat" $ do
specify "no modulation" $ do
toGremlin (source "g" & sV' [] &. gRepeat Nothing Nothing Nothing (gOut' []))
`shouldBe` "g.V().repeat(__.out())"
specify "gTimes and gEmitHead" $ do
toGremlin (source "g" & sV' [] &. gRepeat Nothing (gTimes 3) gEmitHead (gOut' []))
`shouldBe` "g.V().times(3).emit().repeat(__.out())"
specify "gUntilHead and gEmitTail" $ do
toGremlin (source "g" & sV' [] &. gRepeat Nothing (gUntilHead $ hasName "foo") gEmitTail (gOut' []))
`shouldBe` "g.V().until(__.has(\"name\",\"foo\")).repeat(__.out()).emit()"
specify "gUntilTail and gEmitHeadT" $ do
toGremlin (source "g" & sV' [] &. gRepeat Nothing (gUntilTail $ hasName "foo") (gEmitHeadT $ hasName "bar") (gOut' []))
`shouldBe` "g.V().emit(__.has(\"name\",\"bar\")).repeat(__.out()).until(__.has(\"name\",\"foo\"))"
specify "gUntilTail and gEmitTailT" $ do
toGremlin (source "g" & sV' [] &. gRepeat Nothing (gUntilTail $ hasName "foo") (gEmitTailT $ hasName "bar") (gOut' []))
`shouldBe` "g.V().repeat(__.out()).until(__.has(\"name\",\"foo\")).emit(__.has(\"name\",\"bar\"))"
specify "gLoops without label" $ do
toGremlin (source "g" & sV' [] &. gRepeat Nothing (gUntilTail $ gLoops Nothing >>> gIs 5) Nothing (gOut' []))
`shouldBe` "g.V().repeat(__.out()).until(__.loops().is(5))"
specify "gLoops with label" $ do
let loop_label = "LP"
toGremlin (source "g" & sV' [] &. gRepeat (Just loop_label) (gUntilTail $ gLoops (Just loop_label) >>> gIs 5) Nothing (gOut' []))
`shouldBe` "g.V().repeat(\"LP\",__.out()).until(__.loops(\"LP\").is(5))"
spec_where :: Spec
spec_where = do
describe "gWhereP1" $ do
specify "no modulation" $ do
let la :: AsLabel AVertex
la = "a"
toGremlin (source "g" & sV' [] &. gAs la &. gOut' [] &. gWhereP1 (pEq la) Nothing)
`shouldBe` "g.V().as(\"a\").out().where(P.eq(\"a\"))"
specify "with modulation (by traversal)" $ do
let la :: AsLabel AVertex
la = "a"
toGremlin (source "g" & sV' [] &. gAs la &. gOut' [] &. gWhereP1 (pGte la) (Just $ gBy gLabel))
`shouldBe` "g.V().as(\"a\").out().where(P.gte(\"a\")).by(__.label())"
describe "gWhereP2" $ do
specify "no modulation" $ do
let la = ("a" :: AsLabel AVertex)
lb = ("b" :: AsLabel AVertex)
age = ("age" :: Key AVertex Int)
toGremlin (source "g" & sV' [] &. gAs la &. gOut' [] &. gAs lb &. gValues [age] &. gWhereP2 la (pEq lb) Nothing)
`shouldBe` "g.V().as(\"a\").out().as(\"b\").values(\"age\").where(\"a\",P.eq(\"b\"))"
specify "with modulation" $ do
let la = ("a" :: AsLabel AVertex)
lb = ("b" :: AsLabel AVertex)
age = ("age" :: Key AVertex Int)
name = ("name" :: Key AVertex Text)
toGremlin (source "g" & sV' [] &. gAs la &. gOut' [] &. gAs lb &. gValues [name] &. gWhereP2 la (pGte lb) (Just $ gBy age))
`shouldBe` "g.V().as(\"a\").out().as(\"b\").values(\"name\").where(\"a\",P.gte(\"b\")).by(\"age\")"
spec_match :: Spec
spec_match = do
describe "gMatch" $ do
specify "top-level Leaf" $ do
let pat = mPattern label (gOut' [])
label = ("a" :: AsLabel AVertex)
toGremlin (source "g" & sV' [] &. gMatch pat)
`shouldBe` "g.V().match(__.as(\"a\").out())"
specify "top-level And" $ do
let pat = And
( mPattern label_a (gOut' [] >>> gAs label_b) )
[ mPattern label_a (gHas2' the_key "foobar")
]
label_a = ("a" :: AsLabel AVertex)
label_b = "b"
the_key = ("k" :: Key AVertex Text)
toGremlin (source "g" & sV' [] &. gMatch pat)
`shouldBe` "g.V().match(__.as(\"a\").out().as(\"b\"),__.as(\"a\").has(\"k\",\"foobar\"))"
specify "top-level Or" $ do
let pat = Or
( mPattern label (gHas2' key1 "foobar") )
[ mPattern label (gHas2' key2 100)
]
label = ("a" :: AsLabel AVertex)
key1 = ("k1" :: Key AVertex Text)
key2 = ("k2" :: Key AVertex Int)
toGremlin (source "g" & sV' [] &. gMatch pat)
`shouldBe` "g.V().match(__.or(__.as(\"a\").has(\"k1\",\"foobar\"),__.as(\"a\").has(\"k2\",100)))"
specify "top-level Not" $ do
let pat = Not ( mPattern label (gHas2' k "quux") )
label = ("a" :: AsLabel AVertex)
k = ("k" :: Key AVertex Text)
toGremlin (source "g" & sV' [] &. gMatch pat)
`shouldBe` "g.V().match(__.not(__.as(\"a\").has(\"k\",\"quux\")))"
specify "heterogeneous patterns, nested And" $ do
let pat = Or
( And
( mPattern label_a (gOut' [] >>> gAs label_b) )
[ mPattern label_b (gValues [key_age] >>> gAs label_c),
mPattern label_c (gIs' 30)
]
)
[ And
( mPattern label_a (gValues [key_age] >>> gAs label_c) )
[ mPattern label_a (gValues [key_name] >>> gAs label_d),
Not ( mPattern label_d (gIs' "toshio") )
]
]
label_a = ("a" :: AsLabel AVertex)
label_b = "b"
label_c = "c"
label_d = "d"
key_age = ("age" :: Key AVertex Int)
key_name = ("name" :: Key AVertex Text)
toGremlin (source "g" & sV' [] &. gMatch pat)
`shouldBe` "g.V().match(__.or(__.and(__.as(\"a\").out().as(\"b\"),__.as(\"b\").values(\"age\").as(\"c\"),__.as(\"c\").is(30)),__.and(__.as(\"a\").values(\"age\").as(\"c\"),__.as(\"a\").values(\"name\").as(\"d\"),__.not(__.as(\"d\").is(\"toshio\")))))"
specify "history labels in pattern" $ do
let pat = mPattern ext_label (gIn' [] >>> gHas2 the_key "foo")
ext_label = "e"
the_key = ("k" :: Key AVertex Text)
toGremlin (source "g" & sV' [] &. gAs ext_label &. gOut' [] &. gMatch pat)
`shouldBe` "g.V().as(\"e\").out().match(__.as(\"e\").in().has(\"k\",\"foo\"))"