Skip to content

fix(rvlite): CypherEngine MATCH returns all matched rows with properties#270

Open
dmoellenbeck wants to merge 1 commit intoruvnet:mainfrom
dmoellenbeck:fix/cypher-multirow-properties
Open

fix(rvlite): CypherEngine MATCH returns all matched rows with properties#270
dmoellenbeck wants to merge 1 commit intoruvnet:mainfrom
dmoellenbeck:fix/cypher-multirow-properties

Conversation

@dmoellenbeck
Copy link

Summary

Fixes #269CypherEngine.execute() now correctly returns multiple rows from MATCH ... RETURN queries, with all node properties properly serialized to JavaScript.

Problem

Any MATCH (n:Label) RETURN n.prop query returned at most 1 row with empty properties {}, regardless of how many nodes matched:

cypher.execute('CREATE (:Person {name: "Alice", age: 30})');
cypher.execute('CREATE (:Person {name: "Bob", age: 25})');

cypher.execute('MATCH (p:Person) RETURN p.name, p.age');
// Before: { columns: ["?column?","?column?"], rows: [{}] }
// After:  { columns: ["p.name","p.age"], rows: [{p.name:"Alice",p.age:30}, {p.name:"Bob",p.age:25}] }

Root Cause

Two separate bugs in the Cypher executor:

  1. Context merge in execute_match() (executor.rs): All matched node contexts were merged into a single ExecutionContext via context.bind(). Each call overwrote the previous binding, so only the last matched node survived into the RETURN phase.

  2. WASM serialization in CypherEngine::execute() (mod.rs): serde_wasm_bindgen::to_value() silently dropped ContextValue enum variants during serialization to JavaScript, producing empty {} objects.

Solution

executor.rs

  • execute_match() returns Vec<ExecutionContext> — one context per matched row, instead of merging into one
  • execute_return() iterates all contexts, producing one result row per match
  • Added expression_to_column_name() — generates readable column names ("p.name") instead of "?column?"
  • Added ContextValue::to_json_value() and value_to_json() — flattens ContextValue enums to plain serde_json::Value for reliable serialization

mod.rs

  • CypherEngine::execute() now serializes via serde_json::to_string() + js_sys::JSON::parse() instead of serde_wasm_bindgen::to_value(), avoiding the silent enum serialization failure

Testing

  • All 24 existing cypher unit tests pass (cargo test -p rvlite -- cypher)
  • 3 new integration tests added in tests/cypher_integration_test.rs:
    • test_match_return_multiple_rows — Creates 3 nodes, verifies 3 rows with correct column names
    • test_match_where_return_multiple_rows — WHERE filter returns subset of nodes
    • test_match_return_variable_nodeRETURN n returns full node objects with properties
  • WASM build verified with wasm-pack build on macOS Apple Silicon
  • Tested end-to-end via MCP server (rvlite_cypher tool)

Breaking Changes

None. The fix only changes what was previously incorrect behavior (empty results). Existing queries that returned 0-1 rows will now correctly return all matching rows.

Related

Additional Note for MCP Server

The rvlite_cypher handler in bin/mcp-server.js currently uses require('rvlite') (CJS), but rvlite v0.2.x is ESM-only — the CJS entry point exports an empty object. This means the MCP tool always falls through to "rvlite package not installed". A separate fix is needed to switch the handler to await import('rvlite/wasm') with WASM init. Happy to open a follow-up issue/PR for this if helpful.

Previously, execute_match() merged all matched node contexts into a
single ExecutionContext via context.bind(). Each call to bind() overwrote
the variable, so only the last matched node survived into RETURN.
This meant any MATCH query returned at most 1 row with empty properties.

Changes in executor.rs:
- execute_match() now returns Vec<ExecutionContext> (one per matched row)
- execute_return() iterates all contexts to produce multiple result rows
- Column names use expression text ("p.name") instead of "?column?"
- Added ContextValue::to_json_value() for proper WASM serialization
- Added value_to_json() helper covering all Value variants incl List/Map

Changes in mod.rs (WASM binding):
- Serializes via serde_json + js_sys::JSON::parse instead of
  serde_wasm_bindgen::to_value which failed on ContextValue enums

Tests added (cypher_integration_test.rs):
- test_match_return_multiple_rows
- test_match_where_return_multiple_rows
- test_match_return_variable_node

All 24 existing cypher unit tests pass.

Fixes ruvnet#269
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.

CypherEngine: MATCH RETURN produces single row — context.bind() overwrites previous bindings

1 participant