cocoindex-io/cocoindex · error

build_node_upsert requires at least one primary key field

Error message

build_node_upsert requires at least one primary key field

What it means

build_node_upsert() refuses to build a MERGE query when pk_fields is empty. A MERGE needs at least one key property to identify the node uniquely; without primary key fields the upsert would match every node or be meaningless, so the library fails fast instead of generating a broken Cypher query.

Source

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

    (e.g. ``var="n"`` makes it clear this clause attaches to ``n``).
    """
    parts = [f"{_quote(f)}: ${prefix}_{i}" for i, f in enumerate(fields)]
    return "{" + ", ".join(parts) + "}"


def build_node_upsert(
    label: str,
    pk_fields: Sequence[str],
    has_value_fields: bool,
) -> str:
    """``MERGE (n:`Label` {pk: $key_0, ...}) [SET n += $props]``.

    ``has_value_fields`` controls whether the ``SET n += $props`` clause is
    emitted. Caller passes ``True`` when there is at least one non-PK column to
    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"

View on GitHub (pinned to e84aa99b32)

Solutions

  1. Declare at least one primary key field in the node table schema (e.g. the unique 'id' column) and pass it in pk_fields.
  2. Check the code that builds the pk_fields list — a filter or optional config may be dropping the key fields.
  3. If the node truly has no natural key, designate a deterministic unique property (e.g. content hash) as the primary key.

Example fix

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

Strategy: validation

Validate before calling

if not pk_fields:
    raise ValueError('node table needs at least one primary key field before upsert')
# proceed with build_node_upsert(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_upsert(label, pk_fields, has_value_fields)
except ValueError as e:
    logger.error('node upsert misconfigured: %s', e)
    raise ConfigError(f'table {label!r} must declare a primary key field') from e

Prevention

When it happens

Trigger: Calling build_node_upsert(label, pk_fields=[], ...) — i.e. declaring a node table/handler with no primary key fields configured.

Common situations: Schema definition omitted the primary key (e.g. forgot to mark 'id' as a key column); a table maps only non-key value columns; a key fields list was built programmatically and filtered to 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/293ebf1869c0813d. Report an issue: GitHub.