cocoindex-io/cocoindex · error

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() refuses to build a MATCH ()-[r:{...}]->() DELETE r query when pk_fields is empty. The relationship must be identified by its primary key properties; with none, the delete would target all relationships of that type or be invalid, so it raises instead.

Source

Thrown at python/cocoindex/connectors/falkordb/_cypher.py:133

        )
    cypher = (
        f"MERGE (s:{_quote(from_label)} {_key_clause('from_key', from_pk_fields, 's')}) "
        f"MERGE (t:{_quote(to_label)} {_key_clause('to_key', to_pk_fields, 't')}) "
        f"MERGE (s)-[r:{_quote(rel_type)} {_key_clause('rel_key', rel_pk_fields, 'r')}]->(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)} "
        f"{_key_clause('key', pk_fields, 'r')}]->() DELETE r"
    )


def build_node_index_create(label: str, fields: Sequence[str]) -> str:
    """``CREATE INDEX FOR (e:`Label`) ON (e.`f1`, e.`f2`, ...)``."""
    if not fields:
        raise ValueError("build_node_index_create requires at least one field")
    field_list = ", ".join(f"e.{_quote(f)}" for f in fields)
    return f"CREATE INDEX FOR (e:{_quote(label)}) ON ({field_list})"


def build_node_index_drop(label: str, fields: Sequence[str]) -> str:
    """``DROP INDEX FOR (e:`Label`) ON (e.`f1`, ...)``."""

View on GitHub (pinned to e84aa99b32)

Solutions

  1. Pass the same rel_pk_fields used in build_relationship_upsert (at least one).
  2. If the relationship has no natural key, add a deterministic id property and use it as the PK.
  3. Trace where pk_fields comes from and fix the empty source (schema config or filtering logic).

Example fix

// before
build_relationship_delete(rel_type="WORKS_AT", pk_fields=[])
// after
build_relationship_delete(rel_type="WORKS_AT", pk_fields=["since"])
Defensive patterns

Strategy: validation

Validate before calling

if not pk_fields:
    raise ValueError('relationship needs PK fields before delete')
cypher = build_relationship_delete(rel_type, pk_fields)

Type guard

def has_pk_fields(pk_fields: object) -> bool:
    return isinstance(pk_fields, (list, tuple)) and len(pk_fields) > 0

Try / catch

try:
    cypher = build_relationship_delete(rel_type, pk_fields)
except ValueError as e:
    logger.error('cannot build relationship delete: %s', e)
    raise ConfigError(f'relationship {rel_type!r} has no PK to match for delete') from e

Prevention

When it happens

Trigger: Calling build_relationship_delete(rel_type, pk_fields=[]) — the relationship's key field list is empty (mirroring the upsert requirement).

Common situations: Relationship declared without its own primary key; cleanup/reconcile path receiving an empty key list because the schema never defined rel keys; dynamic key list built from optional properties.

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


AI-assisted analysis of cocoindex-io/cocoindex@e84aa99b32 (2026-09-08). Data as JSON: /api/errors/8dfa4b30abfa87a9. Report an issue: GitHub.