Skip to content

perf(falkordb): push structural Graph queries down to Cypher - #44

Merged
2 commits merged into
yoheinakajima:mainfrom
dudizimber:falkordb-projection-reads
Jun 29, 2026
Merged

2 commits merged into
yoheinakajima:mainfrom
dudizimber:falkordb-projection-reads

Conversation

@dudizimber

Copy link
Copy Markdown
Contributor

fix #43
depends on #42

Summary

Graph query methods previously evaluated every filter and traversal in Python
by scanning the full materialized projection (all_objects() / all_relations())
on each call. For FalkorDBGraphStore that means fetching and JSON-decoding every
entity even to answer a narrow query (e.g. "objects of type X" or a 1-hop
neighborhood). This PR lets backends evaluate structural queries close to the data
while keeping query semantics identical across all backends.

What changed

  • New optional query hooks on the GraphStore ABC — find_objects,
    find_relations, and neighborhood — with working Python defaults computed over
    all_objects / all_relations. The base class is the canonical definition of
    their semantics; InMemoryGraphStore inherits them unchanged.
  • Graph delegates to the hooks instead of scanning:
    • Graph.objects(type=...) / query(...) and has_object_of_type(...) →
      find_objects
    • Graph.relations(...) and Graph.get_relations(...) → find_relations
    • Graph.neighborhood(...) → neighborhood
  • FalkorDBGraphStore overrides the hooks with Cypher so type filters, relation
    lookups, and neighborhood walks run inside the database (the neighborhood walk uses
    a native variable-length path over AGRelation edges) and return only matching
    rows instead of the whole projection.
  • where predicates still run in Python. Graph.objects(where=...) pushes the
    type filter down but applies the where clause in Python over the returned
    subset, because the structured data payload is stored as a JSON string rather
    than as native, indexable properties.

Scope / non-goals

  • View (frozen snapshot) and whole-graph consumers (diffing, prompt building,
    pattern matching, the registry, CLI status) legitimately read the full projection
    and are intentionally left unchanged.
  • Pushing where predicates down (would require flattening selected scalar fields to
    native properties + dual-write) is deferred to a future change.

Correctness & parity

  • Backward-compatible quirks preserved: get_relations keeps the v0 behavior of
    ignoring object_id for an unrecognized direction, and the "both"
    source-OR-target membership test (which the AND-only find_relations can't express)
    stays in Graph.
  • New parity tests in GraphStoreConformance run the same assertions against
    both InMemoryGraphStore (the reference) and FalkorDBGraphStore, covering:
    find_objects (none + type filter), find_relations (all source/target/type
    combinations, including dangling endpoints), and neighborhood (depth 0/1/2/3, a
    placeholder midpoint, a cycle, and non-object/unknown starts).

Security

Every value crosses the Cypher boundary as a bound $param. The only literal spliced
into a query is the variable-length upper bound *1..{hops}, where hops = int(depth)
is a caller-owned coerced integer — no user text, no injection surface.

Testing

  • InMemory conformance suite: green (includes the new parity tests).
  • Live FalkorDB suite (Docker container): 22 passed, including the variable-length
    path queries.
  • Doc-link, llms.txt, Graph, and GraphStore suites pass; docstring gate passes.

Docs / changelog

  • Updated the "How entities are stored" notes in docs/guides/using-falkordb.md to
    describe push-down vs. the Python-evaluated where.
  • Added an [Unreleased] entry to CHANGELOG.md.

@yoheinakajima yoheinakajima closed this pull request by merging all changes into yoheinakajima:main in fd5b029 Jun 29, 2026
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.

FalkorDB: structural Graph queries scan the whole projection instead of pushing down to Cypher

2 participants