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.

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.

Appendix: 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)