kuzudb / kuzu

Embeddable property graph database management system built for query speed and scalability. Implements Cypher.
https://kuzudb.com/
MIT License
1.24k stars 86 forks source link

RuntimeError: Binder exception: Variable __existence already exists. #3510

Closed sapalli2989 closed 1 week ago

sapalli2989 commented 3 months ago

Hi, above error can be reproduced by following snippet:

import kuzu
from uuid import uuid4
import tempfile

with tempfile.TemporaryDirectory() as dbpath:
    conn = kuzu.Connection(kuzu.Database(dbpath))
    conn.execute(
        """
        CREATE NODE TABLE T (id UUID, PRIMARY KEY(id));
        CREATE NODE TABLE U (id UUID, name STRING, PRIMARY KEY(id));
        CREATE REL TABLE has (FROM T TO U);
        """,
    )
    # Prerequirement: existing :T
    tid = uuid4()
    conn.execute("CREATE (t:T {id: $id}) RETURN t;", {"id": tid})

    # Merge :U from memory (new here, but might already exist)
    uid = uuid4()
    conn.execute(
        """
        MATCH (t:T {id: $tid}) // (1)
        MERGE (u:U {id: $uid}) // (2)
        SET u.name = $name     // (3)
        MERGE (t)-[e:has]->(u)
        RETURN t,u,e;
        """,
        {"tid": tid, "uid": uid, "name": "foo"},
    )

Some background for clarification: Goal was to construct a simple kind of object graph mapper (OGM) with MERGE: Given entities T,U with composite relation (:T)-[:has]->(:U), try to merge a new or existing U from memory. (1) ensures :T exists (2) merges an :U by its identifier (otherwise MERGE would try to create a new entity, if any property has changed) (3) update all :U properties in database (assuming object memory has most recent version or creates new entity)

Kuzu 0.4.2

andyfengHKU commented 3 months ago

Hi @sapalli2989,

I can confirm this is bug on our side and working on it. Meanwhile, I wonder if the following workaround works for u?

# Create :T
tid = uuid4()
conn.execute(
"""
CREATE (t:T {id: $tid}) RETURN t;
""", ....) 

# Create :U if not exist
uid = uuid4()
conn.execute(
"""
MERGE (u:U {id: $uid}) 
SET u.name = $name
""", ...)

# Create :has if not exist
conn.execute(
"""
MATCH (t:T {id: $tid}), (u:U {id: $uid}) 
MERGE (t)-[e:has]->(u)
RETURN t, u, e
""", ...)

Essentially we split merge into multiple statements instead of one.

sapalli2989 commented 3 months ago

Great, thanks!

andyfengHKU commented 1 week ago

This is now fixed in #4046