cocoindex-io/cocoindex · error · ValueError
build_relationship_delete requires at least one primary key
Error message
build_relationship_delete requires at least one primary key field
What it means
build_relationship_delete generates MATCH ()-[r:RelType {pk: ...}]->() DELETE r, requiring at least one key property to identify the relationship. An empty pk_fields list would produce invalid Cypher, so it raises ValueError.
Source
Thrown at python/cocoindex/connectors/neo4j/_cypher.py:155
)
cypher = (
f"MERGE (s:{_quote(from_label)} {_key_clause('from_key', from_pk_fields)}) "
f"MERGE (t:{_quote(to_label)} {_key_clause('to_key', to_pk_fields)}) "
f"MERGE (s)-[r:{_quote(rel_type)} {_key_clause('rel_key', rel_pk_fields)}]->(t)"
)
if has_value_fields:
cypher += " SET r += $props"
return cypher
def build_relationship_delete(rel_type: str, pk_fields: Sequence[str]) -> str:
"""``MATCH ()-[r:`RelType` {pk: $key_0, ...}]->() DELETE r``.
Endpoints are intentionally not deleted — they're tracked by their own
table handlers and will be deleted by their own reconciler if orphaned.
"""
if not pk_fields:
raise ValueError(
"build_relationship_delete requires at least one primary key field"
)
return (
f"MATCH ()-[r:{_quote(rel_type)} {_key_clause('key', pk_fields)}]->() DELETE r"
)
def build_node_index_create(
name: str,
label: str,
fields: Sequence[str],
) -> str:
"""``CREATE INDEX <name> IF NOT EXISTS FOR (n:`Label`) ON (n.`f1`, n.`f2`, ...)``.
Neo4j requires named indexes; ``IF NOT EXISTS`` makes setup idempotent.
"""
if not fields:
raise ValueError("build_node_index_create requires at least one field")View on GitHub (pinned to e84aa99b32)
Solutions
- Declare primary key fields on the relationship spec (same ones used in build_relationship_upsert)
- Pass the identical rel_pk_fields to both the upsert and delete builders so deletes can find rows the upserts created
- Add an early assertion that the relationship PK list is non-empty at schema construction
Example fix
// before build_relationship_delete(rel_type="MENTIONS", pk_fields=[]) // after build_relationship_delete(rel_type="MENTIONS", pk_fields=["ordinal"])
Defensive patterns
Strategy: validation
Validate before calling
if not rel_pk_fields:
raise ValueError("relationship delete needs the same PK fields used for upsert")
cypher = build_relationship_delete(rel_type=rel_type, pk_fields=rel_pk_fields) Type guard
def pk_ok(fields) -> bool:
return bool(fields) Try / catch
try:
cypher = build_relationship_delete(rel_type, pk_fields)
except ValueError as e:
if "at least one primary key" in str(e):
logger.error("cannot reconcile deletes for %r: no relationship PK", rel_type)
raise
raise Prevention
- Share one constant/spec for relationship PK fields across upsert and delete
- Declare an explicit relationship key column (e.g. ordinal or composite key) in the schema
- Unit-test delete generation for every relationship spec
When it happens
Trigger: Calling build_relationship_delete with pk_fields=[] — the relationship spec has no primary key fields when deletions are reconciled.
Common situations: Relationship declared without its own primary key; PK fields list built dynamically and left empty; refactors that removed the relationship's key column.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- build_relationship_upsert requires PK fields for from, to, a
- build_relationship_upsert requires PK fields for from, to, a
- build_node_upsert requires at least one primary key field
- build_node_delete requires at least one primary key field
- build_node_index_create requires at least one field
AI-assisted analysis of cocoindex-io/cocoindex@e84aa99b32 (2026-09-08).
Data as JSON: /api/errors/452ff3aab9b94fef.
Report an issue: GitHub.