This is an automated email from the ASF dual-hosted git repository. xiazcy pushed a commit to branch go-opts-strat-gl-updates in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 97a6b9d663d127d5336ef75732ab164f70be835e Author: Yang Xia <[email protected]> AuthorDate: Thu Feb 20 10:13:22 2025 -0800 Updated datetime, added GValue, updated Options strategy, removed unneeded strategy package name, added NewTraversalStrategy to allow custom strategies. --- gremlin-go/driver/connection_test.go | 2 +- gremlin-go/driver/gValue.go | 61 ++++++++++++++++++ gremlin-go/driver/gValue_test.go | 87 ++++++++++++++++++++++++++ gremlin-go/driver/graphTraversalSource.go | 7 ++- gremlin-go/driver/graphTraversalSource_test.go | 31 +++------ gremlin-go/driver/gremlinlang.go | 25 +++++++- gremlin-go/driver/gremlinlang_test.go | 26 ++++---- gremlin-go/driver/request.go | 2 +- gremlin-go/driver/strategies.go | 70 ++++++++++----------- gremlin-go/driver/strategies_test.go | 34 ++++++---- gremlin-go/driver/traversal_test.go | 4 +- 11 files changed, 258 insertions(+), 91 deletions(-) diff --git a/gremlin-go/driver/connection_test.go b/gremlin-go/driver/connection_test.go index 6bcb2e75dd..fc7497b767 100644 --- a/gremlin-go/driver/connection_test.go +++ b/gremlin-go/driver/connection_test.go @@ -606,7 +606,7 @@ func TestConnection(t *testing.T) { assert.True(t, ok) assert.NotNil(t, result) - g := cloneGraphTraversalSource(&Graph{}, NewBytecode(nil), NewGremlinLang(nil), nil) + g := cloneGraphTraversalSource(&Graph{}, NewGremlinLang(nil), nil) b := g.V().Count().Bytecode resultSet, err = client.submitBytecode(b) assert.Nil(t, err) diff --git a/gremlin-go/driver/gValue.go b/gremlin-go/driver/gValue.go new file mode 100644 index 0000000000..a1e5028129 --- /dev/null +++ b/gremlin-go/driver/gValue.go @@ -0,0 +1,61 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +*/ + +package gremlingo + +import ( + "fmt" + "strings" +) + +// GValue is a variable or literal value that is used in a Traversal. It is composed of a key-value pair where the key +// is the name given to the variable and the value is the object that the variable resolved to. +type GValue interface { + Name() string + IsNil() bool + Value() interface{} +} + +type gValue struct { + name string + value interface{} +} + +// NewGValue creates a new GValue to be used in traversals. The GValue name cannot begin with "_". +func NewGValue(name string, value interface{}) GValue { + if strings.HasPrefix(name, "_") { + panic(fmt.Sprintf("invalid GValue name '%v'. Should not start with _.", name)) + } + return &gValue{name, value} +} + +// Name returns the name of the GValue. +func (gv *gValue) Name() string { + return gv.name +} + +// IsNil determines if the value held is of a nil value. +func (gv *gValue) IsNil() bool { + return gv.value == nil +} + +// Value returns the value held by the GValue. +func (gv *gValue) Value() interface{} { + return gv.value +} diff --git a/gremlin-go/driver/gValue_test.go b/gremlin-go/driver/gValue_test.go new file mode 100644 index 0000000000..cd4235278d --- /dev/null +++ b/gremlin-go/driver/gValue_test.go @@ -0,0 +1,87 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +*/ + +package gremlingo + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestGValue(t *testing.T) { + + t.Run("test simple gValue", func(t *testing.T) { + gVal := NewGValue("intVal", 2) + assert.Equal(t, "intVal", gVal.Name()) + assert.Equal(t, 2, gVal.Value()) + assert.False(t, gVal.IsNil()) + }) + + t.Run("test gValue allow parameter reuse with arrays", func(t *testing.T) { + g := NewGraphTraversalSource(nil, nil) + val := [3]int{1, 2, 3} + param := NewGValue("ids", val) + gl := g.Inject(param).V(param).GremlinLang + assert.Equal(t, "g.inject(ids).V(ids)", gl.GetGremlin()) + assert.Equal(t, val, gl.parameters["ids"]) + }) + + t.Run("test gValue allow parameter reuse with slices", func(t *testing.T) { + g := NewGraphTraversalSource(nil, nil) + val := []int{1, 2, 3} + param := NewGValue("ids", val) + gl := g.Inject(param).V(param).GremlinLang + assert.Equal(t, "g.inject(ids).V(ids)", gl.GetGremlin()) + assert.Equal(t, val, gl.parameters["ids"]) + }) + + t.Run("test gValue allow parameter reuse with maps", func(t *testing.T) { + g := NewGraphTraversalSource(nil, nil) + val := map[string]int{"foo": 1, "bar": 2} + param := NewGValue("ids", val) + gl := g.Inject(param).V(param).GremlinLang + assert.Equal(t, "g.inject(ids).V(ids)", gl.GetGremlin()) + assert.Equal(t, val, gl.parameters["ids"]) + }) + + t.Run("test gValue name not duplicated", func(t *testing.T) { + g := NewGraphTraversalSource(nil, nil) + param1 := NewGValue("ids", [2]int{1, 2}) + param2 := NewGValue("ids", [2]int{2, 3}) + assert.Panics(t, func() { g.Inject(param1).V(param2) }, "parameter with name ids already exists.") + }) + + t.Run("test invalid name that starts with _", func(t *testing.T) { + g := NewGraphTraversalSource(nil, nil) + assert.Panics(t, func() { g.Inject(NewGValue("_ids", [2]int{1, 2})) }, + "invalid GValue name _1. Should not start with _.") + }) + + t.Run("test name is valid identifier", func(t *testing.T) { + g := NewGraphTraversalSource(nil, nil) + assert.Panics(t, func() { g.Inject(NewGValue("1a", [2]int{1, 2})) }, + "invalid parameter name '1a'") + }) + + t.Run("test name is not a number", func(t *testing.T) { + g := NewGraphTraversalSource(nil, nil) + assert.Panics(t, func() { g.Inject(NewGValue("1", [2]int{1, 2})) }, + "invalid parameter name '1'") + }) +} diff --git a/gremlin-go/driver/graphTraversalSource.go b/gremlin-go/driver/graphTraversalSource.go index a93e7768ed..0035de1664 100644 --- a/gremlin-go/driver/graphTraversalSource.go +++ b/gremlin-go/driver/graphTraversalSource.go @@ -120,8 +120,11 @@ func (gts *GraphTraversalSource) WithoutStrategies(args ...TraversalStrategy) *G func (gts *GraphTraversalSource) With(key interface{}, value interface{}) *GraphTraversalSource { source := gts.clone() - //TODO verify - var optionsStrategy TraversalStrategy = gts.gremlinLang.optionsStrategies[0] + //TODO verify remote when connection is set-up + var optionsStrategy TraversalStrategy = nil + if len(gts.gremlinLang.optionsStrategies) != 0 { + optionsStrategy = gts.gremlinLang.optionsStrategies[0] + } if optionsStrategy == nil { optionsStrategy = OptionsStrategy(map[string]interface{}{key.(string): value}) diff --git a/gremlin-go/driver/graphTraversalSource_test.go b/gremlin-go/driver/graphTraversalSource_test.go index 4c31423107..7a842e5e4b 100644 --- a/gremlin-go/driver/graphTraversalSource_test.go +++ b/gremlin-go/driver/graphTraversalSource_test.go @@ -26,44 +26,31 @@ import ( func TestGraphTraversalSource(t *testing.T) { - // TODO update once option strategy application is property updated t.Run("GraphTraversalSource.With tests", func(t *testing.T) { t.Run("Test for single property", func(t *testing.T) { - g := &GraphTraversalSource{graph: &Graph{}, bytecode: NewBytecode(nil), remoteConnection: nil} + g := &GraphTraversalSource{graph: &Graph{}, gremlinLang: NewGremlinLang(nil), remoteConnection: nil} traversal := g.With("foo", "bar") assert.NotNil(t, traversal) - assert.Equal(t, 1, len(traversal.bytecode.sourceInstructions)) - instruction := traversal.bytecode.sourceInstructions[0] - assert.Equal(t, "withStrategies", instruction.operator) - assert.Equal(t, "org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.OptionsStrategy", - instruction.arguments[0].(*traversalStrategy).name) - config := instruction.arguments[0].(*traversalStrategy).configuration + assert.Equal(t, 1, len(traversal.gremlinLang.optionsStrategies)) + config := traversal.gremlinLang.optionsStrategies[0].configuration assert.Equal(t, map[string]interface{}{"foo": "bar"}, config) }) t.Run("Test for multiple property", func(t *testing.T) { - g := &GraphTraversalSource{graph: &Graph{}, bytecode: NewBytecode(nil), remoteConnection: nil} + g := &GraphTraversalSource{graph: &Graph{}, gremlinLang: NewGremlinLang(nil), remoteConnection: nil} traversal := g.With("foo", "bar").With("foo2", "bar2") assert.NotNil(t, traversal) - assert.Equal(t, 1, len(traversal.bytecode.sourceInstructions)) - instruction := traversal.bytecode.sourceInstructions[0] - assert.Equal(t, "withStrategies", instruction.operator) - assert.Equal(t, "org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.OptionsStrategy", - instruction.arguments[0].(*traversalStrategy).name) - config := instruction.arguments[0].(*traversalStrategy).configuration + assert.Equal(t, 1, len(traversal.gremlinLang.optionsStrategies)) + config := traversal.gremlinLang.optionsStrategies[0].configuration assert.Equal(t, map[string]interface{}{"foo": "bar", "foo2": "bar2"}, config) }) t.Run("Test for property replacement", func(t *testing.T) { - g := &GraphTraversalSource{graph: &Graph{}, bytecode: NewBytecode(nil), remoteConnection: nil} + g := &GraphTraversalSource{graph: &Graph{}, gremlinLang: NewGremlinLang(nil), remoteConnection: nil} traversal := g.With("foo", "bar").With("foo", "not bar") assert.NotNil(t, traversal) - assert.Equal(t, 1, len(traversal.bytecode.sourceInstructions)) - instruction := traversal.bytecode.sourceInstructions[0] - assert.Equal(t, "withStrategies", instruction.operator) - assert.Equal(t, "org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.OptionsStrategy", - instruction.arguments[0].(*traversalStrategy).name) - config := instruction.arguments[0].(*traversalStrategy).configuration + assert.Equal(t, 1, len(traversal.gremlinLang.optionsStrategies)) + config := traversal.gremlinLang.optionsStrategies[0].configuration assert.Equal(t, map[string]interface{}{"foo": "not bar"}, config) }) }) diff --git a/gremlin-go/driver/gremlinlang.go b/gremlin-go/driver/gremlinlang.go index 66cdc20826..2d66d0c47f 100644 --- a/gremlin-go/driver/gremlinlang.go +++ b/gremlin-go/driver/gremlinlang.go @@ -21,12 +21,14 @@ package gremlingo import ( "fmt" + "go/token" "math" "math/big" "reflect" "strconv" "strings" "sync/atomic" + "time" ) type GremlinLang struct { @@ -150,6 +152,8 @@ func (gl *GremlinLang) argAsString(arg interface{}) (string, error) { return fmt.Sprintf("%vD", v), nil case *BigDecimal, BigDecimal: return fmt.Sprintf("%vM", v), nil + case time.Time: + return fmt.Sprintf("datetime(\"%v\")", v.Format(time.RFC3339)), nil case cardinality, column, direction, operator, order, pick, pop, barrier, scope, t, merge: name := reflect.ValueOf(v).Type().Name() return fmt.Sprintf("%s.%s", strings.ToUpper(name[:1])+name[1:], v), nil @@ -198,6 +202,25 @@ func (gl *GremlinLang) argAsString(arg interface{}) (string, error) { gl.parameters[key] = val } return v.GetGremlin("__"), nil + case GValue: + key := v.Name() + if !token.IsIdentifier(key) { + panic(fmt.Sprintf("invalid parameter name '%v'.", key)) + } + value := v.Value() + if val, ok := gl.parameters[key]; ok { + if reflect.TypeOf(val).Kind() == reflect.Slice || reflect.TypeOf(value).Kind() == reflect.Slice || + reflect.TypeOf(val).Kind() == reflect.Map || reflect.TypeOf(value).Kind() == reflect.Map { + if !reflect.DeepEqual(val, value) { + panic(fmt.Sprintf("parameter with name '%v' already exists.", key)) + } + } else if val != value { + panic(fmt.Sprintf("parameter with name '%v' already exists.", key)) + } + } else { + gl.parameters[key] = v.Value() + } + return key, nil default: switch reflect.TypeOf(arg).Kind() { case reflect.Map: @@ -395,7 +418,7 @@ func (gl *GremlinLang) buildStrategyArgs(args ...interface{}) string { continue } // special handling for OptionsStrategy - if strategy.name == decorationNamespace+"OptionsStrategy" { + if strategy.name == "OptionsStrategy" { gl.optionsStrategies = append(gl.optionsStrategies, strategy) continue } diff --git a/gremlin-go/driver/gremlinlang_test.go b/gremlin-go/driver/gremlinlang_test.go index c4f3237a6e..450bb729ff 100644 --- a/gremlin-go/driver/gremlinlang_test.go +++ b/gremlin-go/driver/gremlinlang_test.go @@ -20,7 +20,6 @@ under the License. package gremlingo import ( - "fmt" "regexp" "testing" "time" @@ -389,7 +388,7 @@ func Test_GremlinLang(t *testing.T) { assert: func(g *GraphTraversalSource) *GraphTraversal { return g.V().Has("date", P.Gt(time.Date(2021, 1, 1, 9, 30, 0, 0, time.UTC))) }, - equals: "g.V().has(\"date\",gt(new Date(121,1,1,9,30,0)))", + equals: "g.V().has(\"date\",gt(datetime(\"2021-01-01T09:30:00Z\")))", }, { assert: func(g *GraphTraversalSource) *GraphTraversal { @@ -485,31 +484,31 @@ func Test_GremlinLang(t *testing.T) { assert: func(g *GraphTraversalSource) *GraphTraversal { return g.V().Has("date", time.Date(2021, 2, 22, 0, 0, 0, 0, time.UTC)) }, - equals: "g.V().has(\"date\",new Date(121,2,22,0,0,0))", + equals: "g.V().has(\"date\",datetime(\"2021-02-22T00:00:00Z\"))", }, { assert: func(g *GraphTraversalSource) *GraphTraversal { return g.V().Has("date", P.Within(time.Date(2021, 2, 22, 0, 0, 0, 0, time.UTC), time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC))) }, - equals: "g.V().has(\"date\",within([new Date(121,2,22,0,0,0),new Date(121,1,1,0,0,0)]))", + equals: "g.V().has(\"date\",within([datetime(\"2021-02-22T00:00:00Z\"),datetime(\"2021-01-01T00:00:00Z\")]))", }, { assert: func(g *GraphTraversalSource) *GraphTraversal { return g.V().Has("date", P.Between(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2021, 2, 22, 0, 0, 0, 0, time.UTC))) }, - equals: "g.V().has(\"date\",between(new Date(121,1,1,0,0,0),new Date(121,2,22,0,0,0)))", + equals: "g.V().has(\"date\",between(datetime(\"2021-01-01T00:00:00Z\"),datetime(\"2021-02-22T00:00:00Z\")))", }, { assert: func(g *GraphTraversalSource) *GraphTraversal { return g.V().Has("date", P.Inside(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2021, 2, 22, 0, 0, 0, 0, time.UTC))) }, - equals: "g.V().has(\"date\",inside(new Date(121,1,1,0,0,0),new Date(121,2,22,0,0,0)))", + equals: "g.V().has(\"date\",inside(datetime(\"2021-01-01T00:00:00Z\"),datetime(\"2021-02-22T00:00:00Z\")))", }, { assert: func(g *GraphTraversalSource) *GraphTraversal { return g.V().Has("date", P.Gt(time.Date(2021, 1, 1, 9, 30, 0, 0, time.UTC))) }, - equals: "g.V().has(\"date\",gt(new Date(121,1,1,9,30,0)))", + equals: "g.V().has(\"date\",gt(datetime(\"2021-01-01T09:30:00Z\")))", }, { assert: func(g *GraphTraversalSource) *GraphTraversal { @@ -563,7 +562,7 @@ func Test_GremlinLang(t *testing.T) { assert: func(g *GraphTraversalSource) *GraphTraversal { return g.WithStrategies(ReadOnlyStrategy()).AddV("test") }, - equals: "g.withStrategies(new ReadOnlyStrategy()).addV(\"test\")", + equals: "g.withStrategies(ReadOnlyStrategy).addV(\"test\")", }, { assert: func(g *GraphTraversalSource) *GraphTraversal { @@ -590,19 +589,20 @@ func Test_GremlinLang(t *testing.T) { return g.WithStrategies(ReadOnlyStrategy(), SubgraphStrategy(SubgraphStrategyConfig{Vertices: T__.Has("region", "US-TX"), Edges: T__.HasLabel("route")})).V().Count() }, containsRandomClassParams: true, - equals: "g.withStrategies(new ReadOnlyStrategy(),new SubgraphStrategy(vertices:__.has(\"region\",\"US-TX\"),edges:__.hasLabel(\"route\"))).V().count()", + equals: "g.withStrategies(ReadOnlyStrategy,new SubgraphStrategy(vertices:__.has(\"region\",\"US-TX\"),edges:__.hasLabel(\"route\"))).V().count()", }, { assert: func(g *GraphTraversalSource) *GraphTraversal { return g.WithStrategies(ReadOnlyStrategy(), SubgraphStrategy(SubgraphStrategyConfig{Vertices: T__.Has("region", "US-TX")})).V().Count() }, - equals: "g.withStrategies(new ReadOnlyStrategy(),new SubgraphStrategy(vertices:__.has(\"region\",\"US-TX\"))).V().count()", + equals: "g.withStrategies(ReadOnlyStrategy,new SubgraphStrategy(vertices:__.has(\"region\",\"US-TX\"))).V().count()", }, { assert: func(g *GraphTraversalSource) *GraphTraversal { return g.WithStrategies(OptionsStrategy(map[string]interface{}{"evaluationTimeout": 500})).V().Count() }, - equals: "g.withStrategies(new OptionsStrategy(evaluationTimeout:500)).V().count()", + // OptionsStrategy are now extracted into request message and is no longer sent with the script + equals: "g.V().count()", }, { assert: func(g *GraphTraversalSource) *GraphTraversal { @@ -615,7 +615,7 @@ func Test_GremlinLang(t *testing.T) { assert: func(g *GraphTraversalSource) *GraphTraversal { return g.WithStrategies(VertexProgramStrategy(VertexProgramStrategyConfig{})).V().ShortestPath().With("~tinkerpop.shortestPath.target", T__.Has("name", "peter")) }, - equals: "g.withStrategies(new VertexProgramStrategy()).V().shortestPath().with(\"~tinkerpop.shortestPath.target\",__.has(\"name\",\"peter\"))", + equals: "g.withStrategies(VertexProgramStrategy).V().shortestPath().with(\"~tinkerpop.shortestPath.target\",__.has(\"name\",\"peter\"))", }, { assert: func(g *GraphTraversalSource) *GraphTraversal { @@ -671,8 +671,6 @@ func Test_GremlinLang(t *testing.T) { t.Run(testName, func(t *testing.T) { g := NewGraphTraversalSource(nil, nil) gremlinLang := tt.assert(g).GremlinLang.GetGremlin() - fmt.Println("---gremlin lang???") - fmt.Println(gremlinLang) if !tt.containsRandomClassParams && gremlinLang != tt.equals { t.Errorf("GremlinLang = %v, equals %v", gremlinLang, tt.equals) } diff --git a/gremlin-go/driver/request.go b/gremlin-go/driver/request.go index 31a57ef8cd..e64195adb5 100644 --- a/gremlin-go/driver/request.go +++ b/gremlin-go/driver/request.go @@ -157,7 +157,7 @@ func extractWithStrategiesReqArgs(insn instruction) map[string]interface{} { continue } - if strategy.name != decorationNamespace+"OptionsStrategy" { + if strategy.name != "OptionsStrategy" { continue } diff --git a/gremlin-go/driver/strategies.go b/gremlin-go/driver/strategies.go index d47636ee4c..ca53da00c9 100644 --- a/gremlin-go/driver/strategies.go +++ b/gremlin-go/driver/strategies.go @@ -19,15 +19,6 @@ under the License. package gremlingo -const ( - baseNamespace = "org.apache.tinkerpop.gremlin.process.traversal.strategy." - decorationNamespace = baseNamespace + "decoration." - finalizationNamespace = baseNamespace + "finalization." - optimizationNamespace = baseNamespace + "optimization." - verificationNamespace = baseNamespace + "verification." - computerDecorationNamespace = "org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.decoration." -) - type TraversalStrategy interface { } @@ -37,12 +28,17 @@ type traversalStrategy struct { apply func(g GraphTraversal) } +// NewTraversalStrategy creates a new strategy with custom name and config +func NewTraversalStrategy(name string, configuration map[string]interface{}) TraversalStrategy { + return &traversalStrategy{name: name, configuration: configuration} +} + // Decoration strategies // ConnectiveStrategy rewrites the binary conjunction form of a.And().b into a AndStep of // And(a,b) (likewise for OrStep). func ConnectiveStrategy() TraversalStrategy { - return &traversalStrategy{name: decorationNamespace + "ConnectiveStrategy"} + return &traversalStrategy{name: "ConnectiveStrategy"} } // ElementIdStrategy provides a degree of control over element identifier assignment as some Graphs don't provide @@ -50,7 +46,7 @@ func ConnectiveStrategy() TraversalStrategy { // under the hood, thus simulating that capability. // By default, when an identifier is not supplied by the user, newly generated identifiers are UUID objects. func ElementIdStrategy() TraversalStrategy { - return &traversalStrategy{name: decorationNamespace + "ElementIdStrategy"} + return &traversalStrategy{name: "ElementIdStrategy"} } func HaltedTraverserStrategy(config HaltedTraverserStrategyConfig) TraversalStrategy { @@ -58,7 +54,7 @@ func HaltedTraverserStrategy(config HaltedTraverserStrategyConfig) TraversalStra if config.HaltedTraverserFactoryName != "" { configMap["haltedTraverserFactory"] = config.HaltedTraverserFactoryName } - return &traversalStrategy{name: decorationNamespace + "HaltedTraverserStrategy", configuration: configMap} + return &traversalStrategy{name: "HaltedTraverserStrategy", configuration: configMap} } // HaltedTraverserStrategyConfig provides configuration options for HaltedTraverserStrategy. @@ -72,7 +68,7 @@ type HaltedTraverserStrategyConfig struct { // essentially a way for users to provide Traversal level configuration options that can be used in various ways // by different Graph providers. func OptionsStrategy(options map[string]interface{}) TraversalStrategy { - return &traversalStrategy{name: decorationNamespace + "OptionsStrategy", configuration: options} + return &traversalStrategy{name: "OptionsStrategy", configuration: options} } // PartitionStrategy partitions the Vertices, Edges and Vertex properties of a Graph into String named @@ -89,7 +85,7 @@ func PartitionStrategy(config PartitionStrategyConfig) TraversalStrategy { if len(config.ReadPartitions.ToSlice()) != 0 { configMap["readPartitions"] = config.ReadPartitions } - return &traversalStrategy{name: decorationNamespace + "PartitionStrategy", configuration: configMap} + return &traversalStrategy{name: "PartitionStrategy", configuration: configMap} } // PartitionStrategyConfig provides configuration options for PartitionStrategy. @@ -110,7 +106,7 @@ type PartitionStrategyConfig struct { // sense is to apply some form of order() in these cases. func SeedStrategy(config SeedStrategyConfig) TraversalStrategy { configMap := map[string]interface{}{"seed": config.Seed} - return &traversalStrategy{name: decorationNamespace + "SeedStrategy", configuration: configMap} + return &traversalStrategy{name: "SeedStrategy", configuration: configMap} } // SeedStrategyConfig provides configuration options for SeedStrategy. Zeroed (unset) values are used. @@ -135,7 +131,7 @@ func SubgraphStrategy(config SubgraphStrategyConfig) TraversalStrategy { if config.CheckAdjacentVertices != nil { configMap["checkAdjacentVertices"] = config.CheckAdjacentVertices.(bool) } - return &traversalStrategy{name: decorationNamespace + "SubgraphStrategy", configuration: configMap} + return &traversalStrategy{name: "SubgraphStrategy", configuration: configMap} } // SubgraphStrategyConfig provides configuration options for SubgraphStrategy. Zeroed (unset) values are ignored. @@ -169,7 +165,7 @@ func VertexProgramStrategy(config VertexProgramStrategyConfig) TraversalStrategy for k, v := range config.Configuration { configMap[k] = v } - return &traversalStrategy{name: computerDecorationNamespace + "VertexProgramStrategy", configuration: configMap} + return &traversalStrategy{name: "VertexProgramStrategy", configuration: configMap} } // VertexProgramStrategyConfig provides configuration options for VertexProgramStrategy. @@ -191,7 +187,7 @@ func MatchAlgorithmStrategy(config MatchAlgorithmStrategyConfig) TraversalStrate if config.MatchAlgorithm != "" { configMap["matchAlgorithm"] = config.MatchAlgorithm } - return &traversalStrategy{name: finalizationNamespace + "MatchAlgorithmStrategy", configuration: configMap} + return &traversalStrategy{name: "MatchAlgorithmStrategy", configuration: configMap} } // MatchAlgorithmStrategyConfig provides configuration options for MatchAlgorithmStrategy. @@ -211,7 +207,7 @@ func EdgeLabelVerificationStrategy(config EdgeLabelVerificationStrategyConfig) T "throwException": config.ThrowExcecption, } - return &traversalStrategy{name: verificationNamespace + "EdgeLabelVerificationStrategy", configuration: configMap} + return &traversalStrategy{name: "EdgeLabelVerificationStrategy", configuration: configMap} } // EdgeLabelVerificationStrategyConfig provides configuration options for EdgeLabelVerificationStrategy. @@ -226,12 +222,12 @@ type EdgeLabelVerificationStrategyConfig struct { // about the traversal. This strategy is not activated by default. However, graph system providers may choose // to make this a default strategy in order to ensure their respective strategies are better able to operate. func LambdaRestrictionStrategy() TraversalStrategy { - return &traversalStrategy{name: verificationNamespace + "LambdaRestrictionStrategy"} + return &traversalStrategy{name: "LambdaRestrictionStrategy"} } // ReadOnlyStrategy detects steps marked with Mutating and returns an error if one is found. func ReadOnlyStrategy() TraversalStrategy { - return &traversalStrategy{name: verificationNamespace + "ReadOnlyStrategy"} + return &traversalStrategy{name: "ReadOnlyStrategy"} } // ReservedKeysVerificationStrategy detects property keys that should not be used by the traversal. @@ -244,7 +240,7 @@ func ReservedKeysVerificationStrategy(config ReservedKeysVerificationStrategyCon if len(config.Keys) != 0 { configMap["keys"] = config.Keys } - return &traversalStrategy{name: verificationNamespace + "ReservedKeysVerificationStrategy", configuration: configMap} + return &traversalStrategy{name: "ReservedKeysVerificationStrategy", configuration: configMap} } // ReservedKeysVerificationStrategyConfig provides configuration options for ReservedKeysVerificationStrategy. @@ -265,20 +261,20 @@ type ReservedKeysVerificationStrategyConfig struct { // the Vertex on the other side of an Edge) can be satisfied by trips to incident Graph Elements (e.g. just the Edge // itself). func AdjacentToIncidentStrategy() TraversalStrategy { - return &traversalStrategy{name: optimizationNamespace + "AdjacentToIncidentStrategy"} + return &traversalStrategy{name: "AdjacentToIncidentStrategy"} } // ByModulatorOptimizationStrategy looks for standard traversals in By-modulators and replaces them with more // optimized traversals (e.g. TokenTraversal) if possible. func ByModulatorOptimizationStrategy() TraversalStrategy { - return &traversalStrategy{name: optimizationNamespace + "ByModulatorOptimizationStrategy"} + return &traversalStrategy{name: "ByModulatorOptimizationStrategy"} } // CountStrategy optimizes any occurrence of CountGlobalStep followed by an IsStep The idea is to limit // the number of incoming elements in a way that it's enough for the IsStep to decide whether it evaluates // true or false. If the traversal already contains a user supplied limit, the strategy won't modify it. func CountStrategy() TraversalStrategy { - return &traversalStrategy{name: optimizationNamespace + "CountStrategy"} + return &traversalStrategy{name: "CountStrategy"} } // EarlyLimitStrategy looks for RangeGlobalSteps that can be moved further left in the traversal and thus be applied @@ -286,7 +282,7 @@ func CountStrategy() TraversalStrategy { // If the logical consequence of one or multiple RangeGlobalSteps is an empty result, the strategy will remove // as many steps as possible and add a NoneStep instead. func EarlyLimitStrategy() TraversalStrategy { - return &traversalStrategy{name: optimizationNamespace + "EarlyLimitStrategy"} + return &traversalStrategy{name: "EarlyLimitStrategy"} } // FilterRankingStrategy reorders filter- and order-steps according to their rank. Step ranks are defined within @@ -294,14 +290,14 @@ func EarlyLimitStrategy() TraversalStrategy { // push step labels as far "right" as possible in order to keep Traversers as small and bulkable as possible prior to // the absolute need for Path-labeling. func FilterRankingStrategy() TraversalStrategy { - return &traversalStrategy{name: optimizationNamespace + "FilterRankingStrategy"} + return &traversalStrategy{name: "FilterRankingStrategy"} } // IdentityRemovalStrategy looks for IdentityStep instances and removes them. // If the identity step is labeled, its labels are added to the previous step. // If the identity step is labeled and it's the first step in the traversal, it stays. func IdentityRemovalStrategy() TraversalStrategy { - return &traversalStrategy{name: optimizationNamespace + "IdentityRemovalStrategy"} + return &traversalStrategy{name: "IdentityRemovalStrategy"} } // IncidentToAdjacentStrategy looks for .OutE().InV(), .InE().OutV() and .BothE().OtherV() @@ -312,7 +308,7 @@ func IdentityRemovalStrategy() TraversalStrategy { // the traversal contains a Path step // the traversal contains a Lambda step func IncidentToAdjacentStrategy() TraversalStrategy { - return &traversalStrategy{name: optimizationNamespace + "IncidentToAdjacentStrategy"} + return &traversalStrategy{name: "IncidentToAdjacentStrategy"} } // InlineFilterStrategy analyzes filter-steps with child traversals that themselves are pure filters. If @@ -321,7 +317,7 @@ func IncidentToAdjacentStrategy() TraversalStrategy { // a graph provider may need to reason about when writing their own strategies. As a result, this strategy helps // increase the likelihood that a provider's filtering optimization will succeed at re-writing the traversal. func InlineFilterStrategy() TraversalStrategy { - return &traversalStrategy{name: optimizationNamespace + "InlineFilterStrategy"} + return &traversalStrategy{name: "InlineFilterStrategy"} } // LazyBarrierStrategy is an OLTP-only strategy that automatically inserts a NoOpBarrierStep after every @@ -329,21 +325,21 @@ func InlineFilterStrategy() TraversalStrategy { // traversal's last step or a barrier. NoOpBarrierSteps allow Traversers to be bulked, thus this strategy // is meant to reduce memory requirements and improve the overall query performance. func LazyBarrierStrategy() TraversalStrategy { - return &traversalStrategy{name: optimizationNamespace + "LazyBarrierStrategy"} + return &traversalStrategy{name: "LazyBarrierStrategy"} } // MatchPredicateStrategy will fold any post-Where() step that maintains a traversal constraint into // Match(). MatchStep is intelligent with traversal constraint applications and thus, can more // efficiently use the constraint of WhereTraversalStep or WherePredicateStep. func MatchPredicateStrategy() TraversalStrategy { - return &traversalStrategy{name: optimizationNamespace + "MatchPredicateStrategy"} + return &traversalStrategy{name: "MatchPredicateStrategy"} } // OrderLimitStrategy is an OLAP strategy that folds a RangeGlobalStep into a preceding // OrderGlobalStep. This helps to eliminate traversers early in the traversal and can // significantly reduce the amount of memory required by the OLAP execution engine. func OrderLimitStrategy() TraversalStrategy { - return &traversalStrategy{name: optimizationNamespace + "OrderLimitStrategy"} + return &traversalStrategy{name: "OrderLimitStrategy"} } // PathProcessorStrategy is an OLAP strategy that does its best to turn non-local children in Where() @@ -351,13 +347,13 @@ func OrderLimitStrategy() TraversalStrategy { // PathProcessorStrategy helps to ensure that more traversals meet the local child constraint imposed // on OLAP traversals. func PathProcessorStrategy() TraversalStrategy { - return &traversalStrategy{name: optimizationNamespace + "PathProcessorStrategy"} + return &traversalStrategy{name: "PathProcessorStrategy"} } // PathRetractionStrategy will remove Paths from the Traversers and increase the likelihood of bulking // as Path data is not required after Select('b'). func PathRetractionStrategy() TraversalStrategy { - return &traversalStrategy{name: optimizationNamespace + "PathRetractionStrategy"} + return &traversalStrategy{name: "PathRetractionStrategy"} } // ProductiveByStrategy takes an argument of By() and wraps it CoalesceStep so that the result is either @@ -370,7 +366,7 @@ func ProductiveByStrategy(config ...ProductiveByStrategyConfig) TraversalStrateg configMap["productiveKeys"] = config[0].ProductiveKeys } - return &traversalStrategy{name: optimizationNamespace + "ProductiveByStrategy", configuration: configMap} + return &traversalStrategy{name: "ProductiveByStrategy", configuration: configMap} } // ProductiveByStrategyConfig provides configuration options for ProductiveByStrategy. @@ -387,7 +383,7 @@ type ProductiveByStrategyConfig struct { // LoopsStep // LambdaHolder func RepeatUnrollStrategy() TraversalStrategy { - return &traversalStrategy{name: optimizationNamespace + "RepeatUnrollStrategy"} + return &traversalStrategy{name: "RepeatUnrollStrategy"} } // RemoteStrategy reconstructs a Traversal by appending a RemoteStep to its end. That step will submit the Traversal to diff --git a/gremlin-go/driver/strategies_test.go b/gremlin-go/driver/strategies_test.go index 3323a78954..3649eedb5a 100644 --- a/gremlin-go/driver/strategies_test.go +++ b/gremlin-go/driver/strategies_test.go @@ -21,6 +21,7 @@ package gremlingo import ( "crypto/tls" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -120,19 +121,12 @@ func TestStrategy(t *testing.T) { assert.Equal(t, int32(0), val) }) - t.Run("Test Bytecode generation for MatchAlgorithmStrategy", func(t *testing.T) { - g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{}) - defer g.remoteConnection.Close() + t.Run("Test GremlinLang generation for MatchAlgorithmStrategy", func(t *testing.T) { + g := NewGraphTraversalSource(nil, nil) config := MatchAlgorithmStrategyConfig{MatchAlgorithm: "greedy"} - bytecode := g.WithStrategies(MatchAlgorithmStrategy(config)).bytecode - assert.Equal(t, 1, len(bytecode.sourceInstructions)) - assert.Equal(t, 1, len(bytecode.sourceInstructions[0].arguments)) - assert.Equal(t, "withStrategies", bytecode.sourceInstructions[0].operator) - assert.Equal(t, "org.apache.tinkerpop.gremlin.process.traversal.strategy.finalization.MatchAlgorithmStrategy", - bytecode.sourceInstructions[0].arguments[0].(*traversalStrategy).name) - assert.Equal(t, map[string]interface{}{"matchAlgorithm": "greedy"}, - bytecode.sourceInstructions[0].arguments[0].(*traversalStrategy).configuration) + gl := g.WithStrategies(MatchAlgorithmStrategy(config)).gremlinLang + assert.True(t, strings.Contains(gl.gremlin.String(), "withStrategies(new MatchAlgorithmStrategy(matchAlgorithm:\"greedy\"))")) }) t.Run("Test read with AdjacentToIncidentStrategy", func(t *testing.T) { @@ -413,4 +407,22 @@ func TestStrategy(t *testing.T) { assert.Nil(t, err) assert.Equal(t, int32(6), val) }) + + t.Run("Test GremlinLang generation for simple custom strategies", func(t *testing.T) { + g := NewGraphTraversalSource(nil, nil) + + customStrategy := NewTraversalStrategy("CustomSingletonStrategy", nil) + gl := g.WithStrategies(customStrategy).gremlinLang + assert.True(t, strings.Contains(gl.gremlin.String(), "withStrategies(CustomSingletonStrategy)")) + }) + + t.Run("Test GremlinLang generation for config custom strategies", func(t *testing.T) { + g := NewGraphTraversalSource(nil, nil) + + customStrategy := NewTraversalStrategy("CustomConfigurableStrategy", + map[string]interface{}{"stringKey": "string value", "intKey": 5, "booleanKey": true}) + gl := g.WithStrategies(customStrategy).gremlinLang + assert.True(t, strings.Contains(gl.gremlin.String(), + "withStrategies(new CustomConfigurableStrategy(stringKey:\"string value\",intKey:5,booleanKey:true))")) + }) } diff --git a/gremlin-go/driver/traversal_test.go b/gremlin-go/driver/traversal_test.go index 21cc75af1e..ad6491f6e2 100644 --- a/gremlin-go/driver/traversal_test.go +++ b/gremlin-go/driver/traversal_test.go @@ -31,7 +31,7 @@ import ( func TestTraversal(t *testing.T) { t.Run("Test clone traversal", func(t *testing.T) { - g := cloneGraphTraversalSource(&Graph{}, NewBytecode(nil), NewGremlinLang(nil), nil) + g := cloneGraphTraversalSource(&Graph{}, NewGremlinLang(nil), nil) original := g.V().Out("created") clone := original.Clone().Out("knows") cloneClone := clone.Clone().Out("created") @@ -56,7 +56,7 @@ func TestTraversal(t *testing.T) { }) t.Run("Test traversal with bindings", func(t *testing.T) { - g := cloneGraphTraversalSource(&Graph{}, NewBytecode(nil), NewGremlinLang(nil), nil) + g := cloneGraphTraversalSource(&Graph{}, NewGremlinLang(nil), nil) bytecode := g.V((&Bindings{}).Of("a", []int32{1, 2, 3})). Out((&Bindings{}).Of("b", "created")). Where(T__.In((&Bindings{}).Of("c", "created"), (&Bindings{}).Of("d", "knows")).
