cocoindex-io/cocoindex · error

build_relationship_upsert requires PK fields for from, to…

Error message

build_relationship_upsert requires PK fields for from, to, and the relationship

What it means

build_relationship_upsert() requires non-empty primary key fields for the source node, the target node, AND the relationship itself, because it emits three MERGE clauses — one per endpoint and one for the relationship — each of which needs key properties to match on. If any of the three lists is empty it raises instead of producing a query that would match non-deterministically.

Solutions

  1. Provide PK fields for both endpoints (the same keys their node tables use) and at least one PK field for the relationship (add an id/hash property if it has no natural key).
  2. Verify the relationship mapping's key_fields configuration covers from, to, and rel entries.
  3. Check upstream field filtering that may strip the key columns.

Example fix

// before
build_relationship_upsert(from_label="Person", from_pk_fields=[], to_label="Company", to_pk_fields=["id"], rel_type="WORKS_AT", rel_pk_fields=[])
// after
build_relationship_upsert(from_label="Person", from_pk_fields=["person_id"], to_label="Company", to_pk_fields=["id"], rel_type="WORKS_AT", rel_pk_fields=["since"])
Defensive patterns

Strategy: validation

Validate before calling

for name, fields in (('from', from_pk_fields), ('to', to_pk_fields), ('rel', rel_pk_fields)):
    if not fields:
        raise ValueError(f'relationship needs PK fields for {name}')
cypher = build_relationship_upsert(from_label, from_pk_fields, to_label, to_pk_fields, rel_type, rel_pk_fields, ...)

Type guard

def all_have_keys(*field_lists: object) -> bool:
    return all(isinstance(f, (list, tuple)) and len(f) > 0 for f in field_lists)

Try / catch

try:
    cypher = build_relationship_upsert(...)
except ValueError as e:
    logger.error('relationship upsert misconfigured: %s', e)
    raise ConfigError(f'relationship {rel_type!r} must define PKs for endpoints and itself') from e

Prevention

When it happens

Trigger: Calling build_relationship_upsert with from_pk_fields=[], to_pk_fields=[], or rel_pk_fields=[] — e.g. a relationship mapping that defines endpoint labels but no key fields, or a relationship with no properties so the author passed an empty rel key list.

Common situations: Relationship schema omitted the relationship's own primary key assuming it wasn't needed; endpoint node tables declared without PKs; refactoring renamed key fields and the lists 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/2aca4693cd7e8eff. Report an issue: GitHub.

Appendix: source

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

    )


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:
        raise ValueError(
            "build_relationship_upsert requires PK fields for from, to, and the relationship"
        )
    cypher = (
        f"MERGE (s:{_quote(from_label)} {_key_clause('from_key', from_pk_fields, 's')}) "
        f"MERGE (t:{_quote(to_label)} {_key_clause('to_key', to_pk_fields, 't')}) "
        f"MERGE (s)-[r:{_quote(rel_type)} {_key_clause('rel_key', rel_pk_fields, 'r')}]->(t)"
    )
    if has_value_fields:
        cypher += " SET r += $props"
    return cypher


def build_relationship_delete(rel_type: str, pk_fields: Sequence[str]) -> str:
    """``MATCH ()-[r:`RelType` {pk: $key_0, ...}]->() DELETE r``.

    Endpoints are intentionally not deleted — they're tracked by their own
    table handlers and will be deleted by their own reconciler if orphaned.
    """

View on GitHub (pinned to e84aa99b32)