Return Cypher query results as graph objects - #873
Merged
Conversation
paracycle
force-pushed
the
uk_add_cypher_query_engine
branch
from
June 23, 2026 21:16
2e6a202 to
bc2a231
Compare
paracycle
force-pushed
the
uk_query_object_results
branch
from
June 23, 2026 21:24
9394f29 to
e5bb1da
Compare
paracycle
force-pushed
the
uk_add_cypher_query_engine
branch
3 times, most recently
from
July 3, 2026 21:56
85bbbba to
cc553f6
Compare
paracycle
force-pushed
the
uk_query_object_results
branch
2 times, most recently
from
July 8, 2026 16:40
cc69ca7 to
d68b816
Compare
paracycle
force-pushed
the
uk_add_cypher_query_engine
branch
from
July 8, 2026 18:16
cc553f6 to
8714ea8
Compare
paracycle
force-pushed
the
uk_query_object_results
branch
2 times, most recently
from
July 8, 2026 20:05
51b4f05 to
2bb59e0
Compare
paracycle
force-pushed
the
uk_add_cypher_query_engine
branch
from
July 8, 2026 20:17
57c979c to
f99fc2c
Compare
paracycle
force-pushed
the
uk_query_object_results
branch
from
July 8, 2026 20:19
2bb59e0 to
1de091c
Compare
paracycle
force-pushed
the
uk_add_cypher_query_engine
branch
from
July 9, 2026 20:09
f99fc2c to
fcbbf98
Compare
paracycle
force-pushed
the
uk_query_object_results
branch
from
July 9, 2026 20:13
1de091c to
ae8fcbd
Compare
paracycle
force-pushed
the
uk_add_cypher_query_engine
branch
from
July 9, 2026 20:35
fcbbf98 to
0206dbe
Compare
paracycle
force-pushed
the
uk_query_object_results
branch
3 times, most recently
from
July 9, 2026 22:39
5fb40e7 to
933bf3d
Compare
vinistock
reviewed
Jul 15, 2026
Comment on lines
+162
to
+166
| void *query; | ||
| TypedData_Get_Struct(self, void *, &query_type, query); | ||
|
|
||
| void *graph; | ||
| TypedData_Get_Struct(graph_obj, void *, &graph_type, graph); |
Member
There was a problem hiding this comment.
Could we use rdxi_graph_from_handle here?
Member
Author
There was a problem hiding this comment.
rdxi_graph_from_handle is for handle objects (Declaration/Definition/Document) that store a back-reference to their parent Graph via HandleData.graph_obj. In Query#run, graph_obj is the Graph itself, not a handle, so it can't be used directly.
I added rdxi_graph_from_object in graph.h as the Graph-level equivalent, and used it in both render and run.
paracycle
force-pushed
the
uk_query_object_results
branch
from
July 16, 2026 20:18
933bf3d to
a22a461
Compare
vinistock
approved these changes
Jul 29, 2026
Morriar
reviewed
Jul 29, 2026
paracycle
force-pushed
the
uk_query_object_results
branch
2 times, most recently
from
July 29, 2026 17:18
fa58010 to
bdddecc
Compare
Add an object-returning `Rubydex::Query#run(graph)` alongside the string-returning `Query#render`. Where `render` formats rows into a table or JSON string, `run` returns an `Array<Hash>` whose values are real Ruby objects — including `Declaration`/`Definition`/`Document` handles for node columns, and Hashes for map projections — so callers can navigate the graph directly instead of re-parsing formatted text. Targets `cypher-parser` 0.8: - structured node results (`CypherValue::Node`) surfaced through the FFI as handle-buildable cells, plus `CypherValue::Map` -> Ruby Hash. - implement the new `GraphProvider::expand_in` for the cheap, exact reverse edges (a definition's document, a declaration's definitions), turning those incoming traversals from a whole-graph build into direct lookups; other edges fall back to the default reverse-adjacency build.
paracycle
force-pushed
the
uk_query_object_results
branch
from
July 29, 2026 17:24
bdddecc to
6865c54
Compare
paracycle
enabled auto-merge
July 29, 2026 17:28
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.
Goal
Build on the Cypher query engine from #868 so callers get matched graph nodes back as first-class objects, not as formatted text they have to re-parse or iterate over.
#868 added
Rubydex::Query#render(graph, format), which runs a query and returns atable/jsonstring — great for the CLI and humans, but a dead end for programmatic callers: to actually use a matched class or method you'd have to parse the formatted output, then look the node back up in the graph by name. This PR adds an object-returning sibling,Query#run(graph), that hands you the matched nodes directly.What changes
Rubydex::Query#render(graph, format = :table)— unchanged: rows → formattedString.Rubydex::Query#run(graph)— new: rows →Array<Hash>(T::Array[T::Hash[String, T::untyped]]), where the values are real Ruby objects:RETURN c) comes back as a liveRubydex::Declaration/Definition/Documenthandle;RETURN c.name,count(c)) come back as plain Ruby values (String,Integer,true/false,nil);RETURN c { .name, .kind }) comes back as a RubyHash, and a list comes back as a RubyArray(elements decoded recursively).So instead of:
you write:
The query language does the matching and filtering; you get the resulting nodes back ready to use, without writing your own traversal/iteration to turn names back into graph objects.
How it works
cypher-parser0.8.1: the generic executor carries node identity and structured values.CypherValuecoversNull/Bool/Int/Strscalars,Node { id, name, .. }for a bound graph node, andList/Mapcontainers (so map projections likec { .name, .kind }survive execution).GraphProvidergains:node_id(node)— encodes a node to an opaque id, so a bound node survives execution as an id rather than being flattened to text;expand_in(node, rel_type)— walks a relationship backwards (incoming edges), enabling queries like(:Definition)<-[:DEFINES]-(:Document).The crate stays dependency-free and rubydex-agnostic.
rubydex schema (
query::cypher::schema): implementsnode_idandNodeRef::decodeover the existingdecl:/def:/doc:<u64>id scheme, and implementsexpand_infor the reversible relationships (DEFINES,DECLARES) so incoming traversals resolve to the right nodes.FFI (
rubydex-sys): all Cypher-related code lives in a newcypher_api.rsmodule —CQueryResult,CParseResult,rdx_cypher_parse,rdx_cypher_query_free,rdx_cypher_schema, and the structured-result types. Onlyrdx_query_run(the formatted-string runner) remains ingraph_api.rs.rdx_query_run_rowsreturns aCRunRowswrapping aCRowsIteropaque iterator (following the existingDeclarationsIter/DocumentsIterpattern), walked viardx_rows_iter_{column_count,columns,len,next,free}.CCellis a tagged union (CCellTag:Null/Bool/Int/Str/Node/List/Map);Nodecells carry a category (declaration/definition/document) plus the entity id, andMapstores parallel key/value arrays. Acstring_rawhelper was added toutils.rsfor allocating owned C strings across the FFI boundary.Gem (
ext/rubydex/query.c, extending the file added in Query the in-memory graph with Cypher #868):Query#runwalks theCRowsIteriterator into anArray<Hash>, decoding each cell — node cells into the appropriateDeclaration/Definition/Documenthandle, lists intoArray, maps intoHash, scalars into plain values. The iterator walk is wrapped inrb_ensuresordx_rows_iter_freeruns even if Ruby raises during cell conversion. Bothrenderandrunuse a newrdxi_graph_from_objecthelper ingraph.hto extract the graph pointer from the Graph VALUE.Why this matters for both APIs
CypherValue::Node) and supports incoming-edge traversal (expand_in), so anyGraphProvider-backed consumer — Rust callers, the FFI layer, future language servers/tools — can get matched nodes back by id and resolve them to their own representation, rather than being limited to formatted strings.In short: the query does the matching; callers get the nodes, not a transcript of them.
Verification
cargo build/cargo testgreen; clippy clean (-D warnings);cargo fmt --checkclean.rake compilegreen;bundle exec ruby -Itest test/graph_test.rb→ 110 runs, 0 failures (includes new object-result, map-projection, aggregation, reverse-traversal, and label-disjunction tests).