cocoindex-io/cocoindex · error

build_node_delete requires at least one primary key field

Error message

build_node_delete requires at least one primary key field

What it means

build_node_delete() refuses to build a MATCH ... DETACH DELETE query when pk_fields is empty. Identifying which node to delete requires at least one key property; with none, the query would either delete all nodes or be invalid, so the library raises instead.

Source

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

    write; otherwise the MERGE alone suffices.
    """
    if not pk_fields:
        raise ValueError("build_node_upsert requires at least one primary key field")
    cypher = f"MERGE (n:{_quote(label)} {_key_clause('key', pk_fields, 'n')})"
    if has_value_fields:
        cypher += " SET n += $props"
    return cypher


def build_node_delete(label: str, pk_fields: Sequence[str]) -> str:
    """``MATCH (n:`Label` {pk: $key_0, ...}) DETACH DELETE n``.

    DETACH DELETE removes any incident edges as a safety measure for nodes that
    are also referenced as relationship endpoints — without it, the DELETE
    would fail on nodes that still have edges.
    """
    if not pk_fields:
        raise ValueError("build_node_delete requires at least one primary key field")
    return (
        f"MATCH (n:{_quote(label)} {_key_clause('key', pk_fields, 'n')}) "
        f"DETACH DELETE n"
    )


def build_relationship_upsert(
    rel_type: str,
    from_label: str,
    from_pk_fields: Sequence[str],
    to_label: str,
    to_pk_fields: Sequence[str],
    rel_pk_fields: Sequence[str],
    has_value_fields: bool,
) -> str:
    """Three MERGEs: source endpoint, target endpoint, then the relationship.

    Endpoint properties are NOT touched — they are owned by their table's own

View on GitHub (pinned to e84aa99b32)

Solutions

  1. Add at least one primary key field to the node table schema and pass it as pk_fields.
  2. Inspect how pk_fields is constructed upstream (config parsing, field filtering) to see why it is empty.
  3. Use the same key fields for delete as for upsert so matched nodes are consistent.

Example fix

// before
build_node_delete(label="Person", pk_fields=[])
// after
build_node_delete(label="Person", pk_fields=["person_id"])
Defensive patterns

Strategy: validation

Validate before calling

if not pk_fields:
    raise ValueError('node table needs at least one primary key field before delete')
cypher = build_node_delete(label, 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_node_delete(label, pk_fields)
except ValueError as e:
    logger.error('cannot build node delete: %s', e)
    raise ConfigError(f'table {label!r} has no primary key to match for delete') from e

Prevention

When it happens

Trigger: Calling build_node_delete(label, pk_fields=[]) — deleting nodes for a table/handler whose primary key field list is empty.

Common situations: Same root cause as the upsert case: schema defined without a primary key; reconciler cleanup path for a mis-declared table; pk fields list built dynamically and ended up empty.

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/5147785a0b34e844. Report an issue: GitHub.