{"record":{"id":"2aca4693cd7e8eff","repo":"cocoindex-io/cocoindex","slug":"build-relationship-upsert-requires-pk-fields-for-f","errorCode":null,"errorMessage":"build_relationship_upsert requires PK fields for from, to, and the relationship","messagePattern":"build_relationship_upsert requires PK fields for from, to, and the relationship","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"python/cocoindex/connectors/falkordb/_cypher.py","lineNumber":113,"sourceCode":"    )\n\n\ndef build_relationship_upsert(\n    rel_type: str,\n    from_label: str,\n    from_pk_fields: Sequence[str],\n    to_label: str,\n    to_pk_fields: Sequence[str],\n    rel_pk_fields: Sequence[str],\n    has_value_fields: bool,\n) -> str:\n    \"\"\"Three MERGEs: source endpoint, target endpoint, then the relationship.\n\n    Endpoint properties are NOT touched — they are owned by their table's own\n    record handler. We only ``SET r += $props`` on the relationship itself.\n    \"\"\"\n    if not from_pk_fields or not to_pk_fields or not rel_pk_fields:\n        raise ValueError(\n            \"build_relationship_upsert requires PK fields for from, to, and the relationship\"\n        )\n    cypher = (\n        f\"MERGE (s:{_quote(from_label)} {_key_clause('from_key', from_pk_fields, 's')}) \"\n        f\"MERGE (t:{_quote(to_label)} {_key_clause('to_key', to_pk_fields, 't')}) \"\n        f\"MERGE (s)-[r:{_quote(rel_type)} {_key_clause('rel_key', rel_pk_fields, 'r')}]->(t)\"\n    )\n    if has_value_fields:\n        cypher += \" SET r += $props\"\n    return cypher\n\n\ndef build_relationship_delete(rel_type: str, pk_fields: Sequence[str]) -> str:\n    \"\"\"``MATCH ()-[r:`RelType` {pk: $key_0, ...}]->() DELETE r``.\n\n    Endpoints are intentionally not deleted — they're tracked by their own\n    table handlers and will be deleted by their own reconciler if orphaned.\n    \"\"\"","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/cocoindex-io/cocoindex/blob/e84aa99b3292c5270a4b313b2a7137ad9ce8ab3b/python/cocoindex/connectors/falkordb/_cypher.py#L95-L131","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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).","Verify the relationship mapping's key_fields configuration covers from, to, and rel entries.","Check upstream field filtering that may strip the key columns."],"exampleFix":"// before\nbuild_relationship_upsert(from_label=\"Person\", from_pk_fields=[], to_label=\"Company\", to_pk_fields=[\"id\"], rel_type=\"WORKS_AT\", rel_pk_fields=[])\n// after\nbuild_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\"])","handlingStrategy":"validation","validationCode":"for name, fields in (('from', from_pk_fields), ('to', to_pk_fields), ('rel', rel_pk_fields)):\n    if not fields:\n        raise ValueError(f'relationship needs PK fields for {name}')\ncypher = build_relationship_upsert(from_label, from_pk_fields, to_label, to_pk_fields, rel_type, rel_pk_fields, ...)","typeGuard":"def all_have_keys(*field_lists: object) -> bool:\n    return all(isinstance(f, (list, tuple)) and len(f) > 0 for f in field_lists)","tryCatchPattern":"try:\n    cypher = build_relationship_upsert(...)\nexcept ValueError as e:\n    logger.error('relationship upsert misconfigured: %s', e)\n    raise ConfigError(f'relationship {rel_type!r} must define PKs for endpoints and itself') from e","preventionTips":["Define the relationship's own primary key even if it feels optional — add an id or hash property.","Mirror endpoint PKs from the node table definitions instead of duplicating them by hand.","Validate the full relationship mapping (labels + three key lists) when it is loaded, not at query build time."],"tags":["cypher","falkordb","relationship","primary-key"],"backgroundTag":"missing-required-argument","analyzedSha":"e84aa99b3292c5270a4b313b2a7137ad9ce8ab3b","analyzedAt":"2026-09-08T15:59:19.997Z","contentChangedAt":"2026-09-08T15:59:19.997Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}