Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions ext/rubydex/graph.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@

extern const rb_data_type_t graph_type;

static inline void *rdxi_graph_from_object(VALUE graph_obj) {
void *graph;
TypedData_Get_Struct(graph_obj, void *, &graph_type, graph);
return graph;
}

void rdxi_initialize_graph(VALUE mRubydex);

#endif // RUBYDEX_GRAPH_H
115 changes: 113 additions & 2 deletions ext/rubydex/query.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#include "query.h"
#include "declaration.h"
#include "definition.h"
#include "document.h"
#include "graph.h"
#include "rustbindings.h"
#include "utils.h"
Expand Down Expand Up @@ -77,8 +80,7 @@ static VALUE rdxr_query_render(int argc, VALUE *argv, VALUE self) {
void *query;
TypedData_Get_Struct(self, void *, &query_type, query);

void *graph;
TypedData_Get_Struct(graph_obj, void *, &graph_type, graph);
void *graph = rdxi_graph_from_object(graph_obj);

struct CQueryResult result = rdx_query_run(query, graph, rdxi_symbol_or_string_cstr(format, "table"));

Expand All @@ -96,10 +98,119 @@ static VALUE rdxr_query_render(int argc, VALUE *argv, VALUE self) {
return output;
}

// Converts a structured result cell into a Ruby value. Node cells become real graph handles
// (Declaration / Definition / Document) built against `graph_obj`; lists recurse.
static VALUE cypher_cell_to_value(VALUE graph_obj, const struct CCell *cell) {
switch (cell->tag) {
case CCellTag_Null:
return Qnil;
case CCellTag_Bool:
return cell->payload.bool_val ? Qtrue : Qfalse;
case CCellTag_Int:
return LL2NUM(cell->payload.int_val);
case CCellTag_Str:
return cell->payload.str_val == NULL ? Qnil : rb_utf8_str_new_cstr(cell->payload.str_val);
case CCellTag_List: {
VALUE array = rb_ary_new_capa((long)cell->payload.list.len);
for (size_t i = 0; i < cell->payload.list.len; i++) {
rb_ary_push(array, cypher_cell_to_value(graph_obj, &cell->payload.list.items[i]));
}
return array;
}
case CCellTag_Map: {
VALUE hash = rb_hash_new();
for (size_t i = 0; i < cell->payload.map.len; i++) {
const char *raw_key = cell->payload.map.keys[i];
VALUE key = raw_key == NULL ? Qnil : rb_utf8_str_new_cstr(raw_key);
rb_hash_aset(hash, key, cypher_cell_to_value(graph_obj, &cell->payload.map.values[i]));
}
return hash;
}
case CCellTag_Node: {
VALUE argv[] = {graph_obj, ULL2NUM(cell->payload.node.id)};
VALUE klass;
switch (cell->payload.node.category) {
case CNodeCategory_Declaration:
klass = rdxi_declaration_class_for_kind((CDeclarationKind)cell->payload.node.kind);
break;
case CNodeCategory_Definition:
klass = rdxi_definition_class_for_kind((DefinitionKind)cell->payload.node.kind);
break;
case CNodeCategory_Document:
default:
klass = cDocument;
break;
}
return rb_class_new_instance(2, argv, klass);
}
default:
return Qnil;
}
}

// Body function for rb_ensure in Query#run — walks the iterator and builds the rows array.
// May raise if cell conversion (e.g. handle construction) fails; the ensure function frees the
// iterator regardless.
static VALUE query_run_yield(VALUE args) {
VALUE graph_obj = rb_ary_entry(args, 0);
struct CRowsIter *iter = (struct CRowsIter *)(uintptr_t)NUM2ULL(rb_ary_entry(args, 1));

size_t column_count = rdx_rows_iter_column_count(iter);
const char *const *columns = rdx_rows_iter_columns(iter);
VALUE rows = rb_ary_new_capa((long)rdx_rows_iter_len(iter));

struct CResultRow row;
while (rdx_rows_iter_next(iter, &row)) {
VALUE hash = rb_hash_new();
for (size_t c = 0; c < row.len && c < column_count; c++) {
VALUE key = rb_utf8_str_new_cstr(columns[c]);
rb_hash_aset(hash, key, cypher_cell_to_value(graph_obj, &row.cells[c]));
}
rb_ary_push(rows, hash);
}

return rows;
}

// Ensure function for rb_ensure in Query#run to always free the iterator.
static VALUE query_run_ensure(VALUE args) {
struct CRowsIter *iter = (struct CRowsIter *)(uintptr_t)NUM2ULL(rb_ary_entry(args, 1));
rdx_rows_iter_free(iter);
return Qnil;
}

/*
* call-seq:
* run(graph) -> Array[Hash[String, Object]]
*
* Runs this parsed query against +graph+ and returns the rows as Ruby objects: each row is a Hash
* keyed by RETURN column name. Scalar cells become String/Integer/true/false/nil, lists become
* Arrays, and node cells become Declaration / Definition / Document handles. Raises ArgumentError
* on an execution error.
*/
static VALUE rdxr_query_run(VALUE self, VALUE graph_obj) {
void *query;
TypedData_Get_Struct(self, void *, &query_type, query);

void *graph = rdxi_graph_from_object(graph_obj);

struct CRunRows run = rdx_query_run_rows(query, graph);

if (run.error != NULL) {
VALUE message = rb_utf8_str_new_cstr(run.error);
free_c_string(run.error);
rb_raise(rb_eArgError, "%s", StringValueCStr(message));
}

VALUE args = rb_ary_new_from_args(2, graph_obj, ULL2NUM((uintptr_t)run.iter));
return rb_ensure(query_run_yield, args, query_run_ensure, args);
}

void rdxi_initialize_query(VALUE mRubydex) {
VALUE cQuery = rb_define_class_under(mRubydex, "Query", rb_cObject);
rb_undef_alloc_func(cQuery);
rb_define_singleton_method(cQuery, "parse", rdxr_query_parse, 1);
rb_define_singleton_method(cQuery, "schema", rdxr_cypher_schema, -1);
rb_define_method(cQuery, "render", rdxr_query_render, -1);
rb_define_method(cQuery, "run", rdxr_query_run, 1);
}
3 changes: 3 additions & 0 deletions rbi/rubydex.rbi
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,9 @@ class Rubydex::Query
def schema(format = :table); end
end

sig { params(graph: Rubydex::Graph).returns(T::Array[T::Hash[String, T.untyped]]) }
def run(graph); end

sig { params(graph: Rubydex::Graph, format: T.any(String, Symbol)).returns(String) }
def render(graph, format = :table); end
end
Expand Down
4 changes: 2 additions & 2 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading