move lib/go/gremlin to ent/dialect (#1192)

Summary:
Pull Request resolved: https://github.com/facebookexternal/fbc/pull/1192

Pull Request resolved: https://github.com/facebookincubator/ent/pull/11

Reviewed By: alexsn

Differential Revision: D16377224

fbshipit-source-id: 07ca7436eb9b64fbe2299568560b91466b2417ba
This commit is contained in:
Ariel Mashraki
2019-07-20 08:14:34 -07:00
committed by Facebook Github Bot
parent 8b2447b8eb
commit 1e47de5300
175 changed files with 8626 additions and 478 deletions

View File

@@ -0,0 +1,53 @@
package __
import "fbc/ent/dialect/gremlin/graph/dsl"
// As is the api for calling __.As().
func As(args ...interface{}) *dsl.Traversal { return New().As(args...) }
// Is is the api for calling __.Is().
func Is(args ...interface{}) *dsl.Traversal { return New().Is(args...) }
// Not is the api for calling __.Not().
func Not(args ...interface{}) *dsl.Traversal { return New().Not(args...) }
// Has is the api for calling __.Has().
func Has(args ...interface{}) *dsl.Traversal { return New().Has(args...) }
// Or is the api for calling __.Or().
func Or(args ...interface{}) *dsl.Traversal { return New().Or(args...) }
// In is the api for calling __.In().
func In(args ...interface{}) *dsl.Traversal { return New().In(args...) }
// Out is the api for calling __.Out().
func Out(args ...interface{}) *dsl.Traversal { return New().Out(args...) }
// OutE is the api for calling __.OutE().
func OutE(args ...interface{}) *dsl.Traversal { return New().OutE(args...) }
// InE is the api for calling __.InE().
func InE(args ...interface{}) *dsl.Traversal { return New().InE(args...) }
// InV is the api for calling __.InV().
func InV(args ...interface{}) *dsl.Traversal { return New().InV(args...) }
// V is the api for calling __.V().
func V(args ...interface{}) *dsl.Traversal { return New().V(args...) }
// OutV is the api for calling __.OutV().
func OutV(args ...interface{}) *dsl.Traversal { return New().OutV(args...) }
// Values is the api for calling __.Values().
func Values(args ...string) *dsl.Traversal { return New().Values(args...) }
// OtherV is the api for calling __.OtherV().
func OtherV() *dsl.Traversal { return New().OtherV() }
// Count is the api for calling __.Count().
func Count() *dsl.Traversal { return New().Count() }
// Fold is the api for calling __.Fold().
func Fold() *dsl.Traversal { return New().Fold() }
func New() *dsl.Traversal { return new(dsl.Traversal).Add(dsl.Token("__")) }

View File

@@ -0,0 +1,199 @@
// Package dsl provide an API for writing gremlin dsl queries almost as-is
// in Go without using strings in the code.
//
// Note that, the API is not type-safe and assume the provided query and
// its arguments are valid.
package dsl
import (
"fmt"
"strings"
"time"
)
// Node represents a DSL step in the traversal.
type Node interface {
// Code returns the code representation of the element and its bindings (if any).
Code() (string, []interface{})
}
type (
// Token holds a simple token, like assignment.
Token string
// List represents a list of elements.
List struct {
Elements []interface{}
}
// Func represents a function call.
Func struct {
Name string
Args []interface{}
}
// Block represents a block/group of nodes.
Block struct {
Nodes []interface{}
}
// Var represents a variable assignment and usage.
Var struct {
Name string
Elem interface{}
}
)
// Code stringified the token.
func (t Token) Code() (string, []interface{}) { return string(t), nil }
// Code returns the code representation of a list.
func (l List) Code() (string, []interface{}) {
c, args := codeList(", ", l.Elements...)
return fmt.Sprintf("[%s]", c), args
}
// Code returns the code representation of a function call.
func (f Func) Code() (string, []interface{}) {
c, args := codeList(", ", f.Args...)
return fmt.Sprintf("%s(%s)", f.Name, c), args
}
// Code returns the code representation of group/block of nodes.
func (b Block) Code() (string, []interface{}) {
return codeList("; ", b.Nodes...)
}
// Code returns the code representation of variable declaration or its identifier.
func (v Var) Code() (string, []interface{}) {
c, args := code(v.Elem)
if v.Name == "" {
return c, args
}
return fmt.Sprintf("%s = %s", v.Name, c), args
}
// predefined nodes.
var (
G = Token("g")
Dot = Token(".")
)
// NewFunc returns a new function node.
func NewFunc(name string, args ...interface{}) *Func {
return &Func{Name: name, Args: args}
}
// NewList returns a new list node.
func NewList(args ...interface{}) *List {
return &List{Elements: args}
}
// Querier is the interface that wraps the Query method.
type Querier interface {
// Query returns the query-string (similar to the Gremlin byte-code) and its bindings.
Query() (string, Bindings)
}
// Bindings are used to associate a variable with a value.
type Bindings map[string]interface{}
// Add adds new value to the bindings map, formats it if needed, and returns its generated name.
func (b Bindings) Add(v interface{}) string {
k := fmt.Sprintf("$%x", len(b))
switch v := v.(type) {
case time.Time:
b[k] = v.Unix()
default:
b[k] = v
}
return k
}
// Cardinality of vertex properties.
type Cardinality string
// Cardinality options.
const (
Set Cardinality = "set"
Single Cardinality = "single"
)
// Code implements the Node interface.
func (c Cardinality) Code() (string, []interface{}) { return string(c), nil }
// Order of vertex properties.
type Order string
// Order options.
const (
Incr Order = "incr"
Decr Order = "decr"
Shuffle Order = "shuffle"
)
// Code implements the Node interface.
func (o Order) Code() (string, []interface{}) { return string(o), nil }
// Column references a particular type of column in a complex data structure such as a Map, a Map.Entry, or a Path.
type Column string
// Column options.
const (
Keys Column = "keys"
Values Column = "values"
)
// Code implements the Node interface.
func (o Column) Code() (string, []interface{}) { return string(o), nil }
// Scope used for steps that have a variable scope which alter the manner in which the step will behave in relation to how the traverses are processed.
type Scope string
// Scope options.
const (
Local Scope = "local"
Global Scope = "global"
)
// Code implements the Node interface.
func (s Scope) Code() (string, []interface{}) { return string(s), nil }
func codeList(sep string, vs ...interface{}) (string, []interface{}) {
var (
br strings.Builder
args []interface{}
)
for i, node := range vs {
if i > 0 {
br.WriteString(sep)
}
c, nargs := code(node)
br.WriteString(c)
args = append(args, nargs...)
}
return br.String(), args
}
func code(v interface{}) (string, []interface{}) {
switch n := v.(type) {
case Node:
return n.Code()
case *Traversal:
var (
b strings.Builder
args []interface{}
)
for i := range n.nodes {
code, nargs := n.nodes[i].Code()
b.WriteString(code)
args = append(args, nargs...)
}
return b.String(), args
default:
return "%s", []interface{}{v}
}
}
func sface(args []string) (v []interface{}) {
for _, s := range args {
v = append(v, s)
}
return
}

View File

@@ -0,0 +1,219 @@
package dsl_test
import (
"strconv"
"testing"
"fbc/ent/dialect/gremlin/graph/dsl"
"fbc/ent/dialect/gremlin/graph/dsl/__"
"fbc/ent/dialect/gremlin/graph/dsl/g"
"fbc/ent/dialect/gremlin/graph/dsl/p"
"github.com/stretchr/testify/require"
)
func TestTraverse(t *testing.T) {
tests := []struct {
input dsl.Querier
wantQuery string
wantBinds dsl.Bindings
}{
{
input: g.V(5),
wantQuery: "g.V($0)",
wantBinds: dsl.Bindings{"$0": 5},
},
{
input: g.V(2).Both("knows"),
wantQuery: "g.V($0).both($1)",
wantBinds: dsl.Bindings{"$0": 2, "$1": "knows"},
},
{
input: g.V(49).BothE("knows").OtherV().ValueMap(),
wantQuery: "g.V($0).bothE($1).otherV().valueMap()",
wantBinds: dsl.Bindings{"$0": 49, "$1": "knows"},
},
{
input: g.AddV("person").Property("name", "a8m").Next(),
wantQuery: "g.addV($0).property($1, $2).next()",
wantBinds: dsl.Bindings{"$0": "person", "$1": "name", "$2": "a8m"},
},
{
input: dsl.Each([]interface{}{1, 2, 3}, func(it *dsl.Traversal) *dsl.Traversal {
return g.V(it)
}),
wantQuery: "[$0, $1, $2].each { g.V(it) }",
wantBinds: dsl.Bindings{"$0": 1, "$1": 2, "$2": 3},
},
{
input: dsl.Each([]interface{}{g.V(1).Next()}, func(it *dsl.Traversal) *dsl.Traversal {
return it.ID()
}),
wantQuery: "[g.V($0).next()].each { it.id() }",
wantBinds: dsl.Bindings{"$0": 1},
},
{
input: g.AddV("person").AddE("knows").To(g.V(2)),
wantQuery: "g.addV($0).addE($1).to(g.V($2))",
wantBinds: dsl.Bindings{"$0": "person", "$1": "knows", "$2": 2},
},
{
input: func() *dsl.Traversal {
v1 := g.V(2).Next()
v2 := g.AddV("person").Property("name", "a8m")
e1 := g.V(v1).AddE("knows").To(v2)
return dsl.Group(v1, v2, e1)
}(),
wantQuery: "t0 = g.V($0).next(); t1 = g.addV($1).property($2, $3); t2 = g.V(t0).addE($4).to(t1); t2",
wantBinds: dsl.Bindings{"$0": 2, "$1": "person", "$2": "name", "$3": "a8m", "$4": "knows"},
},
{
input: func() *dsl.Traversal {
v1 := g.AddV("person")
each := dsl.Each([]interface{}{1, 2, 3}, func(it *dsl.Traversal) *dsl.Traversal {
return g.V(v1).AddE("knows").To(g.V(it)).Next()
})
return dsl.Group(v1, each)
}(),
wantQuery: "t0 = g.addV($0); t1 = [$1, $2, $3].each { g.V(t0).addE($4).to(g.V(it)).next() }; t1",
wantBinds: dsl.Bindings{"$0": "person", "$1": 1, "$2": 2, "$3": 3, "$4": "knows"},
},
{
input: g.V().HasLabel("person").
Choose(__.Values("age").Is(p.LTE(20))),
wantQuery: "g.V().hasLabel($0).choose(__.values($1).is(lte($2)))",
wantBinds: dsl.Bindings{"$0": "person", "$1": "age", "$2": 20},
},
{
input: g.AddV("person").Property("name", "a8m").Properties(),
wantQuery: "g.addV($0).property($1, $2).properties()",
wantBinds: dsl.Bindings{"$0": "person", "$1": "name", "$2": "a8m"},
},
{
input: func() *dsl.Traversal {
v1 := g.AddV("person").Next()
e1 := g.V(v1).AddE("knows").To(g.V(2).Next())
return dsl.Group(v1, e1, g.V(v1).ValueMap(true))
}(),
wantQuery: "t0 = g.addV($0).next(); t1 = g.V(t0).addE($1).to(g.V($2).next()); t2 = g.V(t0).valueMap($3); t2",
wantBinds: dsl.Bindings{"$0": "person", "$1": "knows", "$2": 2, "$3": true},
},
{
input: func() *dsl.Traversal {
vs := g.V().HasLabel("person").ToList()
edge := g.V(vs).AddE("assoc").To(g.V(1)).Iterate()
each := dsl.Each(vs, func(it *dsl.Traversal) *dsl.Traversal {
return g.V(1).AddE("inverse").To(it).Next()
})
return dsl.Group(vs, edge, each)
}(),
wantQuery: "t0 = g.V().hasLabel($0).toList(); t1 = g.V(t0).addE($1).to(g.V($2)).iterate(); t2 = t0.each { g.V($3).addE($4).to(it).next() }; t2",
wantBinds: dsl.Bindings{"$0": "person", "$1": "assoc", "$2": 1, "$3": 1, "$4": "inverse"},
},
{
input: g.V().Where(__.Or(__.Has("age", 29), __.Has("age", 30))),
wantQuery: "g.V().where(__.or(__.has($0, $1), __.has($2, $3)))",
wantBinds: dsl.Bindings{"$0": "age", "$1": 29, "$2": "age", "$3": 30},
},
{
input: g.V().Has("name", p.Containing("le")).Has("name", p.StartingWith("A")),
wantQuery: `g.V().has($0, containing($1)).has($2, startingWith($3))`,
wantBinds: dsl.Bindings{"$0": "name", "$1": "le", "$2": "name", "$3": "A"},
},
{
input: g.AddV().Property(dsl.Single, "age", 32).ValueMap(),
wantQuery: "g.addV().property(single, $0, $1).valueMap()",
wantBinds: dsl.Bindings{"$0": "age", "$1": 32},
},
{
input: g.V().Count(),
wantQuery: "g.V().count()",
wantBinds: dsl.Bindings{},
},
{
input: func() *dsl.Traversal {
v := g.V().HasID(1)
u := v.Clone().InE().Drop()
return dsl.Join(v, u)
}(),
wantQuery: "g.V().hasId($0); g.V().hasId($1).inE().drop()",
wantBinds: dsl.Bindings{"$0": 1, "$1": 1},
},
{
input: func() *dsl.Traversal {
v := g.V().HasID(1)
u := v.Clone().InE().Drop()
w := u.Clone()
return dsl.Join(v, u, w)
}(),
wantQuery: "g.V().hasId($0); g.V().hasId($1).inE().drop(); g.V().hasId($2).inE().drop()",
wantBinds: dsl.Bindings{"$0": 1, "$1": 1, "$2": 1},
},
{
input: g.V().OutE("knows").Where(__.InV().Has("name", "a8m")).OutV(),
wantQuery: "g.V().outE($0).where(__.inV().has($1, $2)).outV()",
wantBinds: dsl.Bindings{"$0": "knows", "$1": "name", "$2": "a8m"},
},
{
input: g.V().Has("name", p.Within("a8m", "alex")),
wantQuery: "g.V().has($0, within($1, $2))",
wantBinds: dsl.Bindings{"$0": "name", "$1": "a8m", "$2": "alex"},
},
{
input: g.V().HasID(p.Within(1, 2)),
wantQuery: "g.V().hasId(within($0, $1))",
wantBinds: dsl.Bindings{"$0": 1, "$1": 2},
},
{
input: g.V().HasID(p.Without(1, 2)),
wantQuery: "g.V().hasId(without($0, $1))",
wantBinds: dsl.Bindings{"$0": 1, "$1": 2},
},
{
input: g.V().Order().By("name"),
wantQuery: "g.V().order().by($0)",
wantBinds: dsl.Bindings{"$0": "name"},
},
{
input: g.V().Order().By("name", dsl.Incr),
wantQuery: "g.V().order().by($0, incr)",
wantBinds: dsl.Bindings{"$0": "name"},
},
{
input: g.V().Order().By("name", dsl.Incr).Undo(),
wantQuery: "g.V().order()",
wantBinds: dsl.Bindings{},
},
{
input: g.V().OutE("knows").Where(__.InV().Has("name", "a8m")).Undo(),
wantQuery: "g.V().outE($0)",
wantBinds: dsl.Bindings{"$0": "knows"},
},
{
input: g.V().Has("name").Group().By("name").By("age").Select(dsl.Values),
wantQuery: "g.V().has($0).group().by($1).by($2).select(values)",
wantBinds: dsl.Bindings{"$0": "name", "$1": "name", "$2": "age"},
},
{
input: g.V().Fold().Unfold(),
wantQuery: "g.V().fold().unfold()",
wantBinds: dsl.Bindings{},
},
{
input: g.V().Has("person", "name", "a8m").Count().Coalesce(
__.Is(p.NEQ(0)).Constant("unique constraint failed"),
g.AddV("person").Property("name", "a8m").ValueMap(true),
),
wantQuery: "g.V().has($0, $1, $2).count().coalesce(__.is(neq($3)).constant($4), g.addV($5).property($6, $7).valueMap($8))",
wantBinds: dsl.Bindings{"$0": "person", "$1": "name", "$2": "a8m", "$3": 0, "$4": "unique constraint failed", "$5": "person", "$6": "name", "$7": "a8m", "$8": true},
},
}
for i, tt := range tests {
tt := tt
t.Run(strconv.Itoa(i), func(t *testing.T) {
query, bindings := tt.input.Query()
require.Equal(t, tt.wantQuery, query)
require.Equal(t, tt.wantBinds, bindings)
})
}
}

View File

@@ -0,0 +1,15 @@
package g
import "fbc/ent/dialect/gremlin/graph/dsl"
// V is the api for calling g.V().
func V(args ...interface{}) *dsl.Traversal { return dsl.NewTraversal().V(args...) }
// E is the api for calling g.E().
func E(args ...interface{}) *dsl.Traversal { return dsl.NewTraversal().E(args...) }
// AddV is the api for calling g.AddV().
func AddV(args ...interface{}) *dsl.Traversal { return dsl.NewTraversal().AddV(args...) }
// AddE is the api for calling g.AddE().
func AddE(args ...interface{}) *dsl.Traversal { return dsl.NewTraversal().AddE(args...) }

View File

@@ -0,0 +1,85 @@
package p
import (
"fbc/ent/dialect/gremlin/graph/dsl"
)
// EQ is the equal predicate.
func EQ(v interface{}) *dsl.Traversal {
return op("eq", v)
}
// NEQ is the not-equal predicate.
func NEQ(v interface{}) *dsl.Traversal {
return op("neq", v)
}
// GT is the greater than predicate.
func GT(v interface{}) *dsl.Traversal {
return op("gt", v)
}
// GTE is the greater than or equal predicate.
func GTE(v interface{}) *dsl.Traversal {
return op("gte", v)
}
// LT is the less than predicate.
func LT(v interface{}) *dsl.Traversal {
return op("lt", v)
}
// LTE is the less than or equal predicate.
func LTE(v interface{}) *dsl.Traversal {
return op("lte", v)
}
// Between is the between/contains predicate.
func Between(v, u interface{}) *dsl.Traversal {
return op("between", v, u)
}
// StartingWith is the prefix test predicate.
func StartingWith(prefix string) *dsl.Traversal {
return op("startingWith", prefix)
}
// EndingWith is the suffix test predicate.
func EndingWith(suffix string) *dsl.Traversal {
return op("endingWith", suffix)
}
// Containing is the sub string test predicate.
func Containing(substr string) *dsl.Traversal {
return op("containing", substr)
}
// NotStartingWith is the negation of StartingWith.
func NotStartingWith(prefix string) *dsl.Traversal {
return op("notStartingWith", prefix)
}
// NotEndingWith is the negation of EndingWith.
func NotEndingWith(suffix string) *dsl.Traversal {
return op("notEndingWith", suffix)
}
// NotContaining is the negation of Containing.
func NotContaining(substr string) *dsl.Traversal {
return op("notContaining", substr)
}
// Within Determines if a value is within the specified list of values.
func Within(args ...interface{}) *dsl.Traversal {
return op("within", args...)
}
// Without determines if a value is not within the specified list of values.
func Without(args ...interface{}) *dsl.Traversal {
return op("without", args...)
}
func op(name string, args ...interface{}) *dsl.Traversal {
t := &dsl.Traversal{}
return t.Add(dsl.NewFunc(name, args...))
}

View File

@@ -0,0 +1,381 @@
package dsl
import (
"fmt"
"strings"
)
// Traversal mimics the TinkerPop graph traversal.
type Traversal struct {
// nodes holds the dsl nodes. first element is the reference name
// of the TinkerGraph. defaults to "g".
nodes []Node
}
// NewTraversal returns a new default traversal with "g" as a reference name to the Graph.
func NewTraversal() *Traversal {
return &Traversal{[]Node{G}}
}
// Group groups a list of traversals into one. all traversals are assigned into a temporary
// variables named by their index. The last variable functions as a return value of the query.
// Note that, this "temporary hack" is not perfect and may not work in some cases because of
// the limitation of evaluation order.
func Group(trs ...*Traversal) *Traversal {
var (
b = Block{}
names = make(map[*Traversal]Token)
)
for i, tr := range trs {
if _, ok := names[tr]; ok {
continue
}
v := &Var{Name: fmt.Sprintf("t%d", i), Elem: &Traversal{nodes: tr.nodes}}
b.Nodes = append(b.Nodes, v)
names[tr] = Token(v.Name)
}
for _, tr := range trs {
tr.nodes = []Node{names[tr]}
}
b.Nodes = append(b.Nodes, names[trs[len(trs)-1]])
return &Traversal{[]Node{b}}
}
// Join joins a list of traversals with a semicolon separator.
func Join(trs ...*Traversal) *Traversal {
b := Block{}
for _, tr := range trs {
b.Nodes = append(b.Nodes, &Traversal{nodes: tr.nodes})
}
return &Traversal{[]Node{b}}
}
// V step is usually used to start a traversal but it may also be used mid-traversal.
func (t *Traversal) V(args ...interface{}) *Traversal {
t.Add(Dot, NewFunc("V", args...))
return t
}
// OtherV maps the Edge to the incident vertex that was not just traversed from in the path history.
func (t *Traversal) OtherV() *Traversal {
t.Add(Dot, NewFunc("otherV"))
return t
}
// E step is usually used to start a traversal but it may also be used mid-traversal.
func (t *Traversal) E(args ...interface{}) *Traversal {
t.Add(Dot, NewFunc("E", args...))
return t
}
// AddV adds a vertex.
func (t *Traversal) AddV(args ...interface{}) *Traversal {
t.Add(Dot, NewFunc("addV", args...))
return t
}
// AddE adds an edge.
func (t *Traversal) AddE(args ...interface{}) *Traversal {
t.Add(Dot, NewFunc("addE", args...))
return t
}
// Next gets the next n-number of results from the traversal.
func (t *Traversal) Next() *Traversal {
return t.Add(Dot, NewFunc("next"))
}
// Drop removes elements and properties from the graph.
func (t *Traversal) Drop() *Traversal {
return t.Add(Dot, NewFunc("drop"))
}
// Property sets a Property value and related meta properties if supplied,
// if supported by the Graph and if the Element is a VertexProperty.
func (t *Traversal) Property(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("property", args...))
}
// Both maps the Vertex to its adjacent vertices given the edge labels.
func (t *Traversal) Both(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("both", args...))
}
// BothE maps the Vertex to its incident edges given the edge labels.
func (t *Traversal) BothE(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("bothE", args...))
}
// Has filters vertices, edges and vertex properties based on their properties.
// See: http://tinkerpop.apache.org/docs/current/reference/#has-step.
func (t *Traversal) Has(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("has", args...))
}
// HasID filters vertices, edges and vertex properties based on their identifier.
func (t *Traversal) HasID(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("hasId", args...))
}
// HasLabel filters vertices, edges and vertex properties based on their label.
func (t *Traversal) HasLabel(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("hasLabel", args...))
}
// HasNext returns true if the iteration has more elements.
func (t *Traversal) HasNext() *Traversal {
return t.Add(Dot, NewFunc("hasNext"))
}
// Match maps the Traverser to a Map of bindings as specified by the provided match traversals.
func (t *Traversal) Match(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("match", args...))
}
// Choose routes the current traverser to a particular traversal branch option which allows the creation of if-then-else like semantics within a traversal.
func (t *Traversal) Choose(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("choose", args...))
}
// Select arbitrary values from the traversal.
func (t *Traversal) Select(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("select", args...))
}
// Group organizes objects in the stream into a Map.Calls to group() are typically accompanied with by() modulators which help specify how the grouping should occur.
func (t *Traversal) Group() *Traversal {
return t.Add(Dot, NewFunc("group"))
}
// Values maps the Element to the values of the associated properties given the provide property keys.
func (t *Traversal) Values(args ...string) *Traversal {
return t.Add(Dot, NewFunc("values", sface(args)...))
}
// ValueMap maps the Element to a Map of the property values key'd according to their Property.key().
func (t *Traversal) ValueMap(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("valueMap", args...))
}
// Properties maps the Element to its associated properties given the provide property keys.
func (t *Traversal) Properties(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("properties", args...))
}
// Range filters the objects in the traversal by the number of them to pass through the stream.
func (t *Traversal) Range(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("range", args...))
}
// Limit filters the objects in the traversal by the number of them to pass through the stream, where only the first n objects are allowed as defined by the limit argument.
func (t *Traversal) Limit(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("limit", args...))
}
// ID maps the Element to its Element.id().
func (t *Traversal) ID() *Traversal {
return t.Add(Dot, NewFunc("id"))
}
// Label maps the Element to its Element.label().
func (t *Traversal) Label() *Traversal {
return t.Add(Dot, NewFunc("label"))
}
// From provides from()-modulation to respective steps.
func (t *Traversal) From(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("from", args...))
}
// To used as a modifier to addE(String) this method specifies the traversal to use for selecting the incoming vertex of the newly added Edge.
func (t *Traversal) To(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("to", args...))
}
// As provides a label to the step that can be accessed later in the traversal by other steps.
func (t *Traversal) As(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("as", args...))
}
// Or ensures that at least one of the provided traversals yield a result.
func (t *Traversal) Or(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("or", args...))
}
// And ensures that all of the provided traversals yield a result.
func (t *Traversal) And(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("and", args...))
}
// Is filters the E object if it is not P.eq(V) to the provided value.
func (t *Traversal) Is(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("is", args...))
}
// Not removes objects from the traversal stream when the traversal provided as an argument does not return any objects.
func (t *Traversal) Not(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("not", args...))
}
// In maps the Vertex to its incoming adjacent vertices given the edge labels.
func (t *Traversal) In(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("in", args...))
}
// Where filters the current object based on the object itself or the path history.
func (t *Traversal) Where(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("where", args...))
}
// Out maps the Vertex to its outgoing adjacent vertices given the edge labels.
func (t *Traversal) Out(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("out", args...))
}
// OutE maps the Vertex to its outgoing incident edges given the edge labels.
func (t *Traversal) OutE(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("outE", args...))
}
// InE maps the Vertex to its incoming incident edges given the edge labels.
func (t *Traversal) InE(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("inE", args...))
}
// OutV maps the Edge to its outgoing/tail incident Vertex.
func (t *Traversal) OutV(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("outV", args...))
}
// InV maps the Edge to its incoming/head incident Vertex.
func (t *Traversal) InV(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("inV", args...))
}
// ToList puts all the results into a Groovy list.
func (t *Traversal) ToList() *Traversal {
return t.Add(Dot, NewFunc("toList"))
}
// Iterate iterates the traversal presumably for the generation of side-effects.
func (t *Traversal) Iterate() *Traversal {
return t.Add(Dot, NewFunc("iterate"))
}
// Count maps the traversal stream to its reduction as a sum of the Traverser.bulk() values
// (i.e. count the number of traversers up to this point).
func (t *Traversal) Count(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("count", args...))
}
// Order all the objects in the traversal up to this point and then emit them one-by-one in their ordered sequence.
func (t *Traversal) Order(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("order", args...))
}
// By can be applied to a number of different step to alter their behaviors.
// This form is essentially an identity() modulation.
func (t *Traversal) By(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("by", args...))
}
// Fold rolls up objects in the stream into an aggregate list..
func (t *Traversal) Fold() *Traversal {
return t.Add(Dot, NewFunc("fold"))
}
// Unfold unrolls a Iterator, Iterable or Map into a linear form or simply emits the object if it is not one of those types.
func (t *Traversal) Unfold() *Traversal {
return t.Add(Dot, NewFunc("unfold"))
}
// Sum maps the traversal stream to its reduction as a sum of the Traverser.get() values multiplied by their Traverser.bulk().
func (t *Traversal) Sum(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("sum", args...))
}
// Mean determines the mean value in the stream.
func (t *Traversal) Mean(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("mean", args...))
}
// Min determines the smallest value in the stream.
func (t *Traversal) Min(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("min", args...))
}
// Max determines the greatest value in the stream.
func (t *Traversal) Max(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("max", args...))
}
// Coalesce evaluates the provided traversals and returns the result of the first traversal to emit at least one object.
func (t *Traversal) Coalesce(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("coalesce", args...))
}
// Dedup removes all duplicates in the traversal stream up to this point.
func (t *Traversal) Dedup(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("dedup", args...))
}
// Constant maps any object to a fixed E value.
func (t *Traversal) Constant(args ...interface{}) *Traversal {
return t.Add(Dot, NewFunc("constant", args...))
}
// Each is a Groovy each-loop function.
func Each(v interface{}, cb func(it *Traversal) *Traversal) *Traversal {
t := &Traversal{}
switch v := v.(type) {
case *Traversal:
t.Add(&Var{Elem: v})
case []interface{}:
t.Add(NewList(v...))
default:
t.Add(Token("undefined"))
}
t.Add(Dot, Token("each"), Token(" { "))
t.Add(cb(&Traversal{[]Node{Token("it")}}).nodes...)
t.Add(Token(" }"))
return t
}
// Add is the public API for adding new nodes to the traversal by its sub packages.
func (t *Traversal) Add(n ...Node) *Traversal {
t.nodes = append(t.nodes, n...)
return t
}
// Query returns the query-representation and its binding of this traversal object.
func (t *Traversal) Query() (string, Bindings) {
var (
names []interface{}
query strings.Builder
bindings = Bindings{}
)
for _, n := range t.nodes {
code, args := n.Code()
query.WriteString(code)
for _, arg := range args {
names = append(names, bindings.Add(arg))
}
}
return fmt.Sprintf(query.String(), names...), bindings
}
// Clone creates a deep copy of an existing traversal.
func (t *Traversal) Clone() *Traversal {
if t == nil {
return nil
}
return &Traversal{append(make([]Node, 0, len(t.nodes)), t.nodes...)}
}
// Undo reverts the last-step of the traversal.
func (t *Traversal) Undo() *Traversal {
if n := len(t.nodes); n > 2 {
t.nodes = t.nodes[:n-2]
}
return t
}

View File

@@ -0,0 +1,92 @@
package graph
import (
"fmt"
"fbc/ent/dialect/gremlin/encoding/graphson"
"github.com/pkg/errors"
)
type (
// An Edge between two vertices.
Edge struct {
Element
OutV, InV Vertex
}
// graphson edge repr.
edge struct {
Element
OutV interface{} `json:"outV"`
OutVLabel string `json:"outVLabel"`
InV interface{} `json:"inV"`
InVLabel string `json:"inVLabel"`
}
)
// NewEdge create a new graph edge.
func NewEdge(id interface{}, label string, outV, inV Vertex) Edge {
return Edge{
Element: NewElement(id, label),
OutV: outV,
InV: inV,
}
}
// String implements fmt.Stringer interface.
func (e Edge) String() string {
return fmt.Sprintf("e[%v][%v-%s->%v]", e.ID, e.OutV.ID, e.Label, e.InV.ID)
}
// MarshalGraphson implements graphson.Marshaler interface.
func (e Edge) MarshalGraphson() ([]byte, error) {
return graphson.Marshal(edge{
Element: e.Element,
OutV: e.OutV.ID,
OutVLabel: e.OutV.Label,
InV: e.InV.ID,
InVLabel: e.InV.Label,
})
}
// UnmarshalGraphson implements graphson.Unmarshaler interface.
func (e *Edge) UnmarshalGraphson(data []byte) error {
var edge edge
if err := graphson.Unmarshal(data, &edge); err != nil {
return errors.Wrap(err, "unmarshaling edge")
}
*e = NewEdge(
edge.ID, edge.Label,
NewVertex(edge.OutV, edge.OutVLabel),
NewVertex(edge.InV, edge.InVLabel),
)
return nil
}
// GraphsonType implements graphson.Typer interface.
func (edge) GraphsonType() graphson.Type {
return "g:Edge"
}
// Property denotes a key/value pair associated with an edge.
type Property struct {
Key string `json:"key"`
Value interface{} `json:"value"`
}
// NewProperty create a new graph edge property.
func NewProperty(key string, value interface{}) Property {
return Property{key, value}
}
// GraphsonType implements graphson.Typer interface.
func (Property) GraphsonType() graphson.Type {
return "g:Property"
}
// String implements fmt.Stringer interface.
func (p Property) String() string {
return fmt.Sprintf("p[%s->%v]", p.Key, p.Value)
}

View File

@@ -0,0 +1,104 @@
package graph
import (
"fmt"
"testing"
"fbc/ent/dialect/gremlin/encoding/graphson"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestEdgeString(t *testing.T) {
e := NewEdge(
13, "develops",
NewVertex(1, ""),
NewVertex(10, ""),
)
assert.Equal(t, "e[13][1-develops->10]", fmt.Sprint(e))
}
func TestEdgeEncoding(t *testing.T) {
t.Parallel()
e := NewEdge(13, "develops",
NewVertex(1, "person"),
NewVertex(10, "software"),
)
got, err := graphson.MarshalToString(e)
require.NoError(t, err)
want := `{
"@type" : "g:Edge",
"@value" : {
"id" : {
"@type" : "g:Int64",
"@value" : 13
},
"label" : "develops",
"inVLabel" : "software",
"outVLabel" : "person",
"inV" : {
"@type" : "g:Int64",
"@value" : 10
},
"outV" : {
"@type" : "g:Int64",
"@value" : 1
}
}
}`
assert.JSONEq(t, want, got)
e = Edge{}
err = graphson.UnmarshalFromString(got, &e)
require.NoError(t, err)
assert.Equal(t, NewElement(int64(13), "develops"), e.Element)
assert.Equal(t, NewVertex(int64(1), "person"), e.OutV)
assert.Equal(t, NewVertex(int64(10), "software"), e.InV)
}
func TestPropertyEncoding(t *testing.T) {
t.Parallel()
props := []Property{
NewProperty("from", int32(2017)),
NewProperty("to", int32(2019)),
}
got, err := graphson.MarshalToString(props)
require.NoError(t, err)
want := `{
"@type" : "g:List",
"@value" : [
{
"@type" : "g:Property",
"@value" : {
"key" : "from",
"value" : {
"@type" : "g:Int32",
"@value" : 2017
}
}
},
{
"@type" : "g:Property",
"@value" : {
"key" : "to",
"value" : {
"@type" : "g:Int32",
"@value" : 2019
}
}
}
]
}`
assert.JSONEq(t, want, got)
}
func TestPropertyString(t *testing.T) {
p := NewProperty("since", 2019)
assert.Equal(t, "p[since->2019]", fmt.Sprint(p))
}

View File

@@ -0,0 +1,12 @@
package graph
// Element defines a base struct for graph elements.
type Element struct {
ID interface{} `json:"id"`
Label string `json:"label"`
}
// NewElement create a new graph element.
func NewElement(id interface{}, label string) Element {
return Element{id, label}
}

View File

@@ -0,0 +1,52 @@
package graph
import (
"reflect"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
)
// ValueMap models a .valueMap() gremlin response.
type ValueMap []map[string]interface{}
// Decode decodes a value map into v.
func (m ValueMap) Decode(v interface{}) error {
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr {
return errors.New("cannot unmarshal into a non pointer")
}
if rv.IsNil() {
return errors.New("cannot unmarshal into a nil pointer")
}
if rv.Elem().Kind() != reflect.Slice {
v = &[]interface{}{v}
}
return m.decode(v)
}
func (m ValueMap) decode(v interface{}) error {
cfg := mapstructure.DecoderConfig{
DecodeHook: func(f, t reflect.Kind, data interface{}) (interface{}, error) {
if f == reflect.Slice && t != reflect.Slice {
rv := reflect.ValueOf(data)
if rv.Len() == 1 {
data = rv.Index(0).Interface()
}
}
return data, nil
},
Result: v,
TagName: "json",
}
dec, err := mapstructure.NewDecoder(&cfg)
if err != nil {
return errors.Wrap(err, "creating structure decoder")
}
if err := dec.Decode(m); err != nil {
return errors.Wrap(err, "decoding value map")
}
return nil
}

View File

@@ -0,0 +1,70 @@
package graph
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestValueMapDecodeOne(t *testing.T) {
vm := ValueMap{map[string]interface{}{
"id": int64(1),
"label": "person",
"name": []interface{}{"marko"},
"age": []interface{}{int32(29)},
}}
var ent struct {
ID uint64 `json:"id"`
Label string `json:"label"`
Name string `json:"name"`
Age uint8 `json:"age"`
}
err := vm.Decode(&ent)
require.NoError(t, err)
assert.Equal(t, uint64(1), ent.ID)
assert.Equal(t, "person", ent.Label)
assert.Equal(t, "marko", ent.Name)
assert.Equal(t, uint8(29), ent.Age)
}
func TestValueMapDecodeMany(t *testing.T) {
vm := ValueMap{
map[string]interface{}{
"id": int64(1),
"label": "person",
"name": []interface{}{"chico"},
},
map[string]interface{}{
"id": int64(2),
"label": "person",
"name": []interface{}{"dico"},
},
}
ents := []struct {
ID int `json:"id"`
Label string `json:"label"`
Name string `json:"name"`
}{}
err := vm.Decode(&ents)
require.NoError(t, err)
require.Len(t, ents, 2)
assert.Equal(t, 1, ents[0].ID)
assert.Equal(t, "person", ents[0].Label)
assert.Equal(t, "chico", ents[0].Name)
assert.Equal(t, 2, ents[1].ID)
assert.Equal(t, "person", ents[1].Label)
assert.Equal(t, "dico", ents[1].Name)
}
func TestValueMapDecodeBadInput(t *testing.T) {
type s struct{ Name string }
err := ValueMap{}.Decode(s{})
assert.Error(t, err)
err = ValueMap{}.Decode((*s)(nil))
assert.Error(t, err)
}

View File

@@ -0,0 +1,58 @@
package graph
import (
"fmt"
"fbc/ent/dialect/gremlin/encoding/graphson"
)
// Vertex represents a graph vertex.
type Vertex struct {
Element
}
// NewVertex create a new graph vertex.
func NewVertex(id interface{}, label string) Vertex {
if label == "" {
label = "vertex"
}
return Vertex{
Element: NewElement(id, label),
}
}
// GraphsonType implements graphson.Typer interface.
func (Vertex) GraphsonType() graphson.Type {
return "g:Vertex"
}
// String implements fmt.Stringer interface.
func (v Vertex) String() string {
return fmt.Sprintf("v[%v]", v.ID)
}
// VertexProperty denotes a key/value pair associated with a vertex.
type VertexProperty struct {
ID interface{} `json:"id"`
Key string `json:"label"`
Value interface{} `json:"value"`
}
// NewVertexProperty create a new graph vertex property.
func NewVertexProperty(id interface{}, key string, value interface{}) VertexProperty {
return VertexProperty{
ID: id,
Key: key,
Value: value,
}
}
// GraphsonType implements graphson.Typer interface.
func (VertexProperty) GraphsonType() graphson.Type {
return "g:VertexProperty"
}
// String implements fmt.Stringer interface.
func (vp VertexProperty) String() string {
return fmt.Sprintf("vp[%s->%v]", vp.Key, vp.Value)
}

View File

@@ -0,0 +1,82 @@
package graph
import (
"fmt"
"testing"
"fbc/ent/dialect/gremlin/encoding/graphson"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestVertexCreation(t *testing.T) {
v := NewVertex(45, "person")
assert.Equal(t, 45, v.ID)
assert.Equal(t, "person", v.Label)
v = NewVertex(46, "")
assert.Equal(t, "vertex", v.Label)
}
func TestVertexString(t *testing.T) {
v := NewVertex(42, "")
assert.Equal(t, "v[42]", fmt.Sprint(v))
}
func TestVertexEncoding(t *testing.T) {
t.Parallel()
v := NewVertex(1, "user")
got, err := graphson.MarshalToString(v)
require.NoError(t, err)
want := `{
"@type" : "g:Vertex",
"@value" : {
"id" : {
"@type" : "g:Int64",
"@value" : 1
},
"label" : "user"
}
}`
assert.JSONEq(t, want, got)
v = Vertex{}
err = graphson.UnmarshalFromString(got, &v)
require.NoError(t, err)
assert.Equal(t, int64(1), v.ID)
assert.Equal(t, "user", v.Label)
}
func TestVertexPropertyEncoding(t *testing.T) {
t.Parallel()
vp := NewVertexProperty("46ab60c2-918c-4cc4-a13b-350510e8908a", "name", "alex")
got, err := graphson.MarshalToString(vp)
require.NoError(t, err)
want := `{
"@type" : "g:VertexProperty",
"@value" : {
"id" : "46ab60c2-918c-4cc4-a13b-350510e8908a",
"label": "name",
"value": "alex"
}
}`
assert.JSONEq(t, want, got)
vp = VertexProperty{}
err = graphson.UnmarshalFromString(got, &vp)
require.NoError(t, err)
assert.Equal(t, "46ab60c2-918c-4cc4-a13b-350510e8908a", vp.ID)
assert.Equal(t, "name", vp.Key)
assert.Equal(t, "alex", vp.Value)
}
func TestVertexPropertyString(t *testing.T) {
vp := NewVertexProperty(55, "country", "israel")
assert.Equal(t, "vp[country->israel]", fmt.Sprint(vp))
}