Reputation: 53
I have code to add nodes to a neo4j instance via the python driver. Nodes are matched based on label and 'name', and there is a unique constraint imposed on label and 'name'. I use MERGE so that I either MATCH or CREATE a node depending whether it exists. I then add new properties if I matched an existing node but have new properties to add from my graph in python. This code has been working consistently, even when I edit nodes with new properties. However, it recently broke when I changed the 'count' property on node(17) with name 'My' and label 'Person'. Is there a reason why changing (rather than adding) a property would cause a problem with this code?
node_label and node_name are properties of nodes in a networkx graph, as are other_props (other_props contains the 'count').
Here is my code:
query = (
f"MERGE (n: {node_label} {{name: \"{node_name}\"}})\n"
f"ON CREATE\n"
f" SET n.created = timestamp()\n"
f"SET n += {{{other_props}}}\n"
f"RETURN n, n.created"
)
My understanding is that with MERGE it should be impossible to violate my unique constraint if I am MERGEing based on the same properties involved in the unique constraint.
But I am somehow getting the following error:
If anyone has ideas on how I could possibly be violating the unique constraint, do let me know. I am still fairly new to Cypher and am looking to get better.
In neo4j browser, the exact same query has no issue...
Upvotes: 0
Views: 373
Reputation: 1403
Here is a short Python bit of how I imagine the stripped down version of what you're doing looks like
import neo4j
import neo4j.exceptions
def main():
uri = "neo4j://localhost:7687"
user = "neo4j"
password = "pass"
auth = neo4j.basic_auth(user, password)
with neo4j.GraphDatabase.driver(uri, auth=auth) as driver:
with driver.session() as session:
create_query = "CREATE (:Person {name: $name})"
match_query = "MATCH (p:Person {name: $name}) RETURN p"
merge_query = ("MERGE (p:Person {name: $name}) "
"ON CREATE "
" SET p.name=$name2")
wrong_merge = ("MERGE (p:Person {name: $name}) "
"ON CREATE "
" SET p.name='created' "
"SET p.name=$name2")
session.run("CREATE CONSTRAINT person_name IF NOT EXISTS "
"FOR (p:Person) REQUIRE p.name IS UNIQUE")
session.run("MATCH (n) DETACH DELETE n")
session.run(create_query, name="Alice")
session.run(create_query, name="Bob")
try:
session.run(create_query, name="Alice")
assert False # should not get here
except neo4j.exceptions.Neo4jError as e:
print(f"Fails as expected: {e}")
# noop because Alice already exists
session.run(merge_query, name="Alice", name2="Alice2")
assert len(list(session.run(match_query, name="Alice"))) == 1
assert not list(session.run(match_query, name="Alice2"))
# finds Alice and always updates
session.run(wrong_merge, name="Alice", name2="Alice2")
assert not list(session.run(match_query, name="Alice"))
assert len(list(session.run(match_query, name="Alice2"))) == 1
# You can't rename Alice to Bob, bcs. Bob already exists
try:
session.run(wrong_merge, name="Alice2", name2="Bob")
assert False # should not get here
except neo4j.exceptions.Neo4jError as e:
print(f"Fails as expected: {e}")
if __name__ == "__main__":
main()
Have a look at the wrong_merge
query. The second SET
always gets executed, regardless if MERGE
creates a new node or matches an existing one. I strongly suspect name
to be present in your other_props
hence trying to change the name to something that already exists.
Lastly, regardless whether this is the case or not, I recommend you to get used to never building any database queries (no matter which db) by doing string concatenation or interpolation. Almost all DBs offer a way to parametrize your query. This has two benefits:
Upvotes: 0