Skip to content

match() with TinkerGQL#3437

Open
spmallette wants to merge 65 commits into
apache:masterfrom
spmallette:match
Open

match() with TinkerGQL#3437
spmallette wants to merge 65 commits into
apache:masterfrom
spmallette:match

Conversation

@spmallette

@spmallette spmallette commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

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:

gremlin> g.match("MATCH (a:person)-[:created]->(s:software {name: 'lop'}),
(b:person)-[:created]->(s)").
......1> where("a", neq("b")).
......2> select("a", "b").by("name")
==>[a:marko,b:josh]
==>[a:marko,b:peter]
==>[a:josh,b:marko]
==>[a:josh,b:peter]
==>[a:peter,b:marko]
==>[a:peter,b:josh]
gremlin> g.match("MATCH (a:person)-[e:knows {weight: 1.0}]->(b:person)").
......1> select("a", "e", "b").
......2> by("name").by("weight").by("name")
==>[a:marko,e:1.0,b:josh]

There definitely could be "more". Some important shortcomings I'd like to rectify for 4.0 would be:

  • OLAP support
  • Variable length path syntax
  • Step label pre-binding

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

@codecov-commenter

codecov-commenter commented Jun 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 56.25000% with 77 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.40%. Comparing base (a28cd1f) to head (9546834).
⚠️ Report is 43 commits behind head on master.

Files with missing lines Patch % Lines
...in/language/translator/DotNetTranslateVisitor.java 40.00% 18 Missing ⚠️
...remlin/language/translator/GoTranslateVisitor.java 30.76% 18 Missing ⚠️
...lin/tinkergraph/structure/AbstractTinkerGraph.java 38.09% 12 Missing and 1 partial ⚠️
...in/language/grammar/DefaultGremlinBaseVisitor.java 0.00% 5 Missing ⚠️
.../org/apache/tinkerpop/gremlin/structure/Graph.java 16.66% 5 Missing ⚠️
...in/language/translator/PythonTranslateVisitor.java 42.85% 3 Missing and 1 partial ⚠️
...ocess/traversal/step/map/DeclarativeMatchStep.java 80.95% 3 Missing and 1 partial ⚠️
...pop/gremlin/tinkergraph/structure/TinkerGraph.java 84.21% 2 Missing and 1 partial ⚠️
...kerpop/gremlin/process/traversal/dsl/graph/__.java 0.00% 2 Missing ⚠️
...va/org/apache/tinkerpop/gremlin/LoadGraphWith.java 80.00% 2 Missing ⚠️
... and 2 more
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

__.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)")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
g.match("MATCH (a:person)-[:knows]->(b:person)")
g.match("MATCH (a:person)-[:knows]->(b:person)").

@kenhuuu

kenhuuu commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

VOTE +1

@Cole-Greer Cole-Greer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread docs/src/dev/provider/gremlin-semantics.asciidoc Outdated
Comment thread gql-gremlin/README.md Outdated
Comment thread gql-gremlin/src/main/antlr4/GQL.g4
Comment thread gql-gremlin/src/main/antlr4/GQL.g4
Comment thread gremlin-language/src/main/antlr4/Gremlin.g4 Outdated
Comment thread gremlin-go/driver/cucumber/gremlin.go
@spmallette

Copy link
Copy Markdown
Contributor Author

@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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GraphTraversalSource should also use the permissive Match(args ...interface{}) * signature to remain aligned with GraphTraversal.

@Cole-Greer

Copy link
Copy Markdown
Contributor

Thanks @spmallette for all of the updates. Overall LGTM, just that one comment about GraphTraversalSource in Go.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants