match() with TinkerGQL#3437
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #3437 +/- ##
============================================
+ Coverage 76.35% 76.40% +0.04%
- Complexity 13424 13637 +213
============================================
Files 1012 1024 +12
Lines 60341 61584 +1243
Branches 7075 7197 +122
============================================
+ Hits 46076 47053 +977
- Misses 11548 11679 +131
- Partials 2717 2852 +135 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| __.as('creators').out('created').has('name', 'lop').as('projects'), <1> | ||
| __.as('projects').in('created').has('age', 29).as('cocreators')). <2> | ||
| select('creators','cocreators').by('name') <3> | ||
| g.match("MATCH (a:person)-[:knows]->(b:person)") |
There was a problem hiding this comment.
| g.match("MATCH (a:person)-[:knows]->(b:person)") | |
| g.match("MATCH (a:person)-[:knows]->(b:person)"). |
|
VOTE +1 |
Cole-Greer
left a comment
There was a problem hiding this comment.
Overall the core of this looks really good. Most of my comments are looking for small tweaks around the grammar or GLV translations. The new match step integrates really well into existing gremlin queries. It's spawning lots of ideas for future extensions and improvements.
|
@Cole-Greer i think i've covered all the comments. any last words? |
| // MatchWithParams starts the traversal with a declarative pattern match query with bound | ||
| // parameters. The query language defaults to "gql" and can be overridden with | ||
| // .With("queryLanguage", value). | ||
| func (gts *GraphTraversalSource) MatchWithParams(matchQuery string, params map[string]interface{}) *GraphTraversal { |
There was a problem hiding this comment.
GraphTraversalSource should also use the permissive Match(args ...interface{}) * signature to remain aligned with GraphTraversal.
|
Thanks @spmallette for all of the updates. Overall LGTM, just that one comment about VOTE +1 (pending that last little fix) |
Adds a minimal GQL MATCH grammar to tinkergraph-gremlin covering: - Anonymous nodes (), variable-only (n), labeled (:L), variable+label (n:L) - Directed edges -[var?:Label?]-> - Reverse directed edges <-[var?:Label?]- - Undirected edges -[var?:Label?]- - Multiple comma-separated path patterns in a single MATCH clause Also adds antlr4-maven-plugin configuration to generate a Java parser from GQL.g4, and GqlGrammarTest covering all supported pattern combinations. tinkerpop-70m.1 Assisted-by: Claude Code:claude-sonnet-4-6 [Gastown] [Kiro]
Introduce DeclarativeMatchStep<S> in gremlin-core/step/map/ as a
provider-replaceable placeholder for declarative pattern-matching queries.
- DeclarativeMatchStep extends AbstractStep<S, Optional> and implements
Configuring; its processNextStart() throws UnsupportedOperationException
until a provider strategy replaces it at execution time.
- The query language defaults to "gql" and can be changed via
.with("queryLanguage", value) (new WithOptions.queryLanguage constant).
- Add GraphTraversal.match(String) and match(String, Map<String,Object>)
overloads that build a DeclarativeMatchStep; both reuse Symbols.match.
- Mark the imperative GraphTraversal.match(Traversal...) method and
MatchStep class @deprecated in favour of the new declarative API.
tinkerpop-70m.7
Assisted-by: Claude Code:claude-sonnet-4-6 [Gastown] [Kiro]
…e names Bring TinkerGraphGqlPlanner and supporting GQL model classes onto master using the canonical rule names from the merged GQL.g4 (ti-70m.1): - QueryGraph.java: import package updated from tinkergraph.language.gql to tinkergraph.gql to match ANTLR plugin output package in master's pom.xml - QueryGraph.java: parser.matchStatement() → parser.matchClause() - QueryGraph.java: GQLParser.MatchStatementContext → GQLParser.MatchClauseContext - QueryGraph.java: ctx.patternList().pattern() → ctx.graphPattern().pathPattern() - QueryGraph.java: nodeContents/edgeContents → elementPatternFiller - QueryGraph.java: ctx.inEdge() → ctx.reverseDirectedEdge() - QueryGraph.java: ctx.outEdge() → ctx.directedEdge() - QueryGraph.java: variable().ID() → elementVariable().IDENTIFIER() - QueryGraph.java: label().ID() → labelSpec().labelName().IDENTIFIER() TinkerGraphGqlPlanner.java itself has no ANTLR grammar rule references (it works through the QueryGraph abstraction), so it is included unchanged from ti-70m.3. GqlMatchPlan carries the seedVariable/seedLabel fields added in ti-70m.3 so the executor has a self-contained plan. Also adds .runtime/** to RAT excludes in root pom.xml to prevent Gas Town runtime files from triggering license header violations. All 249 GQL-related tests pass (GqlGrammarTest, QueryGraphTest, TinkerGraphGqlPlannerTest). tinkerpop-70m.17 Assisted-by: Claude Code:claude-sonnet-4-6 [Gastown] [Kiro]
…matching tinkerpop-70m.4 Assisted-by: Claude Code:claude-sonnet-4-6 [Gastown] [Kiro]
…ser path binding Introduce TinkerGraphMatchStep<S> in tinkergraph-gremlin, extending DeclarativeMatchStep. - TinkerGraphMatchStep extends DeclarativeMatchStep and lazily initialises TinkerGraphGqlPlanner and TinkerGraphGqlExecutor on first processNextStart() call. - The GqlMatchPlan is compiled once via TinkerGraphGqlPlanner.plan() (internally cached) and reused across traversal resets. - For each incoming traverser, TinkerGraphGqlExecutor.execute() produces result rows. One output traverser (Optional.empty()) is emitted per row with each named variable binding (varName -> element) recorded as a labeled path entry so that downstream select() steps can retrieve bindings by name. Anonymous variables ($anon*) are filtered from path extension. - Returns TraverserRequirement.LABELED_PATH so traversers track path labels. - Throws UnsupportedOperationException if queryLanguage is non-null and not "gql". - Remove final modifier from DeclarativeMatchStep to allow provider subclassing. - Add TinkerGraphMatchStepTest covering empty match, single-node, single-edge, multi-row, multi-input-traverser, query-language rejection, and anonymous-variable exclusion scenarios (8 tests, all passing). tinkerpop-70m.5 Assisted-by: Claude Code:claude-sonnet-4-6 [Gastown] [Kiro]
Adds TinkerGraphDeclarativeMatchStrategy, a ProviderOptimizationStrategy that replaces DeclarativeMatchStep placeholders with executable TinkerGraphMatchStep instances during traversal optimization. Registers the strategy as a default for both TinkerGraph and TinkerTransactionGraph, following the same pattern as TinkerGraphStepStrategy. tinkerpop-70m.6 Assisted-by: Claude Code:claude-sonnet-4-6 [Gastown] [Kiro]
…in.g4 Add traversalSourceSpawnMethod_match with string and string+map alternatives. Add matching alternatives to traversalMethod_match alongside deprecated nestedTraversalList form. ANTLR parser regenerated and compiles cleanly. tinkerpop-70m.9 Assisted-by: Claude Code:claude-sonnet-4-6 [Gastown] [Kiro]
…tch() tinkerpop-70m.8 Assisted-by: Claude Code:claude-sonnet-4-6 [Gastown] [Kiro]
…TraversalSource tinkerpop-70m.11 Assisted-by: Claude Code:claude-sonnet-4-6 [Gastown] [Kiro]
Add GraphTraversalSource.match(gql_query, params=None) spawn method to the
gremlin-python DSL, enabling g.match('MATCH ...') pattern matching queries.
The method adds the step to traversal bytecode with the GQL query string and
optional params map, mirroring the Java GraphTraversalSource.match(String)
implementation.
Integration test verifies g.match('MATCH (p:person)-[e:knows]->(friend:person)')
.select('p','friend').by('name') returns marko/vadas and marko/josh pairs from
the modern graph. Unit test verifies bytecode generation for both the query-only
and query-with-params forms.
tinkerpop-70m.12
Assisted-by: Claude Code:claude-sonnet-4-6 [Gastown] [Kiro]
…ion test tinkerpop-70m.13 Assisted-by: Claude Code:claude-sonnet-4-6 [Gastown] [Kiro]
…remlin-dotnet DSL Add Match(string gqlQuery) and Match(string gqlQuery, IDictionary<string,object> parameters) overloads to GraphTraversal, GraphTraversalSource, and __ in gremlin-dotnet, mirroring the Java declarative GQL pattern-matching API added in 4.0.0. Deprecate the traversal-based Match(ITraversal[]) overloads with [Obsolete] to align with Java's @deprecated annotation. Add an integration test that executes g.Match("MATCH (p:person)-[e:knows]->(friend:person)") against the modern TinkerGraph and verifies marko's two knows relationships are returned. tinkerpop-70m.14 Assisted-by: Claude Code:claude-sonnet-4-6 [Gastown] [Kiro]
Add typed GQL-string match overloads to the gremlin-go DSL:
GraphTraversalSource:
- Match(gqlQuery string) — spawn step (g.Match("MATCH ..."))
- MatchWithParams(gqlQuery string, params map[string]interface{})
GraphTraversal:
- MatchGql(gqlQuery string) — mid-traversal step
- MatchGqlWithParams(gqlQuery string, params map[string]interface{})
Both methods emit a "match" step in GremlinLang, matching the Java API.
The existing Match(args ...interface{}) is marked deprecated.
Includes:
- Five GremlinLang unit test cases verifying the Gremlin string representation
- One integration test (TestMatchGqlIntegration) that executes
g.Match("MATCH (p:person)-[e:knows]->(friend:person)").Select("p","friend")
against the TinkerGraph modern graph and verifies the two expected
person-knows-person result rows (marko→vadas, marko→josh)
tinkerpop-70m.15
Assisted-by: Claude Code:claude-sonnet-4-6 [Gastown] [Kiro]
Update TraversalMethodVisitor and TraversalSourceSpawnMethodVisitor to handle the new labeled grammar alternatives for declarative match(): - visitTraversalMethod_match_traversal (renamed from match, for deprecated traversal-based overload) - visitTraversalMethod_match_string - visitTraversalMethod_match_string_map - visitTraversalSourceSpawnMethod_match_string - visitTraversalSourceSpawnMethod_match_string_map Update DefaultGremlinBaseVisitor with notImplemented stubs for all five new visitor methods. Update DotNetTranslateVisitor to replace the old visitTraversalMethod_match with the three labeled variants and add C# translation for the two new spawn method variants. tinkerpop-70m.10 Assisted-by: Claude Code:claude-sonnet-4-6 [Gastown] [Kiro]
DeclarativeMatchVerificationStrategy (ti-70m.8) rejects match() as terminal
since TinkerGraphMatchStep extends DeclarativeMatchStep the instanceof check
fires even after InjectMatchStrategy replaces the placeholder step.
Update four tests to follow select() after match():
- testEmptyGraphProducesNoResults: select("n") — empty graph still yields []
- testNoMatchingEdgeProducesNoResults: select("a") — still yields []
- testMultipleInputTraversersProduceIndependentResults: select("a") — still 2 results
- testUnsupportedQueryLanguageThrows: select("n") lets verification pass so
TinkerGraphMatchStep.processNextStart() can throw UnsupportedOperationException
tinkerpop-u5d
Assisted-by: Claude Code:claude-sonnet-4-6 [Gastown] [Kiro]
Fixes and improvements from code review of the ti-70m declarative GQL match work:
- BUG: detect disconnected MATCH patterns in TinkerGraphGqlPlanner and throw
IllegalArgumentException with a clear message naming the unconnected nodes
- BUG: TinkerGraphMatchStep now throws UnsupportedOperationException when
non-empty query parameters are passed (not yet supported)
- BUG: fix JS DotNet translator — add visitTraversalMethod_match_string,
visitTraversalMethod_match_string_map, and source-spawn equivalents so
Match("...") is emitted correctly instead of Match<object>("...")
- BUG: add clone() override to TinkerGraphMatchStep so outputQueue and plan
are reset per clone rather than shared
- OPT: make TinkerGraphGqlPlanner and TinkerGraphGqlExecutor per-graph
singletons (lazy double-checked locking on TinkerGraph and
TinkerTransactionGraph) so the plan cache is shared across traversals;
TinkerGraphDeclarativeMatchStrategy injects these singletons into each step
- REFACTOR: change TinkerGraphGqlPlanner/Executor constructor arg from
TinkerGraph to AbstractTinkerGraph so both graph implementations are supported
- MINOR: add @deprecated to __.match(Traversal...) to match the annotation
already on GraphTraversal.match(Traversal...) and the grammar comment
- TEST: add TinkerGraphDeclarativeMatchStrategyTest (6 integration tests
exercising the full strategy path end-to-end)
- TEST: add TinkerGraphMatchStepTest cases for explicit gql language and
params-throw behaviour
- TEST: add TinkerGraphGqlPlannerTest cases for disconnected pattern detection
- TEST: add DeclarativeMatchVerificationStrategyTest cases for sub-traversal
rejection, pass-with-select-in-subtree, and error message quality
- TEST: add TinkerGraphDeclarativeMatchStrategyTest case verifying that
TinkerGraphMatchStep (subclass) as terminal still throws VerificationException
- CORPUS: add 4 translations.json entries for match(String) traversal-method
and source-spawn forms (single-node and edge-pattern variants)
(tinkerpop-70m,tinkerpop-e0y, tinkerpop-eno, tinkerpop-382, tinkerpop-qb3, tinkerpop-6o1, tinkerpop-7sm, tinkerpop-lfr, tinkerpop-5ug, tinkerpop-u3r)
Assisted-by: Claude Code:claude-sonnet-4-6 [Gastown] [Kiro]
Wasn't triggering properly because the step implementation wasn't implemented to throw in dummy traversers to kick start the traversal. tinkerpop-70m Assisted-by: Claude Code:claude-sonnet-4-6 [Gastown] [Kiro]
tinkerpop-70m Assisted-by: Claude Code:claude-sonnet-4-6 [Gastown] [Kiro]
…et graph providers own their language DeclarativeMatchStep previously defaulted queryLanguage to "gql", coupling the framework to a specific query language. The better design is for the step to carry queryLanguage=null when the user has not specified one, meaning "let the graph decide." A provider accepts null as its native language and also accepts an explicit language identifier it understands, rejecting anything else. - Remove DEFAULT_QUERY_LANGUAGE constant from DeclarativeMatchStep; default queryLanguage to null in all constructors - Update GraphTraversalSource.match() and GraphTraversal.match() to not hardcode a language; update Javadocs to reflect the null-means-native design - Add SUPPORTED_QUERY_LANGUAGE = "gql" constant to TinkerGraphMatchStep (the provider concern, not a framework concern); update language check to accept null or "gql", reject anything else with a clear error message - Update test comment that referenced DEFAULT_QUERY_LANGUAGE (tinkerpop-hlx, tinkerpop-79j, tinkerpop-z71) Assisted-by: Claude Code:claude-sonnet-4-6 [Gastown] [Kiro]
The gqlQuery/gql_query variable name was too GQL-specific for a framework-level API that is language-agnostic. Renamed to matchQuery (match_query in Python) in DeclarativeMatchStep, GraphTraversal, GraphTraversalSource, and the corresponding GLV implementations in Java, Python, Go, JavaScript, and .NET. Also updated non-GQL-specific test descriptions in Go and JavaScript to remove the GQL reference; GQL-specific integration tests (Python, Go) were left unchanged. tinkerpop-70m Assisted-by: Claude Code:claude-sonnet-4-6 [Gastown] [Kiro]
tinkerpop-70m Assisted-by: Claude Code:claude-sonnet-4-6 [Gastown] [Kiro]
…ables in GQL MATCH When a node variable appeared in multiple comma-separated path patterns, resolveNode() returned the existing QueryVertex without merging predicates from the later occurrence — silently dropping them. Additionally, a later labeled occurrence after an earlier unlabeled one was incorrectly rejected as a label conflict (null vs "Label"). QueryVertex gains a package-private merge() method that accumulates predicates and refines a null label. resolveNode() now calls merge() on the existing vertex and only throws for genuine conflicts where both the existing and new label are non-null and different. tinkerpop-xew Assisted-by: Claude Code:claude-sonnet-4-6 [Copilot]
…icate evaluation PropertyPredicate.test() called element.property(key) (singular), which throws multiplePropertiesExistForProvidedKey when a vertex has list-cardinality for that key. This aborted query execution instead of evaluating correctly. Switched to element.properties(key) (plural iterator) and return true on the first matching value, aligning with HasContainer semantics. A missing property (empty iterator) still matches only a null predicate, preserving the existing null-absent behaviour. tinkerpop-05o Assisted-by: Claude Code:claude-sonnet-4-6 [Copilot]
…arams
Four cases surfaced in review:
- null params: match("...", null) must behave identically to match("..."),
verified through TinkerGraphMatchStepTest
- queryLanguage case sensitivity: with("queryLanguage", "GQL") must throw
UnsupportedOperationException; the accepted value is lowercase "gql" only
- multi-property with null value: a vertex whose list-cardinality property
contains an explicit null must match a {key: null} predicate under
any-value-matches semantics (requires allowNullPropertyValues=true graph)
- param binding with multi-property: a $param reference predicate must also
use any-value-matches semantics when the property has list cardinality
Assisted-by: Claude Code:claude-sonnet-4-6 [Copilot]
Covered more corner cases and clarified certain limitations and concepts better.
The declarative match(String) step previously emitted Optional.empty as its
traverser value, forcing callers to always follow match() with select(). This
diverged from the original traversal-based match() which returned all variable
bindings as a Map<String,Object>, and caused three concrete problems:
- match() alone produced ==>Optional.empty rather than actual bindings
- match().where("a", neq("b")) was broken; WherePredicateStep cannot resolve
named variables from Optional.empty
- Optional is not a first-class serializable type in the GLV stack, creating
complications for language variants
Fix: bindRow() now builds a LinkedHashMap of all named variable bindings and
sets it as the traverser value. Path labels are still recorded so select()
continues to work unchanged. match() is now valid as a terminal step.
DeclarativeMatchVerificationStrategy, which enforced the select() requirement,
is removed entirely.
(tinkerpop-1ak)
Assisted-by: Claude Code:claude-sonnet-4-6
The mock strategy in match-test.js was setting traversal.results but toList() reads from traversal._resultsStream (an AsyncGenerator). The test always returned 0 results. Fix by setting _resultsStream to an async generator that yields the expected Traversers. Assisted-by: Claude Code:claude-sonnet-4-6
…and Double-d suffix literals These five types were accepted by the GQL grammar and parsed correctly but had no execution-level tests verifying that a vertex property stored with the corresponding Java type is actually matched by the predicate evaluator.
… module) Proposes extracting the TinkerGraph GQL engine into a new optional gql-gremlin module so any TinkerPop provider can adopt match() without building a grammar, planner, and executor from scratch. Includes interface specs for GraphStatistics, IndexAccess, GqlPlanner, and GqlExecutor, a module dependency diagram, and a TinkerGraph migration outline.
…aces with Graph default methods Replace the separate GraphStatistics and IndexAccess interfaces with default methods directly on Graph (countVerticesByLabel, countEdgesByLabel, index()) and a Graph.Index nested interface following the Graph.Variables / getServiceRegistry() pattern. DefaultGqlPlanner and DefaultGqlExecutor call these methods unconditionally — no instanceof checks or adapters needed. TinkerGraph overrides the three methods on AbstractTinkerGraph; TinkerGraphGqlPlanner and TinkerGraphGqlExecutor are no longer needed and are removed from the architecture. The open question about interface placement is resolved.
…ized GQL execution engine - Add new gql-gremlin Maven module containing the GQL execution engine that was previously embedded in tinkergraph-gremlin - Move GQL classes to org.apache.tinkerpop.gremlin.gql package: QueryGraph, QueryVertex, QueryEdge, PropertyPredicate, ExtensionStep, GqlMatchPlan, GqlPlanner (interface), GqlExecutor (interface), DefaultGqlPlanner (refactored from TinkerGraphGqlPlanner), DefaultGqlExecutor (refactored from TinkerGraphGqlExecutor), GqlMatchStep (refactored from TinkerGraphMatchStep), GqlDeclarativeMatchStrategy (new per-instance strategy factory) - Add Graph.countVerticesByLabel() and Graph.countEdgesByLabel() as default methods returning Long.MAX_VALUE (no information) - Add Graph.Index nested interface with queryVertexIndex() and countVertexIndex() (following Graph.Variables pattern); accessed via graph.index() default method - Override Graph.Index in AbstractTinkerGraph via TinkerGraphIndex inner class that consults TinkerIndexHelper - Override countVerticesByLabel/countEdgesByLabel in TinkerGraph (O(1) via AtomicInteger label-count maps) and TinkerTransactionGraph (scan) - Refactor DefaultGqlPlanner/DefaultGqlExecutor to call graph.index() and countVerticesByLabel/countEdgesByLabel directly; no instanceof checks needed - Simplify TinkerGraphDeclarativeMatchStrategy to use GqlMatchStep and fetch DefaultGqlPlanner/DefaultGqlExecutor singletons from AbstractTinkerGraph - Move gqlPlanner/gqlExecutor singleton fields (with lazy double-checked locking) to AbstractTinkerGraph; clear() resets them - Update tinkergraph-gremlin/pom.xml to depend on gql-gremlin instead of direct antlr4/caffeine dependencies; remove ANTLR plugin execution - Add gql-gremlin module to parent pom.xml - Rename/update all tests to reflect new class and package names - Delete Proposal 10 document and remove its row from future/index.asciidoc
- Rename the dialect 'TinkerGQL' throughout documentation to reflect that gql-gremlin is now a first-class, provider-usable module rather than a TinkerGraph-only feature. - the-traversal.asciidoc: update match-step intro to acknowledge gql-gremlin and TinkerGQL; add full [[tinkergql]] subsection (==== level) covering supported syntax, variable reuse, property filters, parameterized queries, Graph.Index, and unsupported features. - implementations-tinkergraph.asciidoc: replace 288-line [[tinkergraph-gql]] section with a concise stub that links to <<tinkergql>> and retains a TinkerGraph-specific Index Integration subsection. - provider/index.asciidoc: add [[tinkerpop-providers-tinkergql]] section covering Maven dependency, per-instance and global-cache wiring patterns, label-count performance hints, and Graph.Index integration. - upgrade/release-4.x.x.asciidoc: replace 'TinkerPop does not offer a general purpose processing engine' with accurate description of gql-gremlin and TinkerGQL; update provider match() section to reference GqlMatchStep / GqlDeclarativeMatchStrategy. - CHANGELOG.asciidoc: replace single vague GQL entry with three precise entries covering gql-gremlin module, new Graph interface methods, and TinkerGraph integration. - gql-gremlin/README.md: new file describing the module, TinkerGQL dialect, quick start, Maven dependency, wiring patterns, and optional performance hint overrides.
…hStrategy The per-graph-instance caching of DefaultGqlPlanner and DefaultGqlExecutor previously lived on AbstractTinkerGraph (as getGqlPlanner()/getGqlExecutor() lazy-init fields) and was accessed via an instanceof cast in TinkerGraphDeclarativeMatchStrategy. Nothing about that caching was TinkerGraph-specific — it applied equally to any Graph implementation. Move the cache into GqlDeclarativeMatchStrategy itself as a static ConcurrentHashMap<Graph, PlannerExecutorPair> so that: - The strategy becomes a true no-arg singleton (instance()) matching the pattern used by every other TinkerPop strategy. - Any provider that registers GqlDeclarativeMatchStrategy.instance() in their GlobalCache gets per-graph parse-cache sharing for free, with no extra implementation required. - Providers call GqlDeclarativeMatchStrategy.evict(graph) from close() to release the cache entry; AbstractTinkerGraph.close() already does this. Changes: - GqlDeclarativeMatchStrategy: rewritten as singleton + static cache + evict(Graph); create(GqlPlanner, GqlExecutor) retained for custom impls. - TinkerGraphDeclarativeMatchStrategy: deleted. - AbstractTinkerGraph: removed gqlPlanner/gqlExecutor fields, getGqlPlanner(), getGqlExecutor(), and their clear() nulls; added GqlDeclarativeMatchStrategy.evict(this) to close(). - TinkerGraph, TinkerTransactionGraph: register GqlDeclarativeMatchStrategy .instance() instead of TinkerGraphDeclarativeMatchStrategy.instance(). - GqlMatchStep: updated javadoc and error message to remove TinkerGraph references. - TinkerGraphDeclarativeMatchStrategyTest renamed to GqlDeclarativeMatchStrategyTest; testSharedPlanCacheAcrossTraversals replaced with a behavioral assertion; new testEvictAndReuseDoesNotCorrupt test verifies the evict() / re-cache path.
…remlin refactor - Correct provider guide registration pattern: use GqlDeclarativeMatchStrategy.instance() in static initializer (not the now-removed create(graph) overload); add evict(this) to close(); reorder optional sections so Graph method hooks come before custom engine guidance - Clarify Optional sections as "strongly recommended" with explicit consequences of omission: full vertex scans and unoptimized join ordering without countVerticesByLabel/countEdgesByLabel and Graph.Index overrides - Add asymmetry guidance: GqlExecutor is the practical customization point; GqlPlanner replacement is rarely warranted given the cost of constructing GqlMatchPlan directly - Fix stale TinkerGraphDeclarativeMatchStrategy reference in implementations-tinkergraph.asciidoc - Fix stale GqlDeclarativeMatchStrategy.create(graph) call in upgrade notes; describe actual singleton caching behaviour - Correct false claim in gremlin-semantics.asciidoc that TinkerPop provides no general-purpose execution engine for match(String) - Update GqlPlanner and GqlExecutor javadoc to explain when each interface is worth implementing
…uage-variant smoke tests - Add MatchString.feature with @TinkerGQL @StepClassMap @StepMatch covering 10 scenarios: single-node, edge pattern, multi-hop, reverse edge, undirected edge, literal property filter, parameterized query, multi-pattern join, terminal binding map, and mid-traversal inject form. All language variants (Python, JS, Go, .NET) now get match(String) coverage through the Gherkin suite automatically. - Document @TinkerGQL tag in for-committers.asciidoc with opt-out guidance for providers that do not support match(String) - Delete gremlin-dotnet DeclarativeMatchTests.cs and gremlin-go match_step_test.go — both were single smoke tests now superseded by the Gherkin suite - Retain GqlMatchStepTest.java (language hint rejection, null literal, missing param, anonymous variable exclusion, mutation visibility) and GqlDeclarativeMatchStrategyTest.java (cache lifecycle and eviction) as they cover implementation behavior with no Gherkin equivalent
Add 10 new scenarios to MatchString.feature covering syntax gaps: anonymous nodes, integer/null/multi-predicate property filters, named edge variables, edge property filters, unlabeled edges, anonymous intermediate nodes, integer parameter references, and empty results. Fix build failures where @TinkerGQL scenarios were attempted in OLAP runners and against TinkerShuffleGraph: - Add `not @TinkerGQL` to World.GRAPHCOMPUTER_TAG_FILTER; match(String) is OLTP-only and GqlDeclarativeMatchStrategy already exits early on graph-computer traversals - Register TinkerGraph strategies for TinkerShuffleGraph, which otherwise falls back to the bare Graph.class strategy set via GlobalCache and does not inherit GqlDeclarativeMatchStrategy Document the OLAP limitation in the TinkerGQL unsupported-features table and in GqlDeclarativeMatchStrategy javadoc.
…, .NET, and Go
- PythonTranslateVisitor: use double quotes when string literal contains
single quotes, fixing SyntaxError for GQL inline filters like
{name: 'marko'} inside match() queries
- DotNetTranslateVisitor: change match_string_map cast target from
IDictionary<string,object> to IDictionary<object,object> to match
the .NET API signatures in GraphTraversalSource, GraphTraversal, and __
- GoTranslateVisitor: add visitTraversalSourceSpawnMethod_match_string_map
and visitTraversalMethod_match_string_map overrides that emit
MatchWithParams/MatchGqlWithParams with map[string]interface{} params
- Apply same fixes to TypeScript counterparts in gremlin-js
- Update .NET API signatures (GraphTraversalSource, GraphTraversal, __)
to accept IDictionary<object,object> for parameterized match
- Regenerate gremlin.py, Gremlin.cs, gremlin.go, and translations.json
Assisted-by: Claude Code:claude-sonnet-4-6
HadoopGraph has no match execution engine so all @TinkerGQL Gherkin scenarios throw UnsupportedOperationException. Add the same exclusion that GRAPHCOMPUTER_TAG_FILTER already applies to the Spark runner. Assisted-by: Claude Code:claude-sonnet-4-6
…-match Gherkin scenarios Add grammar visitor and GremlinLang unit tests verifying that match(String) and match(String, Map) correctly serialize to and parse from gremlin-lang strings (gaps apache#9). The params map is embedded inline in the gremlin string by GremlinLang.asString(Map) and reconstructed by the grammar visitor on the server side — no dedicated GraphBinary serializer is required. Add two new Gherkin scenarios (gaps apache#11 and apache#12): - g_inject_match_midTraversal_noMatch_emptyResult: verifies mid-traversal match() returns empty results when the pattern has no matches, exercising the lazy row-iterator path in GqlMatchStep with zero rows. - g_match_cyclicPattern_...: verifies a cyclic query graph (triangle: person-knows->person-created->software<-created-person) which exercises the back-edge join constraint in DefaultGqlExecutor.extend(). All GLV translation maps and translations.json updated with the two new scenario entries. Assisted-by: Claude Code:claude-sonnet-4-6
…ch()
Adds a pre-binding guard to GqlMatchStep.processNextStart() that throws
UnsupportedOperationException when an incoming traverser's path already
contains a label that matches a pattern variable name (e.g.
g.V(1).as("a").match("MATCH (a:person)-[:knows]->(b:person)")).
The guard reserves the semantic space cleanly so that when path-label
pre-binding lands it replaces a well-tested throw with binding logic
rather than silently changing undefined behaviour.
Tests added in tinkergraph-gremlin/GqlMatchStepTest cover:
- overlap on a vertex pattern variable throws
- overlap on an edge pattern variable throws
- disjoint step labels and pattern variables do not throw
Assisted-by: Claude Code:claude-sonnet-4-6
- Extract GqlMatchPlan.ANON_VAR_PREFIX constant to replace the raw "$anon" string literal scattered across DefaultGqlPlanner and GqlMatchStep - Add upgrade guide NOTE that match(Traversal...) is deprecated as of 4.0.0 in favour of match(String) - Clarify mid-traversal match() semantics in reference docs: the full graph is scanned independently per incoming traverser, not joined on the traverser's current element - Consolidate the path-label guard tests into the existing GqlMatchStepTest in ...step.map, removing the duplicate top-level class; guard tests now use a try-with-resources modern graph Assisted-by: Claude Code:claude-sonnet-4-6
Java, .NET, Go, and JS already carried deprecation markers on the traversal-based match() overload; Python was the only GLV missing one. When match() is called with non-string arguments on GraphTraversal, a DeprecationWarning is now issued directing users to match(str). Assisted-by: Claude Code:claude-sonnet-4-6
…d edge variable + filter
Two gaps in MatchString.feature:
- Multi-pattern with a bridging variable: verifies that comma-separated
patterns joined on a middle variable (b) produce the correct join result
rather than a Cartesian product.
- Edge variable combined with edge property filter: verifies that [e:label
{key: value}] correctly binds the edge variable while applying the
predicate — the only code path where both are active simultaneously in
DefaultGqlExecutor.
Assisted-by: Claude Code:claude-sonnet-4-6
…tion
- Reject malformed edge-arrow syntax in GQL (stray characters like '>'
were silently swallowed by ANTLR's default lexer error recovery);
added a throwing error listener to both lexer and parser that surfaces
position info matching gremlin-lang's error format
- Add QueryGraphTest unit tests covering all four bad-arrow variants
from the review; add hamcrest test dep to gql-gremlin pom
- Tighten match_string_map grammar to genericMapLiteral (was
genericMapArgument, which also accepted variable references that
every translator silently ignored); update TraversalMethodVisitor,
TraversalSourceSpawnMethodVisitor, and all GLV translators accordingly
- Consolidate gremlin-go Match variants: remove MatchGql and
MatchGqlWithParams, un-deprecate and unify under Match(args
...interface{}); add missing visitTraversalMethod_match_string and
visitTraversalSourceSpawnMethod_match_string handlers to Go translators
(Java and TS); fix stale MatchGql/MatchGqlWithParams references in
gremlinlang_test.go
- Add GremlinTranslatorTest cases for Go and .NET translation of
g.match(string) and g.inject(n).match(string)
- Add MatchString.feature scenario demonstrating path-history variable
access via concat() post-match; wire up all GLV glue files
…ql-gremlin QueryGraphTest and GqlGrammarTest had no TinkerGraph dependencies — they tested pure MATCH parsing logic that lives in gql-gremlin. Moving them alongside the code they test. The existing four error-message-format tests are merged into the comprehensive QueryGraphTest that was living in the wrong module.
…alSource in Go
Aligns GraphTraversalSource.Match() with the permissive Match(args ...interface{})
signature already used on GraphTraversal, removing the separate MatchWithParams method.
Updates all call sites: Go cucumber glue, gremlinlang tests, Java/JS GoTranslateVisitor,
and translations.json.
Provided support for
match()with support for a subset of GQL called TinkerGQL. Given the size of this PR it's hard to really call out everything here in the description, so it is probably best to rely on the asciidoc documentation for an explanation of what is in here. You can also refer back to:There definitely could be "more". Some important shortcomings I'd like to rectify for 4.0 would be:
I think however there's good enough support here now and the feature built out enough to be a good win. Adding more would just make an already impenetrable PR that much harder for reviewers to consider.
Note that benchmarks so far show some favorable cases where GQL is a better choice over imperative Gremlin. GQL's planner pays for itself the moment there is a selective endpoint in the pattern that a left-to-right imperative traversal would visit late. For patterns with no structural advantage (simple lookups, full scans), GQL carries measurable but bounded overhead (~28–37%) from its execution infrastructure. There is probably yet another body of work here to optimize based on the benchmark numbers, but that too i think is worth saving for another day as it has a clear use case and offers more than just an example for providers to follow.
VOTE +1