mirror of
https://github.com/ent/ent.git
synced 2026-05-28 09:49:08 +03:00
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:
committed by
Facebook Github Bot
parent
8b2447b8eb
commit
1e47de5300
53
dialect/gremlin/graph/dsl/__/dsl.go
Normal file
53
dialect/gremlin/graph/dsl/__/dsl.go
Normal 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("__")) }
|
||||
199
dialect/gremlin/graph/dsl/dsl.go
Normal file
199
dialect/gremlin/graph/dsl/dsl.go
Normal 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
|
||||
}
|
||||
219
dialect/gremlin/graph/dsl/dsl_test.go
Normal file
219
dialect/gremlin/graph/dsl/dsl_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
15
dialect/gremlin/graph/dsl/g/g.go
Normal file
15
dialect/gremlin/graph/dsl/g/g.go
Normal 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...) }
|
||||
85
dialect/gremlin/graph/dsl/p/p.go
Normal file
85
dialect/gremlin/graph/dsl/p/p.go
Normal 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...))
|
||||
}
|
||||
381
dialect/gremlin/graph/dsl/traversal.go
Normal file
381
dialect/gremlin/graph/dsl/traversal.go
Normal 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
|
||||
}
|
||||
92
dialect/gremlin/graph/edge.go
Normal file
92
dialect/gremlin/graph/edge.go
Normal 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)
|
||||
}
|
||||
104
dialect/gremlin/graph/edge_test.go
Normal file
104
dialect/gremlin/graph/edge_test.go
Normal 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))
|
||||
}
|
||||
12
dialect/gremlin/graph/element.go
Normal file
12
dialect/gremlin/graph/element.go
Normal 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}
|
||||
}
|
||||
52
dialect/gremlin/graph/valuemap.go
Normal file
52
dialect/gremlin/graph/valuemap.go
Normal 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
|
||||
}
|
||||
70
dialect/gremlin/graph/valuemap_test.go
Normal file
70
dialect/gremlin/graph/valuemap_test.go
Normal 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)
|
||||
}
|
||||
58
dialect/gremlin/graph/vertex.go
Normal file
58
dialect/gremlin/graph/vertex.go
Normal 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)
|
||||
}
|
||||
82
dialect/gremlin/graph/vertex_test.go
Normal file
82
dialect/gremlin/graph/vertex_test.go
Normal 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))
|
||||
}
|
||||
Reference in New Issue
Block a user