cocoindex-io/cocoindex · error · ValueError
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 generates MERGE (n:Label {pk: $key_0, ...}); the MERGE pattern requires at least one key property to match nodes on. An empty pk_fields list would produce invalid Cypher, so it raises ValueError.
Source
Thrown at python/cocoindex/connectors/neo4j/_cypher.py:101
def vector_index_name(label: str, field: str) -> str:
"""Deterministic vector index name for a (label, field) pair."""
return f"coco_vec_{label}__{field}"
def build_node_upsert(
label: str,
pk_fields: Sequence[str],
has_value_fields: bool,
) -> str:
"""``MERGE (n:`Label` {pk: $key_0, ...}) [SET n += $props]``.
Same shape as FalkorDB — Neo4j 5 understands the literal property
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"
View on GitHub (pinned to e84aa99b32)
Solutions
- Declare at least one primary key field in the node's table spec
- If multiple candidate keys exist, pick a stable one as the PK before building the upsert
- Guard the call site: raise your own error early when schema.primary_key is empty
Example fix
// before build_node_upsert(label="Person", pk_fields=[], has_value_fields=True) // after build_node_upsert(label="Person", pk_fields=["id"], has_value_fields=True)
Defensive patterns
Strategy: validation
Validate before calling
pk = list(table_spec.primary_key)
if not pk:
raise ValueError("node table must declare at least one primary key field before upsert")
build_node_upsert(label=label, pk_fields=pk, has_value_fields=True) Type guard
def has_pk(schema) -> bool:
return len(getattr(schema, 'primary_key', ()) ) > 0 Try / catch
try:
cypher = build_node_upsert(label, pk_fields, has_value_fields)
except ValueError as e:
if "at least one primary key" in str(e):
raise SystemExit(f"configure a primary key for node {label!r}") from e
raise Prevention
- Always define a primary key on node tables — MERGE semantics require one
- Validate table specs at startup: reject any node spec with empty primary_key
- Use stable business keys (ids) as PKs rather than mutable properties
- Add a unit test that constructs every node handler and asserts non-empty PKs
When it happens
Trigger: Calling build_node_upsert (or wiring a node table handler) with pk_fields=[] — i.e. the table spec has no primary key fields declared.
Common situations: Table defined without a primary key; PK fields stripped by a refactor; programmatically assembled field lists that end up empty because the schema's primary_key was 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
- build_node_delete requires at least one primary key field
- build_relationship_upsert requires PK fields for from, to, a
- build_relationship_delete requires at least one primary key
- build_node_index_create requires at least one field
- build_node_upsert requires at least one primary key field
AI-assisted analysis of cocoindex-io/cocoindex@e84aa99b32 (2026-09-08).
Data as JSON: /api/errors/43fcfcf6acf2d5b3.
Report an issue: GitHub.