From 0e94e89dad75dcb2d136cd858d1299897d13cc3d Mon Sep 17 00:00:00 2001 From: dylothx Date: Mon, 6 Jul 2026 00:27:42 +0800 Subject: [PATCH 1/2] Use uniqueness constraint for KG writer temp ids --- src/neo4j_graphrag/components/kg_writer.py | 15 +++++++++--- tests/unit/components/test_kg_writer.py | 28 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/neo4j_graphrag/components/kg_writer.py b/src/neo4j_graphrag/components/kg_writer.py index 74e921cf8..300426e77 100644 --- a/src/neo4j_graphrag/components/kg_writer.py +++ b/src/neo4j_graphrag/components/kg_writer.py @@ -215,9 +215,18 @@ def __init__( self.is_version_5_24_or_above = is_version_5_24_or_above(version_tuple) def _db_setup(self) -> None: - self.driver.execute_query(""" - CREATE INDEX __entity__tmp_internal_id IF NOT EXISTS FOR (n:__KGBuilder__) ON (n.__tmp_internal_id) - """) + self.driver.execute_query( + "DROP INDEX __entity__tmp_internal_id IF EXISTS", + database_=self.neo4j_database, + ) + self.driver.execute_query( + """ + CREATE CONSTRAINT __entity__tmp_internal_id_unique IF NOT EXISTS + FOR (n:__KGBuilder__) + REQUIRE n.__tmp_internal_id IS UNIQUE + """, + database_=self.neo4j_database, + ) @staticmethod def _nodes_to_rows( diff --git a/tests/unit/components/test_kg_writer.py b/tests/unit/components/test_kg_writer.py index e40936e4d..e19653408 100644 --- a/tests/unit/components/test_kg_writer.py +++ b/tests/unit/components/test_kg_writer.py @@ -102,6 +102,34 @@ def test_get_unique_properties_for_node_type_deprecation_warning() -> None: ] +@mock.patch( + "neo4j_graphrag.experimental.components.kg_writer.get_version", + return_value=((5, 22, 0), False, False), +) +def test_neo4j_writer_db_setup_uses_unique_constraint( + _: Mock, driver: MagicMock +) -> None: + neo4j_writer = Neo4jWriter(driver=driver, neo4j_database="my_db") + driver.execute_query.reset_mock() + + neo4j_writer._db_setup() + + assert driver.execute_query.call_count == 2 + drop_call = driver.execute_query.call_args_list[0] + assert drop_call.args == ("DROP INDEX __entity__tmp_internal_id IF EXISTS",) + assert drop_call.kwargs == {"database_": "my_db"} + + constraint_call = driver.execute_query.call_args_list[1] + constraint_query = constraint_call.args[0] + assert ( + "CREATE CONSTRAINT __entity__tmp_internal_id_unique IF NOT EXISTS" + in constraint_query + ) + assert "FOR (n:__KGBuilder__)" in constraint_query + assert "REQUIRE n.__tmp_internal_id IS UNIQUE" in constraint_query + assert constraint_call.kwargs == {"database_": "my_db"} + + # --- FilenameCollisionHandler tests --- From 8592f389c7fe1b9c2711a8093f6756a97d0a997e Mon Sep 17 00:00:00 2001 From: dylothx Date: Thu, 20 Aug 2026 00:21:55 +0800 Subject: [PATCH 2/2] fix(kg_writer): run temp-id cleanup against the configured database _db_cleaning opened a session without a database, so cleanup ran against the user's home database while _db_setup and the upserts targeted self.neo4j_database. When neo4j_database differed from the home database, __tmp_internal_id values were never cleared from the target DB and the new uniqueness constraint made subsequent writes fail. Pass database=self.neo4j_database to session(). Also adds the missing CHANGELOG entries for the constraint change (#159) and this fix. --- CHANGELOG.md | 2 ++ src/neo4j_graphrag/components/kg_writer.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 381263450..6775fb12c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,8 @@ - Fixed a bug in `AnthropicLLM` where an `http_client` passed via kwargs (whether an `httpx.Client` or `httpx.AsyncClient`) was forwarded to both the sync `anthropic.Anthropic` and async `anthropic.AsyncAnthropic` clients, causing a type mismatch. `http_client` is now routed to the matching sync/async client only; other kwargs remain shared. An `http_client` of an unrecognized type now emits a warning and is ignored instead of raising, matching `OpenAILLM`'s existing behavior. - Vector and VectorCypher retrievers on Neo4j 2026+: prefix SEARCH queries with `CYPHER 25` and fall back to procedure-based vector search when SEARCH is unsupported or fails. - E2E tests: added exponential backoff retry logic with jitter (5 attempts, 5–60 second waits) to embedding model downloads to handle Hugging Face rate limits. Retries only on transient network errors (connection, timeout) and immediately fails on unrecoverable errors (missing packages, permissions), improving test reliability in parallel CI runs. +- `Neo4jWriter` now guarantees uniqueness of the temporary `__tmp_internal_id` used to match nodes when writing relationships by using a `UNIQUENESS` constraint instead of a plain range index, which did not prevent duplicate ids. +- `Neo4jWriter` now runs its temporary-id cleanup against the configured `neo4j_database` rather than the user's home database, matching the database used for setup and upserts. ## 1.18.0 diff --git a/src/neo4j_graphrag/components/kg_writer.py b/src/neo4j_graphrag/components/kg_writer.py index 300426e77..2e6672bbd 100644 --- a/src/neo4j_graphrag/components/kg_writer.py +++ b/src/neo4j_graphrag/components/kg_writer.py @@ -289,7 +289,7 @@ def _db_cleaning(self) -> None: support_variable_scope_clause=self.is_version_5_23_or_above, batch_size=self.batch_size, ) - with self.driver.session() as session: + with self.driver.session(database=self.neo4j_database) as session: session.run(query) @validate_call