packages feed

greskell-2.0.1.0: test/Data/Greskell/GTraversalSpec.hs

{-# LANGUAGE OverloadedStrings #-}
module Data.Greskell.GTraversalSpec
    ( main
    , spec
    ) where

import           Control.Category         ((<<<), (>>>))
import           Control.Monad            (forM_)
import           Data.Aeson               (ToJSON (..), Value (Number))
import           Data.Function            ((&))
import           Data.Text                (Text, unpack)
import           System.IO                (hPutStrLn, stderr)

import           Test.Hspec

import           Data.Greskell.AsLabel    (AsLabel)
import           Data.Greskell.Graph      (AVertex, Element, ElementID (..), Key, key, tId, tLabel)
import           Data.Greskell.GraphSON   (GValueBody (..), nonTypedGValue)
import           Data.Greskell.Gremlin    (oDecr, oIncr, oShuffle, pEq, pGte, pInside, pNeq)
import           Data.Greskell.Greskell   (Greskell, gvalueInt, toGremlin)
import           Data.Greskell.GTraversal (ByComparator (..), Filter, Transform, Walk, gAs, gBy,
                                           gBy1, gBy2, gEmitHead, gEmitHeadT, gEmitTail, gEmitTailT,
                                           gHas1, gHas2, gHas2', gHas2P, gHasIdP, gHasKeyP,
                                           gHasLabelP, gHasValueP, gIn', gIs, gIs', gLabel, gLoops,
                                           gMatch, gNot, gOrder, gOut', gProperties, gRange,
                                           gRepeat, gTimes, gUntilHead, gUntilTail, gValues,
                                           gWhereP1, gWhereP2, mPattern, sE', sV', source, ($.),
                                           (&.))
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\"))"