cocoindex-io/cocoindex · error · ValueError

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 generates MATCH (n:Label {pk: ...}) DETACH DELETE n, which needs at least one key property to locate the node. Empty pk_fields would yield a malformed match pattern, so it raises ValueError.

Source

Thrown at python/cocoindex/connectors/neo4j/_cypher.py:116

    pattern in MERGE just fine.
    """
    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)})"
    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)}) 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
    record handler. We only ``SET r += $props`` on the relationship itself.
    """
    if not from_pk_fields or not to_pk_fields or not rel_pk_fields:

View on GitHub (pinned to e84aa99b32)

Solutions

  1. Ensure the node's table spec declares a primary key so delete handlers receive pk fields
  2. Pass the same PK fields used for the upsert to build_node_delete
  3. Guard callers to refuse empty PK lists with a clear message before query construction

Example fix

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

Strategy: validation

Validate before calling

if not pk_fields:
    raise ValueError("cannot build node delete without primary key fields")
cypher = build_node_delete(label=label, pk_fields=pk_fields)

Type guard

def non_empty_strs(seq) -> bool:
    return isinstance(seq, (list, tuple)) and len(seq) > 0 and all(isinstance(x, str) for x in seq)

Try / catch

try:
    cypher = build_node_delete(label, pk_fields)
except ValueError as e:
    if "at least one primary key" in str(e):
        logger.error("node %r has no PK; deletes would be unmatchable", label)
        raise
    raise

Prevention

When it happens

Trigger: Calling build_node_delete with pk_fields=[] — the node spec has no primary key fields when generating the delete statement.

Common situations: Same root cause as the upsert variant: schema declared without a primary key, or an empty field list computed dynamically before deletion reconciliation.

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