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]

Reply via email to