Cole-Greer commented on code in PR #3437: URL: https://github.com/apache/tinkerpop/pull/3437#discussion_r3376011897
########## gql-gremlin/src/main/antlr4/GQL.g4: ########## @@ -0,0 +1,311 @@ +/* + * 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. + */ + +/** + * Minimal GQL grammar covering the MATCH subset for node/edge patterns. + * + * Supported patterns: + * - Anonymous nodes: () + * - Variable-only nodes: (n) + * - Labeled nodes: (:Label) or (n:Label) + * - Directed edges: -[e:Label]-> or -[:Label]-> or -[e]-> or -[]-> + * - Reverse directed edges: <-[e:Label]- or <-[:Label]- or <-[e]- or <-[]- + * - Undirected edges: -[e:Label]- or -[:Label]- or -[e]- or -[]- + * - Multiple comma-separated path patterns in a single MATCH clause + * - Inline property filters on nodes: (n:Label {key: 'value', count: 42i, flag: true, x: $param}) + * + * Property value literal types align with Gremlin's type system: + * Strings: single-quoted 'text' or double-quoted "text", Java escape sequences supported + * Integers: optional sign, decimal digits, optional suffix (b/B=Byte, s/S=Short, i/I=Integer, + * l/L=Long, n/N=BigInteger); no suffix defaults to smallest fitting type + * Floats: decimal-point form or integer-form with suffix (f/F=Float, d/D=Double, m/M=BigDecimal); + * no suffix defaults to Double + * Booleans: true/false (case-insensitive) + * Null: null + * Special: NaN, Infinity, +Infinity, -Infinity + * Params: $name (resolved from the params map at execution time) + * + * Out of scope: WHERE clause, RETURN, path quantifiers. + */ +grammar GQL; + +// ─── Parser Rules ──────────────────────────────────────────────────────────── + +/** + * Top-level entry point: a single MATCH clause followed by end-of-input. + */ +matchClause + : K_MATCH graphPattern EOF + ; + +/** + * A graph pattern is one or more comma-separated path patterns. + * + * Example: MATCH (n:Person)-[:KNOWS]->(m), (p:Movie) + */ +graphPattern + : pathPattern (COMMA pathPattern)* + ; + +/** + * A path pattern is a node pattern optionally extended by alternating + * edge and node patterns. + * + * Example: (n:Person)-[:KNOWS]->(m:Person)-[:LIKES]->(c) + */ +pathPattern + : nodePattern (edgePattern nodePattern)* + ; + +/** + * A node pattern: parenthesised element with optional variable, label, and property filter. + * + * Examples: () (n) (:Person) (n:Person) (n:Person {name: 'Alice'}) (n {age: $age}) + */ +nodePattern + : LPAREN elementPatternFiller RPAREN + ; + +/** + * Shared inner content for node patterns: + * an optional variable name, an optional label, and an optional property filter map. + * + * Examples: (empty) n :Label n:Label n:Label {key: value} + */ +elementPatternFiller + : elementVariable? labelSpec? propertyFilter? + ; + +/** + * A single colon-prefixed label. + * + * Example: :KNOWS + */ +labelSpec + : COLON labelName + ; + +/** + * An inline property filter map: a comma-separated list of key-value pairs + * enclosed in curly braces. + * + * Example: {name: 'Alice', age: 30i, active: true, score: $minScore} + */ +propertyFilter + : LBRACE propertyPair (COMMA propertyPair)* RBRACE + ; + +/** + * A single key-value predicate within a property filter. + * + * Example: name: 'Alice' + */ +propertyPair + : propertyKey COLON propertyValue + ; + +/** + * The property key (always an identifier). + */ +propertyKey + : IDENTIFIER + ; + +/** + * A property value: either a literal or a parameter reference. + */ +propertyValue + : literal + | paramRef + ; + +/** + * Literal value types, aligned with Gremlin's GenericLiteralVisitor type system. + * FLOAT_LITERAL must precede INTEGER_LITERAL in ANTLR alternatives so that the + * lexer-level maximal-munch already resolved the token type correctly. + */ +literal + : STRING_LITERAL + | FLOAT_LITERAL + | INTEGER_LITERAL + | K_TRUE + | K_FALSE + | K_NULL + | K_NAN + | SIGNED_INFINITY + | K_INFINITY + ; + +/** + * A parameter reference: a dollar sign followed by an identifier. + * + * Example: $personName + */ +paramRef + : DOLLAR IDENTIFIER + ; + +/** + * Three edge pattern flavors, all requiring bracket notation so that + * variable binding and label are available. + */ +edgePattern + : directedEdge + | reverseDirectedEdge + | undirectedEdge + ; + +/** + * Directed edge: -[var?:Label?]-> + * + * Example: -[e:KNOWS]-> + */ +directedEdge + : DASH LBRACKET elementPatternFiller RBRACKET ARROW Review Comment: I stumbled into a bug in the edge arrow parsing while testing. The grammar actually looks fine on first inspection, but somethings not right in the console: ``` gremlin> g.match("MATCH (src:airport {code: 'YVR'})<-[r:route]-(dest:airport {code: 'YYC'})") ==>[src:v[48],r:e[16277][99-route->48],dest:v[99]] gremlin> g.match("MATCH (src:airport {code: 'YVR'})>-[r:route]-(dest:airport {code: 'YYC'})") ==>[src:v[48],r:e[9098][48-route->99],dest:v[99]] ==>[src:v[48],r:e[16277][99-route->48],dest:v[99]] gremlin> g.match("MATCH (src:airport {code: 'YVR'})>-[r:route]->(dest:airport {code: 'YYC'})") ==>[src:v[48],r:e[9098][48-route->99],dest:v[99]] gremlin> g.match("MATCH (src:airport {code: 'YVR'})>-[r:route]->>>(dest:airport {code: 'YYC'})") ==>[src:v[48],r:e[9098][48-route->99],dest:v[99]] gremlin> g.match("MATCH (src:airport {code: 'YVR'})>-[r:route]->><>(dest:airport {code: 'YYC'})") ==>[src:v[48],r:e[9098][48-route->99],dest:v[99]] gremlin> g.match("MATCH (src:airport {code: 'YVR'})><<-[r:route]->><>(dest:airport {code: 'YYC'})") ==>[src:v[48],r:e[9098][48-route->99],dest:v[99]] ``` ########## gql-gremlin/src/main/antlr4/GQL.g4: ########## @@ -0,0 +1,311 @@ +/* + * 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. + */ + Review Comment: Nit: It would be nice if GQL parsing errors could give a bit more information about where error in the script is similarly to how gremlin-lang gives a position in the script as well as the bad token: ``` engine.eval("g.V().hsLabel('person')") org.apache.tinkerpop.gremlin.language.grammar.GremlinParserException: Failed to interpret Gremlin query: Query parsing failed at line 1, character position at 6, error message : no viable alternative at input 'g.V().hsLabel' Type ':help' or ':h' for help. Display stack trace? [yN] ``` ``` gremlin> g.match("(src:airport {code: 'YVR'})-[r:route]->(dest:airport {code: 'YYC'})") Failed to parse GQL MATCH expression: (src:airport {code: 'YVR'})-[r:route]->(dest:airport {code: 'YYC'}) Type ':help' or ':h' for help. Display stack trace? [yN] ``` ########## gql-gremlin/README.md: ########## @@ -0,0 +1,68 @@ +<!-- +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. +--> + +# gql-gremlin + +`gql-gremlin` is an optional Apache TinkerPop module that provides **TinkerGQL**, a deliberate +minimal subset of [ISO GQL](https://www.iso.org/standard/76120.html) `MATCH` syntax, as a portable graph-pattern execution engine +for the `match(String)` step. + +Any TinkerPop graph provider can add TinkerGQL support to their graph in minutes. TinkerGraph +ships with it out of the box. + +--- + +## What is TinkerGQL? + +TinkerGQL is the named dialect implemented by `gql-gremlin`. It covers enough of the GQL `MATCH` +grammar to express multi-hop path patterns, inline property filters, parameterized queries, and +multi-pattern joins — the most common declarative graph query patterns — while deliberately +omitting features (aggregations, variable-length paths, `WHERE`/`RETURN` clauses) that are Review Comment: I would dispute that variable-length paths are better served by gremlin steps, I think that would be a great feature to add, and I believe it's one of the query types which is much easier to write in a GQL style compared to gremlin. That said, I view this as a foundational PR and a jumping off point, and I don't think variable-length paths need to be included in this initial scope. ########## gremlin-js/gremlin-javascript/lib/language/translator/GoTranslateVisitor.ts: ########## @@ -368,6 +368,35 @@ export default class GoTranslateVisitor extends TranslateVisitor { } } + visitTraversalSourceSpawnMethod_match_string_map(ctx: any): void { Review Comment: The same comments made on the Go translators in Java apply here as well. ########## gremlin-language/src/main/antlr4/Gremlin.g4: ########## @@ -159,6 +160,11 @@ traversalSourceSpawnMethod_union : K_UNION LPAREN nestedTraversalList RPAREN ; +traversalSourceSpawnMethod_match + : K_MATCH LPAREN stringLiteral RPAREN #traversalSourceSpawnMethod_match_string + | K_MATCH LPAREN stringLiteral COMMA genericMapArgument RPAREN #traversalSourceSpawnMethod_match_string_map Review Comment: We should either go with full GValue support for the parameter map in MatchStep (add to GraphTraversal, create a DeclarativeMatchStepPlaceholder...) or we should stick with literals only for now. Currently the parser is just reducing the GValue to a literal map which is misleading. ```suggestion | K_MATCH LPAREN stringLiteral COMMA genericMapLiteral RPAREN #traversalSourceSpawnMethod_match_string_map ``` ########## docs/src/dev/provider/gremlin-semantics.asciidoc: ########## @@ -1796,6 +1796,59 @@ See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/j link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LTrimLocalStep.java[source (local)], link:https://tinkerpop.apache.org/docs/x.y.z/reference/#lTrim-step[reference] +[[match-step]] +=== match() + +*Description:* Executes a declarative pattern match query against the graph using a provider-supported query +string. The query string format is provider-specific; providers may use TinkerPop's optional `gql-gremlin` module +(which implements the <<tinkergql,TinkerGQL>> dialect) or supply their own engine. Users should consult their +graph system's documentation to determine what format is supported. The second argument, when provided, supplies +bound parameters to the query; how (and whether) these are used is left to the provider. + +*Syntax:* + +* `match(String matchQuery)` + +* `match(String matchQuery, Map<String, Object> params)` + +[width="100%",options="header"] +|========================================================= +|Start Step |Mid Step |Modulated |Domain |Range +|Y |Y |Y (via `with()`) |`any` |`Map<String, Element>` Review Comment: ```suggestion |Y |Y |Y (via `with()`) |`any` |`Map<String, Object>` ``` ########## gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/GoTranslateVisitor.java: ########## @@ -415,6 +415,38 @@ protected void appendAnonymousSpawn() { sb.append(GO_PACKAGE_NAME).append("T__."); } + @Override + public Void visitTraversalSourceSpawnMethod_match_string_map(final GremlinParser.TraversalSourceSpawnMethod_match_string_mapContext ctx) { + sb.append("MatchWithParams("); + visit(ctx.stringLiteral()); + sb.append(", "); + visitGoParamsMap(ctx.genericMapArgument()); + sb.append(")"); + return null; + } + + @Override + public Void visitTraversalMethod_match_string_map(final GremlinParser.TraversalMethod_match_string_mapContext ctx) { + sb.append("MatchGqlWithParams("); + visit(ctx.stringLiteral()); + sb.append(", "); + visitGoParamsMap(ctx.genericMapArgument()); + sb.append(")"); + return null; + } + + private void visitGoParamsMap(final GremlinParser.GenericMapArgumentContext mapArg) { + sb.append("map[string]interface{}{"); + if (mapArg.genericMapLiteral() != null) { Review Comment: This is arguably a redundant based on my other comment about switching `GenericMapArgument` to `GenericMapLiteral`, but we shouldn't have a function to translate `GenericMapArgumentContext`, which ignores the `Variable` case. ########## gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/GoTranslateVisitor.java: ########## @@ -415,6 +415,38 @@ protected void appendAnonymousSpawn() { sb.append(GO_PACKAGE_NAME).append("T__."); } + @Override + public Void visitTraversalSourceSpawnMethod_match_string_map(final GremlinParser.TraversalSourceSpawnMethod_match_string_mapContext ctx) { + sb.append("MatchWithParams("); + visit(ctx.stringLiteral()); + sb.append(", "); + visitGoParamsMap(ctx.genericMapArgument()); + sb.append(")"); + return null; + } + + @Override + public Void visitTraversalMethod_match_string_map(final GremlinParser.TraversalMethod_match_string_mapContext ctx) { Review Comment: If we're keeping the step name overrides in Go, I think we need another translation case for `visitTraversalMethod_match_string`, which maps to the `MatchGql()` step name. ########## gremlin-go/driver/graphTraversal.go: ########## @@ -502,11 +502,27 @@ func (g *GraphTraversal) Map(args ...interface{}) *GraphTraversal { } // Match adds the match step to the GraphTraversal. +// Deprecated: As of release 4.0.0, prefer MatchGql for declarative GQL pattern matching. func (g *GraphTraversal) Match(args ...interface{}) *GraphTraversal { g.GremlinLang.AddStep("match", args...) return g } +// MatchGql adds a declarative pattern match step to the GraphTraversal. The query language +// defaults to "gql" and can be overridden with .With("queryLanguage", value). +func (g *GraphTraversal) MatchGql(matchQuery string) *GraphTraversal { Review Comment: Lack of overloads is definitely what I hate most about gremlin-go. I'm not a fan of these new step names (especially as the declarative query string is not necessarily GQL), although options are a bit limited. Considering the old deprecated step still has the permissive signature of `Match(args ...interface{})`, how would you feel about just combining both forms of match into a single step in gremlin-go, and leave it up to the server to resolve which one to use? This would technically be unloading a new problem onto [Rithin's typed steps in go project](https://lists.apache.org/thread/0md08v1m7485389lrfds648459ky0d7d), but I think I'd rather force a compromise there than accept less-than-ideal step names in go. Thoughts? ########## gremlin-go/driver/cucumber/gremlin.go: ########## @@ -1270,6 +1270,28 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[ "g_V_matchXa_0sungBy_b__a_0writtenBy_c__b_writtenBy_dX_whereXc_sungBy_dX_whereXd_hasXname_GarciaXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Match(gremlingo.T__.As("a").In("sungBy").As("b"), gremlingo.T__.As("a").In("writtenBy").As("c"), gremlingo.T__.As("b").Out("writtenBy").As("d")).Where(gremlingo.T__.As("c").Out("sungBy").As("d")).Where(gremlingo.T__.As("d").Has("name", "Garcia"))}}, "g_V_matchXa_hasXname_GarciaX__a_0writtenBy_b__b_followedBy_c__c_writtenBy_d__whereXd_neqXaXXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Match(gremlingo.T__.As("a").Has("name", "Garcia"), gremlingo.T__.As("a").In("writtenBy").As("b"), gremlingo.T__.As("b").Out("followedBy").As("c"), gremlingo.T__.As("c").Out("writtenBy").As("d"), gremlingo.T__.Where("d", gremlingo.P.Neq("a")))}}, "g_V_matchXa_outXknowsX_name_bX_identity": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Match(gremlingo.T__.As("a").Out("knows").Values("name").As("b")).Identity()}}, + "g_match_person_selectXpX_byXnameX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Match("MATCH (p:person)").Select("p").By("name")}}, + "g_match_personXknowsX_person_selectXa_bX_byXnameX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Match("MATCH (a:person)-[:knows]->(b:person)").Select("a", "b").By("name")}}, + "g_match_personXknowsX_personXcreatedX_software_selectXa_b_sX_byXnameX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Match("MATCH (a:person)-[:knows]->(b:person)-[:created]->(s:software)").Select("a", "b", "s").By("name")}}, + "g_match_softwareXreversedCreatedX_person_selectXp_sX_byXnameX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Match("MATCH (s:software)<-[:created]-(p:person)").Select("p", "s").By("name")}}, + "g_match_personXundirectedKnowsX_person_selectXa_bX_byXnameX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Match("MATCH (a:person)-[:knows]-(b:person)").Select("a", "b").By("name")}}, + "g_match_personXname_markoX_knowsPerson_selectXp_fX_byXnameX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Match("MATCH (p:person {name: 'marko'})-[:knows]->(f:person)").Select("p", "f").By("name")}}, + "g_match_personXname_paramX_knowsPerson_selectXp_fX_byXnameX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MatchWithParams("MATCH (p:person {name: $who})-[:knows]->(f:person)", map[string]interface{}{"who": "marko" }).Select("p", "f").By("name")}}, + "g_match_multiPattern_sharedVariable_whereXa_neqXbXX_selectXa_b_sX_byXnameX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Match("MATCH (a:person)-[:created]->(s:software), (b:person)-[:created]->(s)").Where("a", gremlingo.P.Neq("b")).Select("a", "b", "s").By("name")}}, + "g_match_terminalBindingMap": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Match("MATCH (a:person)-[:knows]->(b:person)")}}, + "g_inject_match_midTraversal_selectXa_bX_byXnameX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(1).Match("MATCH (a:person)-[:knows]->(b:person)").Select("a", "b").By("name")}}, Review Comment: It's notable that the translator is still mapping this case to the deprecated `Match(args ...interface{})` instead of the new `MatchGql(matchQuery string)` overload. ########## gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/GoTranslateVisitor.java: ########## @@ -415,6 +415,38 @@ protected void appendAnonymousSpawn() { sb.append(GO_PACKAGE_NAME).append("T__."); } + @Override + public Void visitTraversalSourceSpawnMethod_match_string_map(final GremlinParser.TraversalSourceSpawnMethod_match_string_mapContext ctx) { + sb.append("MatchWithParams("); + visit(ctx.stringLiteral()); + sb.append(", "); + visitGoParamsMap(ctx.genericMapArgument()); + sb.append(")"); + return null; + } + + @Override + public Void visitTraversalMethod_match_string_map(final GremlinParser.TraversalMethod_match_string_mapContext ctx) { + sb.append("MatchGqlWithParams("); Review Comment: The Go translation is funky enough that I think this warrants a new case or two in GremlinTranslatorTest ########## gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MatchString.feature: ########## @@ -0,0 +1,310 @@ +# 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. + +@TinkerGQL @StepClassMap @StepMatch +Feature: Step - match() (String form) + + Scenario: g_match_person_selectXpX_byXnameX + Given the modern graph + And the traversal of + """ + g.match("MATCH (p:person)").select("p").by("name") + """ + When iterated to list + Then the result should be unordered + | result | + | marko | + | vadas | + | josh | + | peter | + + Scenario: g_match_personXknowsX_person_selectXa_bX_byXnameX + Given the modern graph + And the traversal of + """ + g.match("MATCH (a:person)-[:knows]->(b:person)").select("a","b").by("name") + """ + When iterated to list + Then the result should be unordered + | result | + | m[{"a":"marko","b":"vadas"}] | + | m[{"a":"marko","b":"josh"}] | + + Scenario: g_match_personXknowsX_personXcreatedX_software_selectXa_b_sX_byXnameX + Given the modern graph + And the traversal of + """ + g.match("MATCH (a:person)-[:knows]->(b:person)-[:created]->(s:software)").select("a","b","s").by("name") Review Comment: This may prove to be a useful enough pattern that we may want to cut out `select()` and just make `match()` itself by-modulatable ``` g.match("MATCH (a:person)-[:knows]->(b:person)-[:created]->(s:software)").by("name") ``` Out of scope for this PR but it's something to consider. ########## gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MatchString.feature: ########## @@ -0,0 +1,310 @@ +# 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. + +@TinkerGQL @StepClassMap @StepMatch +Feature: Step - match() (String form) + + Scenario: g_match_person_selectXpX_byXnameX + Given the modern graph + And the traversal of + """ + g.match("MATCH (p:person)").select("p").by("name") + """ + When iterated to list + Then the result should be unordered + | result | + | marko | + | vadas | + | josh | + | peter | + + Scenario: g_match_personXknowsX_person_selectXa_bX_byXnameX + Given the modern graph + And the traversal of + """ + g.match("MATCH (a:person)-[:knows]->(b:person)").select("a","b").by("name") + """ + When iterated to list + Then the result should be unordered + | result | + | m[{"a":"marko","b":"vadas"}] | + | m[{"a":"marko","b":"josh"}] | + + Scenario: g_match_personXknowsX_personXcreatedX_software_selectXa_b_sX_byXnameX + Given the modern graph + And the traversal of + """ + g.match("MATCH (a:person)-[:knows]->(b:person)-[:created]->(s:software)").select("a","b","s").by("name") + """ + When iterated to list + Then the result should be unordered + | result | + | m[{"a":"marko","b":"josh","s":"ripple"}] | + | m[{"a":"marko","b":"josh","s":"lop"}] | + + Scenario: g_match_softwareXreversedCreatedX_person_selectXp_sX_byXnameX + Given the modern graph + And the traversal of + """ + g.match("MATCH (s:software)<-[:created]-(p:person)").select("p","s").by("name") + """ + When iterated to list + Then the result should be unordered + | result | + | m[{"p":"marko","s":"lop"}] | + | m[{"p":"josh","s":"ripple"}] | + | m[{"p":"josh","s":"lop"}] | + | m[{"p":"peter","s":"lop"}] | + + Scenario: g_match_personXundirectedKnowsX_person_selectXa_bX_byXnameX + Given the modern graph + And the traversal of + """ + g.match("MATCH (a:person)-[:knows]-(b:person)").select("a","b").by("name") + """ + When iterated to list + Then the result should be unordered + | result | + | m[{"a":"marko","b":"vadas"}] | + | m[{"a":"marko","b":"josh"}] | + | m[{"a":"vadas","b":"marko"}] | + | m[{"a":"josh","b":"marko"}] | + + Scenario: g_match_personXname_markoX_knowsPerson_selectXp_fX_byXnameX + Given the modern graph + And the traversal of + """ + g.match("MATCH (p:person {name: 'marko'})-[:knows]->(f:person)").select("p","f").by("name") + """ + When iterated to list + Then the result should be unordered + | result | + | m[{"p":"marko","f":"vadas"}] | + | m[{"p":"marko","f":"josh"}] | + + Scenario: g_match_personXname_paramX_knowsPerson_selectXp_fX_byXnameX + Given the modern graph + And the traversal of + """ + g.match('MATCH (p:person {name: $who})-[:knows]->(f:person)', ["who": "marko"]).select("p","f").by("name") + """ + When iterated to list + Then the result should be unordered + | result | + | m[{"p":"marko","f":"vadas"}] | + | m[{"p":"marko","f":"josh"}] | + + Scenario: g_match_multiPattern_sharedVariable_whereXa_neqXbXX_selectXa_b_sX_byXnameX + Given the modern graph + And the traversal of + """ + g.match("MATCH (a:person)-[:created]->(s:software), (b:person)-[:created]->(s)").where("a", neq("b")).select("a","b","s").by("name") + """ + When iterated to list + Then the result should be unordered + | result | + | m[{"a":"marko","b":"josh","s":"lop"}] | + | m[{"a":"marko","b":"peter","s":"lop"}] | + | m[{"a":"josh","b":"marko","s":"lop"}] | + | m[{"a":"josh","b":"peter","s":"lop"}] | + | m[{"a":"peter","b":"marko","s":"lop"}] | + | m[{"a":"peter","b":"josh","s":"lop"}] | + + Scenario: g_match_terminalBindingMap + Given the modern graph + And the traversal of + """ + g.match("MATCH (a:person)-[:knows]->(b:person)") + """ + When iterated to list + Then the result should be unordered + | result | + | m[{"a":"v[marko]","b":"v[vadas]"}] | + | m[{"a":"v[marko]","b":"v[josh]"}] | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_inject_match_midTraversal_selectXa_bX_byXnameX + Given the modern graph + And the traversal of + """ + g.inject(1).match("MATCH (a:person)-[:knows]->(b:person)").select("a","b").by("name") + """ + When iterated to list + Then the result should be unordered + | result | + | m[{"a":"marko","b":"vadas"}] | + | m[{"a":"marko","b":"josh"}] | + + Scenario: g_match_anonymousXknowsX_person_selectXbX_byXnameX + Given the modern graph + And the traversal of + """ + g.match("MATCH ()-[:knows]->(b:person)").select("b").by("name") + """ + When iterated to list + Then the result should be unordered + | result | + | vadas | + | josh | + + Scenario: g_match_personXage_29iX_selectXpX_byXnameX + Given the modern graph + And the traversal of + """ + g.match("MATCH (p:person {age: 29i})").select("p").by("name") + """ + When iterated to list + Then the result should be unordered + | result | + | marko | + + Scenario: g_match_absentAgeProperty_selectXvX_byXnameX + Given the modern graph + And the traversal of + """ + g.match("MATCH (v {age: null})").select("v").by("name") + """ + When iterated to list + Then the result should be unordered + | result | + | lop | + | ripple | + + Scenario: g_match_personXname_marko_age_29iX_selectXpX_byXnameX + Given the modern graph + And the traversal of + """ + g.match("MATCH (p:person {name: 'marko', age: 29i})").select("p").by("name") + """ + When iterated to list + Then the result should be unordered + | result | + | marko | + + Scenario: g_match_personXknowsXeX_person_selectXeX + Given the modern graph + And the traversal of + """ + g.match("MATCH (a:person)-[e:knows]->(b:person)").select("e") + """ + When iterated to list + Then the result should be unordered + | result | + | e[marko-knows->vadas] | + | e[marko-knows->josh] | + + Scenario: g_match_personXknowsXweight_1X_person_selectXa_bX_byXnameX + Given the modern graph + And the traversal of + """ + g.match("MATCH (a:person)-[:knows {weight: 1.0}]->(b:person)").select("a","b").by("name") + """ + When iterated to list + Then the result should be unordered + | result | + | m[{"a":"marko","b":"josh"}] | + + Scenario: g_match_personXanyEdgeX_software_selectXa_bX_byXnameX + Given the modern graph + And the traversal of + """ + g.match("MATCH (a:person)-[]->(b:software)").select("a","b").by("name") + """ + When iterated to list + Then the result should be unordered + | result | + | m[{"a":"marko","b":"lop"}] | + | m[{"a":"josh","b":"ripple"}] | + | m[{"a":"josh","b":"lop"}] | + | m[{"a":"peter","b":"lop"}] | + + Scenario: g_match_personXknowsX_anyXcreatedX_software_selectXa_sX_byXnameX + Given the modern graph + And the traversal of + """ + g.match("MATCH (a:person)-[:knows]->()-[:created]->(s:software)").select("a","s").by("name") + """ + When iterated to list + Then the result should be unordered + | result | + | m[{"a":"marko","s":"ripple"}] | + | m[{"a":"marko","s":"lop"}] | + + Scenario: g_match_personXage_paramX_knowsPerson_selectXp_fX_byXnameX_integer + Given the modern graph + And the traversal of + """ + g.match('MATCH (p:person {age: $age})-[:knows]->(f:person)', ["age": 29]).select("p","f").by("name") + """ + When iterated to list + Then the result should be unordered + | result | + | m[{"p":"marko","f":"vadas"}] | + | m[{"p":"marko","f":"josh"}] | + + Scenario: g_match_noMatchPattern_emptyResult + Given the modern graph + And the traversal of + """ + g.match("MATCH (a:software)-[:knows]->(b)").select("a","b") + """ + When iterated to list + Then the result should be empty + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_inject_match_midTraversal_noMatch_emptyResult + Given the modern graph + And the traversal of + """ + g.inject(1).match("MATCH (a:software)-[:knows]->(b:person)").select("a","b") + """ + When iterated to list + Then the result should be empty + + Scenario: g_match_cyclicPattern_personXknowsX_personXcreatedX_softwareXcreatedX_selectXa_b_sX_byXnameX + Given the modern graph + And the traversal of + """ + g.match("MATCH (a:person)-[:knows]->(b:person)-[:created]->(s:software)<-[:created]-(a)").select("a","b","s").by("name") + """ + When iterated to list + Then the result should be unordered + | result | + | m[{"a":"marko","b":"josh","s":"lop"}] | + + Scenario: g_match_multiPattern_bridgeVariable_selectXa_b_sX_byXnameX + Given the modern graph + And the traversal of + """ + g.match("MATCH (a:person)-[:knows]->(b:person), (b:person)-[:created]->(s:software)").select("a","b","s").by("name") + """ + When iterated to list + Then the result should be unordered + | result | + | m[{"a":"marko","b":"josh","s":"ripple"}] | + | m[{"a":"marko","b":"josh","s":"lop"}] | + + Scenario: g_match_personXknowsXeVar_weight_1X_person_selectXa_e_bX + Given the modern graph + And the traversal of + """ + g.match("MATCH (a:person)-[e:knows {weight: 1.0}]->(b:person)").select("a","e","b") + """ + When iterated to list + Then the result should be unordered + | result | + | m[{"a":"v[marko]","e":"e[marko-knows->josh]","b":"v[josh]"}] | Review Comment: Could you add this case as well, I'd like to demonstrate that users can select from the path history as well as simply breaking down the map result: ``` gremlin> g.match("MATCH (a)-[:knows]->(b)").select("a").by("name").concat(constant(" knows "), select("b").by("name")) ==>marko knows vadas ==>marko knows josh ``` ########## gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json: ########## @@ -25246,6 +25246,380 @@ } ] }, + { + "scenario": "g_match_person_selectXpX_byXnameX", + "traversals": [ + { + "original": "g.match(\"MATCH (p:person)\").select(\"p\").by(\"name\")", + "language": "g.match(\"MATCH (p:person)\").select(\"p\").by(\"name\")", + "canonical": "g.match(\"MATCH (p:person)\").select(\"p\").by(\"name\")", + "anonymized": "g.match(string0).select(string1).by(string2)", + "dotnet": "g.Match(\"MATCH (p:person)\").Select<object>(\"p\").By(\"name\")", + "go": "g.Match(\"MATCH (p:person)\").Select(\"p\").By(\"name\")", + "groovy": "g.match(\"MATCH (p:person)\").select(\"p\").by(\"name\")", + "java": "g.match(\"MATCH (p:person)\").select(\"p\").by(\"name\")", + "javascript": "g.match(\"MATCH (p:person)\").select(\"p\").by(\"name\")", + "python": "g.match('MATCH (p:person)').select('p').by('name')" + } + ] + }, + { + "scenario": "g_match_personXknowsX_person_selectXa_bX_byXnameX", + "traversals": [ + { + "original": "g.match(\"MATCH (a:person)-[:knows]->(b:person)\").select(\"a\",\"b\").by(\"name\")", + "language": "g.match(\"MATCH (a:person)-[:knows]->(b:person)\").select(\"a\", \"b\").by(\"name\")", + "canonical": "g.match(\"MATCH (a:person)-[:knows]->(b:person)\").select(\"a\", \"b\").by(\"name\")", + "anonymized": "g.match(string0).select(string1, string2).by(string3)", + "dotnet": "g.Match(\"MATCH (a:person)-[:knows]->(b:person)\").Select<object>(\"a\", \"b\").By(\"name\")", + "go": "g.Match(\"MATCH (a:person)-[:knows]->(b:person)\").Select(\"a\", \"b\").By(\"name\")", + "groovy": "g.match(\"MATCH (a:person)-[:knows]->(b:person)\").select(\"a\", \"b\").by(\"name\")", + "java": "g.match(\"MATCH (a:person)-[:knows]->(b:person)\").select(\"a\", \"b\").by(\"name\")", + "javascript": "g.match(\"MATCH (a:person)-[:knows]->(b:person)\").select(\"a\", \"b\").by(\"name\")", + "python": "g.match('MATCH (a:person)-[:knows]->(b:person)').select('a', 'b').by('name')" + } + ] + }, + { + "scenario": "g_match_personXknowsX_personXcreatedX_software_selectXa_b_sX_byXnameX", + "traversals": [ + { + "original": "g.match(\"MATCH (a:person)-[:knows]->(b:person)-[:created]->(s:software)\").select(\"a\",\"b\",\"s\").by(\"name\")", + "language": "g.match(\"MATCH (a:person)-[:knows]->(b:person)-[:created]->(s:software)\").select(\"a\", \"b\", \"s\").by(\"name\")", + "canonical": "g.match(\"MATCH (a:person)-[:knows]->(b:person)-[:created]->(s:software)\").select(\"a\", \"b\", \"s\").by(\"name\")", + "anonymized": "g.match(string0).select(string1, string2, string3).by(string4)", + "dotnet": "g.Match(\"MATCH (a:person)-[:knows]->(b:person)-[:created]->(s:software)\").Select<object>(\"a\", \"b\", \"s\").By(\"name\")", + "go": "g.Match(\"MATCH (a:person)-[:knows]->(b:person)-[:created]->(s:software)\").Select(\"a\", \"b\", \"s\").By(\"name\")", + "groovy": "g.match(\"MATCH (a:person)-[:knows]->(b:person)-[:created]->(s:software)\").select(\"a\", \"b\", \"s\").by(\"name\")", + "java": "g.match(\"MATCH (a:person)-[:knows]->(b:person)-[:created]->(s:software)\").select(\"a\", \"b\", \"s\").by(\"name\")", + "javascript": "g.match(\"MATCH (a:person)-[:knows]->(b:person)-[:created]->(s:software)\").select(\"a\", \"b\", \"s\").by(\"name\")", + "python": "g.match('MATCH (a:person)-[:knows]->(b:person)-[:created]->(s:software)').select('a', 'b', 's').by('name')" + } + ] + }, + { + "scenario": "g_match_softwareXreversedCreatedX_person_selectXp_sX_byXnameX", + "traversals": [ + { + "original": "g.match(\"MATCH (s:software)<-[:created]-(p:person)\").select(\"p\",\"s\").by(\"name\")", + "language": "g.match(\"MATCH (s:software)<-[:created]-(p:person)\").select(\"p\", \"s\").by(\"name\")", + "canonical": "g.match(\"MATCH (s:software)<-[:created]-(p:person)\").select(\"p\", \"s\").by(\"name\")", + "anonymized": "g.match(string0).select(string1, string2).by(string3)", + "dotnet": "g.Match(\"MATCH (s:software)<-[:created]-(p:person)\").Select<object>(\"p\", \"s\").By(\"name\")", + "go": "g.Match(\"MATCH (s:software)<-[:created]-(p:person)\").Select(\"p\", \"s\").By(\"name\")", + "groovy": "g.match(\"MATCH (s:software)<-[:created]-(p:person)\").select(\"p\", \"s\").by(\"name\")", + "java": "g.match(\"MATCH (s:software)<-[:created]-(p:person)\").select(\"p\", \"s\").by(\"name\")", + "javascript": "g.match(\"MATCH (s:software)<-[:created]-(p:person)\").select(\"p\", \"s\").by(\"name\")", + "python": "g.match('MATCH (s:software)<-[:created]-(p:person)').select('p', 's').by('name')" + } + ] + }, + { + "scenario": "g_match_personXundirectedKnowsX_person_selectXa_bX_byXnameX", + "traversals": [ + { + "original": "g.match(\"MATCH (a:person)-[:knows]-(b:person)\").select(\"a\",\"b\").by(\"name\")", + "language": "g.match(\"MATCH (a:person)-[:knows]-(b:person)\").select(\"a\", \"b\").by(\"name\")", + "canonical": "g.match(\"MATCH (a:person)-[:knows]-(b:person)\").select(\"a\", \"b\").by(\"name\")", + "anonymized": "g.match(string0).select(string1, string2).by(string3)", + "dotnet": "g.Match(\"MATCH (a:person)-[:knows]-(b:person)\").Select<object>(\"a\", \"b\").By(\"name\")", + "go": "g.Match(\"MATCH (a:person)-[:knows]-(b:person)\").Select(\"a\", \"b\").By(\"name\")", + "groovy": "g.match(\"MATCH (a:person)-[:knows]-(b:person)\").select(\"a\", \"b\").by(\"name\")", + "java": "g.match(\"MATCH (a:person)-[:knows]-(b:person)\").select(\"a\", \"b\").by(\"name\")", + "javascript": "g.match(\"MATCH (a:person)-[:knows]-(b:person)\").select(\"a\", \"b\").by(\"name\")", + "python": "g.match('MATCH (a:person)-[:knows]-(b:person)').select('a', 'b').by('name')" + } + ] + }, + { + "scenario": "g_match_personXname_markoX_knowsPerson_selectXp_fX_byXnameX", + "traversals": [ + { + "original": "g.match(\"MATCH (p:person {name: 'marko'})-[:knows]->(f:person)\").select(\"p\",\"f\").by(\"name\")", + "language": "g.match(\"MATCH (p:person {name: 'marko'})-[:knows]->(f:person)\").select(\"p\", \"f\").by(\"name\")", + "canonical": "g.match(\"MATCH (p:person {name: 'marko'})-[:knows]->(f:person)\").select(\"p\", \"f\").by(\"name\")", + "anonymized": "g.match(string0).select(string1, string2).by(string3)", + "dotnet": "g.Match(\"MATCH (p:person {name: 'marko'})-[:knows]->(f:person)\").Select<object>(\"p\", \"f\").By(\"name\")", + "go": "g.Match(\"MATCH (p:person {name: 'marko'})-[:knows]->(f:person)\").Select(\"p\", \"f\").By(\"name\")", + "groovy": "g.match(\"MATCH (p:person {name: 'marko'})-[:knows]->(f:person)\").select(\"p\", \"f\").by(\"name\")", + "java": "g.match(\"MATCH (p:person {name: 'marko'})-[:knows]->(f:person)\").select(\"p\", \"f\").by(\"name\")", + "javascript": "g.match(\"MATCH (p:person {name: 'marko'})-[:knows]->(f:person)\").select(\"p\", \"f\").by(\"name\")", + "python": "g.match(\"MATCH (p:person {name: 'marko'})-[:knows]->(f:person)\").select('p', 'f').by('name')" + } + ] + }, + { + "scenario": "g_match_personXname_paramX_knowsPerson_selectXp_fX_byXnameX", + "traversals": [ + { + "original": "g.match('MATCH (p:person {name: $who})-[:knows]->(f:person)', [\"who\": \"marko\"]).select(\"p\",\"f\").by(\"name\")", + "language": "g.match('MATCH (p:person {name: $who})-[:knows]->(f:person)', [\"who\":\"marko\"]).select(\"p\", \"f\").by(\"name\")", + "canonical": "g.match('MATCH (p:person {name: $who})-[:knows]->(f:person)', [\"who\":\"marko\"]).select(\"p\", \"f\").by(\"name\")", + "anonymized": "g.match(string0, map0).select(string1, string2).by(string3)", + "dotnet": "g.Match(\"MATCH (p:person {name: $who})-[:knows]->(f:person)\", (IDictionary<object, object>) new Dictionary<object, object> {{ \"who\", \"marko\" }}).Select<object>(\"p\", \"f\").By(\"name\")", + "go": "g.MatchWithParams(\"MATCH (p:person {name: $who})-[:knows]->(f:person)\", map[string]interface{}{\"who\": \"marko\" }).Select(\"p\", \"f\").By(\"name\")", + "groovy": "g.match('MATCH (p:person {name: \\$who})-[:knows]->(f:person)', [\"who\":\"marko\"]).select(\"p\", \"f\").by(\"name\")", + "java": "g.match(\"MATCH (p:person {name: $who})-[:knows]->(f:person)\", new LinkedHashMap<Object, Object>() {{ put(\"who\", \"marko\"); }}).select(\"p\", \"f\").by(\"name\")", + "javascript": "g.match(\"MATCH (p:person {name: $who})-[:knows]->(f:person)\", new Map([[\"who\", \"marko\"]])).select(\"p\", \"f\").by(\"name\")", + "python": "g.match('MATCH (p:person {name: $who})-[:knows]->(f:person)', { 'who': 'marko' }).select('p', 'f').by('name')" + } + ] + }, + { + "scenario": "g_match_multiPattern_sharedVariable_whereXa_neqXbXX_selectXa_b_sX_byXnameX", + "traversals": [ + { + "original": "g.match(\"MATCH (a:person)-[:created]->(s:software), (b:person)-[:created]->(s)\").where(\"a\", neq(\"b\")).select(\"a\",\"b\",\"s\").by(\"name\")", + "language": "g.match(\"MATCH (a:person)-[:created]->(s:software), (b:person)-[:created]->(s)\").where(\"a\", P.neq(\"b\")).select(\"a\", \"b\", \"s\").by(\"name\")", + "canonical": "g.match(\"MATCH (a:person)-[:created]->(s:software), (b:person)-[:created]->(s)\").where(\"a\", P.neq(\"b\")).select(\"a\", \"b\", \"s\").by(\"name\")", + "anonymized": "g.match(string0).where(string1, P.neq(string2)).select(string1, string2, string3).by(string4)", + "dotnet": "g.Match(\"MATCH (a:person)-[:created]->(s:software), (b:person)-[:created]->(s)\").Where(\"a\", P.Neq(\"b\")).Select<object>(\"a\", \"b\", \"s\").By(\"name\")", + "go": "g.Match(\"MATCH (a:person)-[:created]->(s:software), (b:person)-[:created]->(s)\").Where(\"a\", gremlingo.P.Neq(\"b\")).Select(\"a\", \"b\", \"s\").By(\"name\")", + "groovy": "g.match(\"MATCH (a:person)-[:created]->(s:software), (b:person)-[:created]->(s)\").where(\"a\", P.neq(\"b\")).select(\"a\", \"b\", \"s\").by(\"name\")", + "java": "g.match(\"MATCH (a:person)-[:created]->(s:software), (b:person)-[:created]->(s)\").where(\"a\", P.neq(\"b\")).select(\"a\", \"b\", \"s\").by(\"name\")", + "javascript": "g.match(\"MATCH (a:person)-[:created]->(s:software), (b:person)-[:created]->(s)\").where(\"a\", P.neq(\"b\")).select(\"a\", \"b\", \"s\").by(\"name\")", + "python": "g.match('MATCH (a:person)-[:created]->(s:software), (b:person)-[:created]->(s)').where('a', P.neq('b')).select('a', 'b', 's').by('name')" + } + ] + }, + { + "scenario": "g_match_terminalBindingMap", + "traversals": [ + { + "original": "g.match(\"MATCH (a:person)-[:knows]->(b:person)\")", + "language": "g.match(\"MATCH (a:person)-[:knows]->(b:person)\")", + "canonical": "g.match(\"MATCH (a:person)-[:knows]->(b:person)\")", + "anonymized": "g.match(string0)", + "dotnet": "g.Match(\"MATCH (a:person)-[:knows]->(b:person)\")", + "go": "g.Match(\"MATCH (a:person)-[:knows]->(b:person)\")", + "groovy": "g.match(\"MATCH (a:person)-[:knows]->(b:person)\")", + "java": "g.match(\"MATCH (a:person)-[:knows]->(b:person)\")", + "javascript": "g.match(\"MATCH (a:person)-[:knows]->(b:person)\")", + "python": "g.match('MATCH (a:person)-[:knows]->(b:person)')" + } + ] + }, + { + "scenario": "g_inject_match_midTraversal_selectXa_bX_byXnameX", + "traversals": [ + { + "original": "g.inject(1).match(\"MATCH (a:person)-[:knows]->(b:person)\").select(\"a\",\"b\").by(\"name\")", + "language": "g.inject(1).match(\"MATCH (a:person)-[:knows]->(b:person)\").select(\"a\", \"b\").by(\"name\")", + "canonical": "g.inject(1).match(\"MATCH (a:person)-[:knows]->(b:person)\").select(\"a\", \"b\").by(\"name\")", + "anonymized": "g.inject(number0).match(string0).select(string1, string2).by(string3)", + "dotnet": "g.Inject<object>(1).Match(\"MATCH (a:person)-[:knows]->(b:person)\").Select<object>(\"a\", \"b\").By(\"name\")", + "go": "g.Inject(1).Match(\"MATCH (a:person)-[:knows]->(b:person)\").Select(\"a\", \"b\").By(\"name\")", + "groovy": "g.inject(1).match(\"MATCH (a:person)-[:knows]->(b:person)\").select(\"a\", \"b\").by(\"name\")", + "java": "g.inject(1).match(\"MATCH (a:person)-[:knows]->(b:person)\").select(\"a\", \"b\").by(\"name\")", + "javascript": "g.inject(1).match(\"MATCH (a:person)-[:knows]->(b:person)\").select(\"a\", \"b\").by(\"name\")", + "python": "g.inject(1).match('MATCH (a:person)-[:knows]->(b:person)').select('a', 'b').by('name')" + } + ] + }, + { + "scenario": "g_match_anonymousXknowsX_person_selectXbX_byXnameX", + "traversals": [ + { + "original": "g.match(\"MATCH ()-[:knows]->(b:person)\").select(\"b\").by(\"name\")", + "language": "g.match(\"MATCH ()-[:knows]->(b:person)\").select(\"b\").by(\"name\")", + "canonical": "g.match(\"MATCH ()-[:knows]->(b:person)\").select(\"b\").by(\"name\")", + "anonymized": "g.match(string0).select(string1).by(string2)", + "dotnet": "g.Match(\"MATCH ()-[:knows]->(b:person)\").Select<object>(\"b\").By(\"name\")", + "go": "g.Match(\"MATCH ()-[:knows]->(b:person)\").Select(\"b\").By(\"name\")", + "groovy": "g.match(\"MATCH ()-[:knows]->(b:person)\").select(\"b\").by(\"name\")", + "java": "g.match(\"MATCH ()-[:knows]->(b:person)\").select(\"b\").by(\"name\")", + "javascript": "g.match(\"MATCH ()-[:knows]->(b:person)\").select(\"b\").by(\"name\")", + "python": "g.match('MATCH ()-[:knows]->(b:person)').select('b').by('name')" + } + ] + }, + { + "scenario": "g_match_personXage_29iX_selectXpX_byXnameX", + "traversals": [ + { + "original": "g.match(\"MATCH (p:person {age: 29i})\").select(\"p\").by(\"name\")", + "language": "g.match(\"MATCH (p:person {age: 29i})\").select(\"p\").by(\"name\")", + "canonical": "g.match(\"MATCH (p:person {age: 29i})\").select(\"p\").by(\"name\")", + "anonymized": "g.match(string0).select(string1).by(string2)", + "dotnet": "g.Match(\"MATCH (p:person {age: 29i})\").Select<object>(\"p\").By(\"name\")", + "go": "g.Match(\"MATCH (p:person {age: 29i})\").Select(\"p\").By(\"name\")", + "groovy": "g.match(\"MATCH (p:person {age: 29i})\").select(\"p\").by(\"name\")", + "java": "g.match(\"MATCH (p:person {age: 29i})\").select(\"p\").by(\"name\")", + "javascript": "g.match(\"MATCH (p:person {age: 29i})\").select(\"p\").by(\"name\")", + "python": "g.match('MATCH (p:person {age: 29i})').select('p').by('name')" + } + ] + }, + { + "scenario": "g_match_absentAgeProperty_selectXvX_byXnameX", + "traversals": [ + { + "original": "g.match(\"MATCH (v {age: null})\").select(\"v\").by(\"name\")", + "language": "g.match(\"MATCH (v {age: null})\").select(\"v\").by(\"name\")", + "canonical": "g.match(\"MATCH (v {age: null})\").select(\"v\").by(\"name\")", + "anonymized": "g.match(string0).select(string1).by(string2)", + "dotnet": "g.Match(\"MATCH (v {age: null})\").Select<object>(\"v\").By(\"name\")", + "go": "g.Match(\"MATCH (v {age: null})\").Select(\"v\").By(\"name\")", + "groovy": "g.match(\"MATCH (v {age: null})\").select(\"v\").by(\"name\")", + "java": "g.match(\"MATCH (v {age: null})\").select(\"v\").by(\"name\")", + "javascript": "g.match(\"MATCH (v {age: null})\").select(\"v\").by(\"name\")", + "python": "g.match('MATCH (v {age: null})').select('v').by('name')" + } + ] + }, + { + "scenario": "g_match_personXname_marko_age_29iX_selectXpX_byXnameX", + "traversals": [ + { + "original": "g.match(\"MATCH (p:person {name: 'marko', age: 29i})\").select(\"p\").by(\"name\")", + "language": "g.match(\"MATCH (p:person {name: 'marko', age: 29i})\").select(\"p\").by(\"name\")", + "canonical": "g.match(\"MATCH (p:person {name: 'marko', age: 29i})\").select(\"p\").by(\"name\")", + "anonymized": "g.match(string0).select(string1).by(string2)", + "dotnet": "g.Match(\"MATCH (p:person {name: 'marko', age: 29i})\").Select<object>(\"p\").By(\"name\")", + "go": "g.Match(\"MATCH (p:person {name: 'marko', age: 29i})\").Select(\"p\").By(\"name\")", + "groovy": "g.match(\"MATCH (p:person {name: 'marko', age: 29i})\").select(\"p\").by(\"name\")", + "java": "g.match(\"MATCH (p:person {name: 'marko', age: 29i})\").select(\"p\").by(\"name\")", + "javascript": "g.match(\"MATCH (p:person {name: 'marko', age: 29i})\").select(\"p\").by(\"name\")", + "python": "g.match(\"MATCH (p:person {name: 'marko', age: 29i})\").select('p').by('name')" + } + ] + }, + { + "scenario": "g_match_personXknowsXeX_person_selectXeX", + "traversals": [ + { + "original": "g.match(\"MATCH (a:person)-[e:knows]->(b:person)\").select(\"e\")", + "language": "g.match(\"MATCH (a:person)-[e:knows]->(b:person)\").select(\"e\")", + "canonical": "g.match(\"MATCH (a:person)-[e:knows]->(b:person)\").select(\"e\")", + "anonymized": "g.match(string0).select(string1)", + "dotnet": "g.Match(\"MATCH (a:person)-[e:knows]->(b:person)\").Select<object>(\"e\")", + "go": "g.Match(\"MATCH (a:person)-[e:knows]->(b:person)\").Select(\"e\")", + "groovy": "g.match(\"MATCH (a:person)-[e:knows]->(b:person)\").select(\"e\")", + "java": "g.match(\"MATCH (a:person)-[e:knows]->(b:person)\").select(\"e\")", + "javascript": "g.match(\"MATCH (a:person)-[e:knows]->(b:person)\").select(\"e\")", + "python": "g.match('MATCH (a:person)-[e:knows]->(b:person)').select('e')" + } + ] + }, + { + "scenario": "g_match_personXknowsXweight_1X_person_selectXa_bX_byXnameX", + "traversals": [ + { + "original": "g.match(\"MATCH (a:person)-[:knows {weight: 1.0}]->(b:person)\").select(\"a\",\"b\").by(\"name\")", + "language": "g.match(\"MATCH (a:person)-[:knows {weight: 1.0}]->(b:person)\").select(\"a\", \"b\").by(\"name\")", + "canonical": "g.match(\"MATCH (a:person)-[:knows {weight: 1.0}]->(b:person)\").select(\"a\", \"b\").by(\"name\")", + "anonymized": "g.match(string0).select(string1, string2).by(string3)", + "dotnet": "g.Match(\"MATCH (a:person)-[:knows {weight: 1.0}]->(b:person)\").Select<object>(\"a\", \"b\").By(\"name\")", + "go": "g.Match(\"MATCH (a:person)-[:knows {weight: 1.0}]->(b:person)\").Select(\"a\", \"b\").By(\"name\")", + "groovy": "g.match(\"MATCH (a:person)-[:knows {weight: 1.0}]->(b:person)\").select(\"a\", \"b\").by(\"name\")", + "java": "g.match(\"MATCH (a:person)-[:knows {weight: 1.0}]->(b:person)\").select(\"a\", \"b\").by(\"name\")", + "javascript": "g.match(\"MATCH (a:person)-[:knows {weight: 1.0}]->(b:person)\").select(\"a\", \"b\").by(\"name\")", + "python": "g.match('MATCH (a:person)-[:knows {weight: 1.0}]->(b:person)').select('a', 'b').by('name')" + } + ] + }, + { + "scenario": "g_match_personXanyEdgeX_software_selectXa_bX_byXnameX", + "traversals": [ + { + "original": "g.match(\"MATCH (a:person)-[]->(b:software)\").select(\"a\",\"b\").by(\"name\")", + "language": "g.match(\"MATCH (a:person)-[]->(b:software)\").select(\"a\", \"b\").by(\"name\")", + "canonical": "g.match(\"MATCH (a:person)-[]->(b:software)\").select(\"a\", \"b\").by(\"name\")", + "anonymized": "g.match(string0).select(string1, string2).by(string3)", + "dotnet": "g.Match(\"MATCH (a:person)-[]->(b:software)\").Select<object>(\"a\", \"b\").By(\"name\")", + "go": "g.Match(\"MATCH (a:person)-[]->(b:software)\").Select(\"a\", \"b\").By(\"name\")", + "groovy": "g.match(\"MATCH (a:person)-[]->(b:software)\").select(\"a\", \"b\").by(\"name\")", + "java": "g.match(\"MATCH (a:person)-[]->(b:software)\").select(\"a\", \"b\").by(\"name\")", + "javascript": "g.match(\"MATCH (a:person)-[]->(b:software)\").select(\"a\", \"b\").by(\"name\")", + "python": "g.match('MATCH (a:person)-[]->(b:software)').select('a', 'b').by('name')" + } + ] + }, + { + "scenario": "g_match_personXknowsX_anyXcreatedX_software_selectXa_sX_byXnameX", + "traversals": [ + { + "original": "g.match(\"MATCH (a:person)-[:knows]->()-[:created]->(s:software)\").select(\"a\",\"s\").by(\"name\")", + "language": "g.match(\"MATCH (a:person)-[:knows]->()-[:created]->(s:software)\").select(\"a\", \"s\").by(\"name\")", + "canonical": "g.match(\"MATCH (a:person)-[:knows]->()-[:created]->(s:software)\").select(\"a\", \"s\").by(\"name\")", + "anonymized": "g.match(string0).select(string1, string2).by(string3)", + "dotnet": "g.Match(\"MATCH (a:person)-[:knows]->()-[:created]->(s:software)\").Select<object>(\"a\", \"s\").By(\"name\")", + "go": "g.Match(\"MATCH (a:person)-[:knows]->()-[:created]->(s:software)\").Select(\"a\", \"s\").By(\"name\")", + "groovy": "g.match(\"MATCH (a:person)-[:knows]->()-[:created]->(s:software)\").select(\"a\", \"s\").by(\"name\")", + "java": "g.match(\"MATCH (a:person)-[:knows]->()-[:created]->(s:software)\").select(\"a\", \"s\").by(\"name\")", + "javascript": "g.match(\"MATCH (a:person)-[:knows]->()-[:created]->(s:software)\").select(\"a\", \"s\").by(\"name\")", + "python": "g.match('MATCH (a:person)-[:knows]->()-[:created]->(s:software)').select('a', 's').by('name')" + } + ] + }, + { + "scenario": "g_match_personXage_paramX_knowsPerson_selectXp_fX_byXnameX_integer", + "traversals": [ + { + "original": "g.match('MATCH (p:person {age: $age})-[:knows]->(f:person)', [\"age\": 29]).select(\"p\",\"f\").by(\"name\")", + "language": "g.match('MATCH (p:person {age: $age})-[:knows]->(f:person)', [\"age\":29]).select(\"p\", \"f\").by(\"name\")", + "canonical": "g.match('MATCH (p:person {age: $age})-[:knows]->(f:person)', [\"age\":29]).select(\"p\", \"f\").by(\"name\")", + "anonymized": "g.match(string0, map0).select(string1, string2).by(string3)", + "dotnet": "g.Match(\"MATCH (p:person {age: $age})-[:knows]->(f:person)\", (IDictionary<object, object>) new Dictionary<object, object> {{ \"age\", 29 }}).Select<object>(\"p\", \"f\").By(\"name\")", + "go": "g.MatchWithParams(\"MATCH (p:person {age: $age})-[:knows]->(f:person)\", map[string]interface{}{\"age\": 29 }).Select(\"p\", \"f\").By(\"name\")", + "groovy": "g.match('MATCH (p:person {age: \\$age})-[:knows]->(f:person)', [\"age\":29]).select(\"p\", \"f\").by(\"name\")", + "java": "g.match(\"MATCH (p:person {age: $age})-[:knows]->(f:person)\", new LinkedHashMap<Object, Object>() {{ put(\"age\", 29); }}).select(\"p\", \"f\").by(\"name\")", + "javascript": "g.match(\"MATCH (p:person {age: $age})-[:knows]->(f:person)\", new Map([[\"age\", 29]])).select(\"p\", \"f\").by(\"name\")", + "python": "g.match('MATCH (p:person {age: $age})-[:knows]->(f:person)', { 'age': 29 }).select('p', 'f').by('name')" + } + ] + }, + { + "scenario": "g_match_noMatchPattern_emptyResult", + "traversals": [ + { + "original": "g.match(\"MATCH (a:software)-[:knows]->(b)\").select(\"a\",\"b\")", + "language": "g.match(\"MATCH (a:software)-[:knows]->(b)\").select(\"a\", \"b\")", + "canonical": "g.match(\"MATCH (a:software)-[:knows]->(b)\").select(\"a\", \"b\")", + "anonymized": "g.match(string0).select(string1, string2)", + "dotnet": "g.Match(\"MATCH (a:software)-[:knows]->(b)\").Select<object>(\"a\", \"b\")", + "go": "g.Match(\"MATCH (a:software)-[:knows]->(b)\").Select(\"a\", \"b\")", + "groovy": "g.match(\"MATCH (a:software)-[:knows]->(b)\").select(\"a\", \"b\")", + "java": "g.match(\"MATCH (a:software)-[:knows]->(b)\").select(\"a\", \"b\")", + "javascript": "g.match(\"MATCH (a:software)-[:knows]->(b)\").select(\"a\", \"b\")", + "python": "g.match('MATCH (a:software)-[:knows]->(b)').select('a', 'b')" + } + ] + }, + { + "scenario": "g_inject_match_midTraversal_noMatch_emptyResult", + "traversals": [ + { + "original": "g.inject(1).match(\"MATCH (a:software)-[:knows]->(b:person)\").select(\"a\",\"b\")", + "language": "g.inject(1).match(\"MATCH (a:software)-[:knows]->(b:person)\").select(\"a\", \"b\")", + "canonical": "g.inject(1).match(\"MATCH (a:software)-[:knows]->(b:person)\").select(\"a\", \"b\")", + "anonymized": "g.inject(number0).match(string0).select(string1, string2)", + "dotnet": "g.Inject<object>(1).Match(\"MATCH (a:software)-[:knows]->(b:person)\").Select<object>(\"a\", \"b\")", + "go": "g.Inject(1).Match(\"MATCH (a:software)-[:knows]->(b:person)\").Select(\"a\", \"b\")", Review Comment: This is arguably the wrong translation for go based on the current implementation. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
