packages feed

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\"))"