Skip to content

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

@dmoellenbeck

Description

@dmoellenbeck

Bug Description

CypherEngine.execute() in rvlite WASM returns at most one row for any MATCH ... RETURN query, even when multiple nodes match. Property access (n.name, n.value) works syntactically but only returns data for the last matched node.

Environment

  • rvlite npm: v0.2.4 (WASM reports "v0.2.0")
  • ruvector npm: v0.2.16
  • Node.js: v25.7.0
  • Platform: macOS (Apple Silicon)
  • Usage: via MCP server (npx ruvector mcp start) + rvlite_cypher tool

Steps to Reproduce

import { initSync, RvLite, CypherEngine } from 'rvlite';
import { readFileSync } from 'fs';

const wasmBuffer = readFileSync('node_modules/rvlite/dist/wasm/rvlite_bg.wasm');
initSync({ module: wasmBuffer });

const db = new RvLite();
const cypher = new CypherEngine(db);

// Create multiple nodes
cypher.execute('CREATE (:Person {name: "Alice", age: 30})');
cypher.execute('CREATE (:Person {name: "Bob", age: 25})');
cypher.execute('CREATE (:Person {name: "Charlie", age: 35})');

// Query all — expect 3 rows, get 1 empty row
const result = cypher.execute('MATCH (p:Person) RETURN p.name, p.age');
console.log(result);

Expected Output

{
  "columns": ["p.name", "p.age"],
  "rows": [
    { "p.name": "Alice", "p.age": 30 },
    { "p.name": "Bob", "p.age": 25 },
    { "p.name": "Charlie", "p.age": 35 }
  ]
}

Actual Output

{
  "columns": ["?column?", "?column?"],
  "rows": [{}]
}

Root Cause Analysis

Located in crates/rvlite/src/cypher/executor.rs, in the execute_match() function:

// Current implementation — overwrites context on each iteration
for match_ctx in matches {
    for (var, val) in match_ctx.variables {
        context.bind(var, val);  // ← Overwrites previous bindings!
    }
}

The issue: all match results are merged into a single ExecutionContext via context.bind(). Since each call to .bind() overwrites the variable, only the last matched node survives. Then execute_return() produces a single row from whatever the final context holds.

The parser (parser.rs) and property access (Expression::Property evaluation) work correctly — the bug is purely in how match results flow into the return clause.

Suggested Fix

execute_match() should return a Vec<ExecutionContext> (one per match), and execute_return() should iterate over all contexts to produce multiple rows:

// Proposed fix sketch
fn execute_match(&mut self, pattern: &MatchPattern) -> Vec<ExecutionContext> {
    let matches = self.graph_store.find_matches(pattern);
    // Return each match as its own context — don't merge
    matches.into_iter().map(|match_ctx| {
        let mut ctx = ExecutionContext::new();
        for (var, val) in match_ctx.variables {
            ctx.bind(var, val);
        }
        ctx
    }).collect()
}

fn execute_return(&mut self, contexts: &[ExecutionContext], return_items: &[ReturnItem]) -> CypherResult {
    let mut rows = Vec::new();
    for ctx in contexts {
        let mut row = HashMap::new();
        for item in return_items {
            let value = self.evaluate_expression_ctx(&item.expression, ctx);
            let alias = item.alias.as_deref().unwrap_or(&item.expression.to_string());
            row.insert(alias.to_string(), value);
        }
        rows.push(row);
    }
    CypherResult { columns: /* from return_items */, rows }
}

Additionally, execute_return should use the actual return item expressions as column names instead of "?column?".

Related

Impact

This blocks all multi-row Cypher queries in the WASM/MCP path, which means:

  • Knowledge graph queries cannot enumerate nodes
  • Pattern matching across relationships returns incomplete data
  • Any application relying on rvlite_cypher MCP tool for graph queries gets wrong results

Workarounds (current)

  1. Use targeted single-node queries with property filters: MATCH (n:Person {name: 'Alice'}) RETURN n
  2. Use rvlite_sql instead of Cypher for multi-row queries
  3. Use hooks_remember/hooks_recall (vector search) instead of graph queries

Thank you for the great work on ruvector! Happy to submit a PR with the fix if helpful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions