Skip to content

Commit e5bb1da

Browse files
committed
Return Cypher query results as objects from Query#run
Add an object-returning `Rubydex::Query#run(graph)` alongside the existing 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 `Rubydex::Declaration`/`Definition`/`Document` handles for node columns — so callers can navigate the graph directly instead of re-parsing formatted text. - cypher-parser 0.4: add `GraphProvider#node_id` and a `CypherValue::Node { id, label, name }` variant carrying an opaque node id. - rubydex schema: implement `node_id` and `NodeRef::decode` over the `decl:/def:/doc:<u64>` id scheme. - FFI: add `rdx_query_run_rows`/`rdx_result_set_free` exposing a structured `CResultSet`/`CCell` result instead of a preformatted string. - Gem: `Query#run` builds `Array<Hash>`, decoding node cells back into the appropriate Declaration/Definition/Document handles.
1 parent bc2a231 commit e5bb1da

8 files changed

Lines changed: 460 additions & 30 deletions

File tree

ext/rubydex/graph.c

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "graph.h"
22
#include "declaration.h"
3+
#include "definition.h"
34
#include "diagnostic.h"
45
#include "document.h"
56
#include "location.h"
@@ -847,6 +848,84 @@ static VALUE rdxr_query_render(int argc, VALUE *argv, VALUE self) {
847848
return output;
848849
}
849850

851+
// Converts a structured result cell into a Ruby value. Node cells become real graph handles
852+
// (Declaration / Definition / Document) built against `graph_obj`; lists recurse.
853+
static VALUE cypher_cell_to_value(VALUE graph_obj, const struct CCell *cell) {
854+
switch (cell->tag) {
855+
case CCellTag_Null:
856+
return Qnil;
857+
case CCellTag_Bool:
858+
return cell->payload.bool_val ? Qtrue : Qfalse;
859+
case CCellTag_Int:
860+
return LL2NUM(cell->payload.int_val);
861+
case CCellTag_Str:
862+
return cell->payload.str_val == NULL ? Qnil : rb_utf8_str_new_cstr(cell->payload.str_val);
863+
case CCellTag_List: {
864+
VALUE array = rb_ary_new_capa((long)cell->payload.list.len);
865+
for (size_t i = 0; i < cell->payload.list.len; i++) {
866+
rb_ary_push(array, cypher_cell_to_value(graph_obj, &cell->payload.list.items[i]));
867+
}
868+
return array;
869+
}
870+
case CCellTag_Node: {
871+
VALUE argv[] = {graph_obj, ULL2NUM(cell->payload.node.id)};
872+
VALUE klass;
873+
switch (cell->payload.node.category) {
874+
case CNodeCategory_Declaration:
875+
klass = rdxi_declaration_class_for_kind((CDeclarationKind)cell->payload.node.kind);
876+
break;
877+
case CNodeCategory_Definition:
878+
klass = rdxi_definition_class_for_kind((DefinitionKind)cell->payload.node.kind);
879+
break;
880+
case CNodeCategory_Document:
881+
default:
882+
klass = cDocument;
883+
break;
884+
}
885+
return rb_class_new_instance(2, argv, klass);
886+
}
887+
default:
888+
return Qnil;
889+
}
890+
}
891+
892+
// Rubydex::Query#run(graph) -> Array[Hash[String, Object]]
893+
// Runs this parsed query against the given graph and returns the rows as Ruby objects: each row is a
894+
// Hash keyed by RETURN column name. Scalar cells become String/Integer/true/false/nil, lists become
895+
// Arrays, and node cells become Declaration / Definition / Document handles. Raises ArgumentError on
896+
// an execution error.
897+
static VALUE rdxr_query_run(VALUE self, VALUE graph_obj) {
898+
void *query;
899+
TypedData_Get_Struct(self, void *, &query_type, query);
900+
901+
void *graph;
902+
TypedData_Get_Struct(graph_obj, void *, &graph_type, graph);
903+
904+
struct CRunRows run = rdx_query_run_rows(query, graph);
905+
906+
if (run.error != NULL) {
907+
VALUE message = rb_utf8_str_new_cstr(run.error);
908+
free_c_string(run.error);
909+
rb_raise(rb_eArgError, "%s", StringValueCStr(message));
910+
}
911+
912+
struct CResultSet *result = run.result;
913+
VALUE rows = rb_ary_new_capa((long)result->row_count);
914+
915+
for (size_t r = 0; r < result->row_count; r++) {
916+
struct CResultRow row = result->rows[r];
917+
VALUE hash = rb_hash_new();
918+
for (size_t c = 0; c < row.len && c < result->column_count; c++) {
919+
VALUE key = rb_utf8_str_new_cstr(result->columns[c]);
920+
rb_hash_aset(hash, key, cypher_cell_to_value(graph_obj, &row.cells[c]));
921+
}
922+
rb_ary_push(rows, hash);
923+
}
924+
925+
rdx_result_set_free(result);
926+
return rows;
927+
}
928+
850929
void rdxi_initialize_graph(VALUE moduleRubydex) {
851930
mRubydex = moduleRubydex;
852931
cGraph = rb_define_class_under(mRubydex, "Graph", rb_cObject);
@@ -886,5 +965,6 @@ void rdxi_initialize_graph(VALUE moduleRubydex) {
886965
rb_undef_alloc_func(cQuery);
887966
rb_define_singleton_method(cQuery, "parse", rdxr_query_parse, 1);
888967
rb_define_singleton_method(cQuery, "schema", rdxr_cypher_schema, -1);
968+
rb_define_method(cQuery, "run", rdxr_query_run, 1);
889969
rb_define_method(cQuery, "render", rdxr_query_render, -1);
890970
}

rbi/rubydex.rbi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,9 @@ class Rubydex::Query
281281
def schema(format = :table); end
282282
end
283283

284+
sig { params(graph: Rubydex::Graph).returns(T::Array[T::Hash[String, T.untyped]]) }
285+
def run(graph); end
286+
284287
sig { params(graph: Rubydex::Graph, format: T.any(String, Symbol)).returns(String) }
285288
def render(graph, format = :table); end
286289
end

rust/Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)