Skip to content

[BUG] add_triplet() does not save edges with FalkorDB provider #1001

@javflocor

Description

@javflocor

Bug Summary

When using graphiti.add_triplet() with FalkorDB, edges ARE created successfully but the source_node_uuid and target_node_uuid properties on the relationship are stored as None instead of the actual node UUIDs.

Impact

  • Severity: High
  • Affected Provider: FalkorDB only (Neo4j works correctly)
  • Version: graphiti-core 0.21.0

Consequences:

  • Edge deduplication may fail (can't match edges by source/target UUIDs)
  • Edge filtering by UUID doesn't work correctly
  • Data integrity issues when validating edge relationships
  • Inconsistent behavior compared to Neo4j provider

Root Cause

File: graphiti_core/models/edges/edge_db_queries.py
Function: get_entity_edge_save_bulk_query()
Lines: 129-133

Issue 1: MATCH vs MERGE (Fixed with this proposal for PR)

The original query used MATCH to find source and target nodes, which would fail if nodes didn't exist. This needs to be changed to MERGE (lines 129-130) to ensure nodes are created if they don't exist.

Change: MATCHMERGE for both source and target node lookups

Issue 2: Missing UUID Fields in SET Statement (Fixed with this proposal for PR)

The FalkorDB Cypher query explicitly lists properties in the SET statement but omits source_node_uuid and target_node_uuid:

case GraphProvider.FALKORDB:
    return """
        UNWIND $entity_edges AS edge
        MERGE (source:Entity {uuid: edge.source_node_uuid})
        MERGE (target:Entity {uuid: edge.target_node_uuid})
        MERGE (source)-[r:RELATES_TO {uuid: edge.uuid}]->(target)
        SET r = {
            uuid: edge.uuid,
            name: edge.name,
            group_id: edge.group_id,
            fact: edge.fact,
            episodes: edge.episodes,
            created_at: edge.created_at,
            expired_at: edge.expired_at,
            valid_at: edge.valid_at,
            invalid_at: edge.invalid_at,
            fact_embedding: vecf32(edge.fact_embedding)
        }
        WITH r, edge
        RETURN edge.uuid AS uuid
    """

Why This is FalkorDB-Specific

Neo4j uses SET e = edge which copies all properties from the edge dictionary:

case _:  # Neo4j
    return """
        UNWIND $entity_edges AS edge
        MATCH (source:Entity {uuid: edge.source_node_uuid})
        MATCH (target:Entity {uuid: edge.target_node_uuid})
        MERGE (source)-[e:RELATES_TO {uuid: edge.uuid}]->(target)
        SET e = edge    # ← Copies ALL properties
        RETURN edge.uuid AS uuid
    """

FalkorDB requires explicit property listing, and the source_node_uuid and target_node_uuid fields were accidentally omitted.

Proposed Fix

Two changes needed in the FalkorDB case:

  1. Change MATCH to MERGE (lines 129-130) - Ensures nodes exist before creating relationships
  2. Add missing UUID fields (line 132) - Stores source and target UUIDs in edge properties

Complete fixed query:

case GraphProvider.FALKORDB:
    return """
        UNWIND $entity_edges AS edge
        MERGE (source:Entity {uuid: edge.source_node_uuid})
        MERGE (target:Entity {uuid: edge.target_node_uuid})
        MERGE (source)-[r:RELATES_TO {uuid: edge.uuid}]->(target)
        SET r = {
            uuid: edge.uuid,
            source_node_uuid: edge.source_node_uuid,    # ADD THIS
            target_node_uuid: edge.target_node_uuid,    # ADD THIS
            name: edge.name,
            group_id: edge.group_id,
            fact: edge.fact,
            episodes: edge.episodes,
            created_at: edge.created_at,
            expired_at: edge.expired_at,
            valid_at: edge.valid_at,
            invalid_at: edge.invalid_at,
            fact_embedding: vecf32(edge.fact_embedding)
        }
        WITH r, edge
        RETURN edge.uuid AS uuid
    """

Test Case

Reproduction

from graphiti_core import Graphiti
from graphiti_core.driver.falkordb_driver import FalkorDriver
from graphiti_core.nodes import EntityNode
from graphiti_core.edges import EntityEdge
from datetime import datetime, timezone

driver = FalkorDriver(host='localhost', port=6379, database='test_db')
graphiti = Graphiti(graph_driver=driver)

# Add triplet
result = await graphiti.add_triplet(
    source_node=EntityNode(
        name="Person A",
        group_id="test",
        labels=["Entity", "Person"],
        created_at=datetime.now(timezone.utc)
    ),
    edge=EntityEdge(
        name="knows",
        source_node_uuid="",
        target_node_uuid="",
        group_id="test",
        fact="Person A knows Person B",
        fact_embedding=None,
        episodes=[],
        valid_at=datetime.now(timezone.utc),
        invalid_at=None,
        created_at=datetime.now(timezone.utc),
        expired_at=None
    ),
    target_node=EntityNode(
        name="Person B",
        group_id="test",
        labels=["Entity", "Person"],
        created_at=datetime.now(timezone.utc)
    )
)

# Verify edge properties
query = """
MATCH (source:Entity)-[r:RELATES_TO]->(target:Entity)
WHERE r.group_id = 'test'
RETURN
    source.uuid AS source_uuid,
    target.uuid AS target_uuid,
    r.source_node_uuid AS stored_source_uuid,
    r.target_node_uuid AS stored_target_uuid
"""

result = await driver.execute_query(query)
# Result: stored_source_uuid and stored_target_uuid are None (BEFORE FIX)
# Result: stored_source_uuid and stored_target_uuid match source_uuid and target_uuid (AFTER FIX)

Expected Behavior (After Fix)

source_uuid: bf29de8d-a3e8-41d8-b078-d3e191c35457
target_uuid: 3794242a-1749-4567-b2e7-6cefb06d38b3
stored_source_uuid: bf29de8d-a3e8-41d8-b078-d3e191c35457
stored_target_uuid: 3794242a-1749-4567-b2e7-6cefb06d38b3

Actual Behavior (Before Fix)

source_uuid: bf29de8d-a3e8-41d8-b078-d3e191c35457
target_uuid: 3794242a-1749-4567-b2e7-6cefb06d38b3
stored_source_uuid: None
stored_target_uuid: None

Investigation Details

The UUIDs are correctly populated in the edge_data dictionary before being passed to the query (verified in bulk_utils.py:207-208):

edge_data: dict[str, Any] = {
    'uuid': edge.uuid,
    'source_node_uuid': edge.source_node_uuid,    # Populated correctly
    'target_node_uuid': edge.target_node_uuid,    # Populated correctly
    'name': edge.name,
    'fact': edge.fact,
    # ... other fields
}

The issue is purely in the Cypher query's SET statement, which doesn't include these fields in the FalkorDB case.

Additional Notes

Changes Made in This Fix

Before (Current Code):

  • Lines 129-130: Uses MATCH which fails if nodes don't exist
  • Lines 132-133: SET statement missing source_node_uuid and target_node_uuid

After (Fixed Code):

  • Lines 129-130: Changed to MERGE - creates nodes if they don't exist
  • Line 132: Added source_node_uuid and target_node_uuid to SET statement

Why Both Changes Matter

  1. MATCH → MERGE: Prevents query failure when nodes don't exist yet

    • Original behavior: Query fails silently if nodes aren't found
    • Fixed behavior: Nodes are created automatically, edge is always created
  2. Missing UUID fields: Critical for edge metadata integrity

    • Affects edge deduplication logic
    • Breaks filtering edges by source/target UUID
    • Causes data integrity validation issues

Testing

Both fixes have been tested locally with real-world data (fundraising knowledge graph) and verified working correctly. Edges now have proper UUID metadata and are created reliably.

Environment

  • graphiti-core: 0.21.0
  • FalkorDB: Latest (Docker container)
  • Python: 3.11
  • OS: macOS

Related Files

  • graphiti_core/models/edges/edge_db_queries.py (lines 124-136)
  • graphiti_core/utils/bulk_utils.py (lines 206-209)

Priority: High - Affects data integrity for FalkorDB users
Complexity: Low - Simple 2-line addition to existing query
Testing: Verified fix works correctly with real-world test cases

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggood first issueGood for newcomers

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions