feat(jdbc): single edge→target JOIN for unbounded g.V().out().has() (stacked on #172) - #173
Open
seanbarzilay wants to merge 1 commit into
Open
seanbarzilay wants to merge 1 commit into
seanbarzilay wants to merge 1 commit into
Conversation
…et JOIN Builds on the unbounded-adjacency rewrite: when a search controller can honour an unbounded-source query, replace the whole hop with a single start step that issues one SearchVertexQuery with no source-id bound, so JDBC resolves it as select v.* from edges e join <target> v on e.<endpoint> = v.id where <target has()> instead of an edge scan + a separate deferred target fetch. DB-side filtering, one round-trip, only matching rows returned. Mechanism: - SearchVertexQuery.allSources: "match all sources" (no id bound), distinct from an empty vertex list (which aborts = "none"). - RowEdgeSchema.getJoinSearch / RowController fallback honour allSources (edge-label predicates only, never abort). - SupportsUnboundedAdjacency marks RowController as allSources-capable. The strategy uses the JOIN start step only when a controller advertises it; every other backend keeps the backend-agnostic g.E().<adjacency>() rewrite -- no ES regression. - UniGraphUnboundedAdjacencyStep: start step, implements Orderable / ReceivesPredicates Holder / PropertyFetcher so order/limit/has()/property strategies fold into it exactly as they do for UniGraphVertexStep. Handles the JOIN result and a deferred fallback (inner edge schemas, where searchJoin declines) that hydrates+filters neighbours, preserving duplicate ids (both()). Two pre-existing latent bugs surfaced and fixed (both only reachable once an adjacency query can be the FIRST query in a traversal, which this step makes possible): - RowController.search(SearchVertexQuery) never flushed the pending write buffer before searchJoin's direct fetch; a first-query join read stale data. Now flushes like the generic search() path. - (worked around) UniGraphPropertiesStrategy.GroupCountStep branch dereferences propertyFetchers without the null-guard the sibling branches have; making the step a PropertyFetcher keeps it non-null. Verified: JdbcUnboundedAdjacencyTest (11) incl. single-join-not-two-queries and multi-hop chaining; JdbcJoinPushdownTest (11) and JdbcAdjacencyFilterTest (9) unchanged. Full feature+structure suites run with the flag ON produce a failing set byte-identical to flag-off and to master (65 failures, same names) -- zero regressions. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #172 (
feat/unbounded-adjacency-scan) — review/merge that first; this PR's diff is only the delta.What
#172 eliminates the source-vertex scan by rewriting unbounded
g.V().out(L).has(k,v)tog.E().hasLabel(L).inV().has(k,v)— an edge scan plus a separate deferred target fetch (two query phases). This PR collapses that into one SQL JOIN when the backend supports it:DB-side filtering, a single round-trip, and only matching rows returned/materialized (vs. reading every edge into the app first).
How
SearchVertexQuery.allSources— "match all sources", i.e. no source-id bound. Distinct from an empty vertex list, which aborts (= match none).RowEdgeSchema.getJoinSearch/RowControllerfallback honourallSources(edge-label predicates only, never abort), reusing the perf(jdbc): adjacency join push-down for vertex-return hops (out/in/both + filter/order/limit) #167 join push-down.SupportsUnboundedAdjacencymarksRowControllerasallSources-capable. The strategy swaps in the JOIN start step only when a controller advertises it; every other backend keeps feat(core): skip source-vertex scan for unbounded g.V().out()/in()/both() via edge-scan rewrite #172's backend-agnosticg.E().<adjacency>()rewrite — no ES regression.UniGraphUnboundedAdjacencyStep— a start step implementingOrderable/ReceivesPredicatesHolder/PropertyFetcher, so the order/limit/has()/property strategies fold into it exactly as they do forUniGraphVertexStep. Handles both the JOIN result and a deferred fallback (inner edge schemas, wheresearchJoindeclines) that hydrates+filters neighbours and preserves duplicate ids (both()).Two latent bugs surfaced + fixed
Both were only reachable once an adjacency query can be the first query in a traversal — which this start step makes possible:
RowController.search(SearchVertexQuery)never flushed the write buffer beforesearchJoin's direct fetch (only the genericsearch()flushes). A first-query join read stale data — this caused 56 feature-suite failures until fixed. Now flushes up front.UniGraphPropertiesStrategyGroupCountStepbranch dereferencespropertyFetcherswithout the null-guard its sibling branches have. Worked around by making the step aPropertyFetcher(so the lookup is non-null); the unguarded line is noted for a follow-up hardening.Testing
JdbcUnboundedAdjacencyTest(11): single-join-not-two-queries, multi-hop chaining from the join step,out/in/both,has()push-down, guards, flag gate.JdbcJoinPushdownTest(11),JdbcAdjacencyFilterTest(9): unchanged.Follow-ups
UniGraphPropertiesStrategyGroupCountStep branch (independent latent bug).outE()/inE()/bothE()and an ESallSourcespath.🤖 Generated with Claude Code