{"record":{"id":"43fcfcf6acf2d5b3","repo":"cocoindex-io/cocoindex","slug":"build-node-upsert-requires-at-least-one-primary-ke-43fcfc","errorCode":null,"errorMessage":"build_node_upsert requires at least one primary key field","messagePattern":"build_node_upsert requires at least one primary key field","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/cocoindex/connectors/neo4j/_cypher.py","lineNumber":101,"sourceCode":"\n\ndef vector_index_name(label: str, field: str) -> str:\n    \"\"\"Deterministic vector index name for a (label, field) pair.\"\"\"\n    return f\"coco_vec_{label}__{field}\"\n\n\ndef build_node_upsert(\n    label: str,\n    pk_fields: Sequence[str],\n    has_value_fields: bool,\n) -> str:\n    \"\"\"``MERGE (n:`Label` {pk: $key_0, ...}) [SET n += $props]``.\n\n    Same shape as FalkorDB — Neo4j 5 understands the literal property\n    pattern in MERGE just fine.\n    \"\"\"\n    if not pk_fields:\n        raise ValueError(\"build_node_upsert requires at least one primary key field\")\n    cypher = f\"MERGE (n:{_quote(label)} {_key_clause('key', pk_fields)})\"\n    if has_value_fields:\n        cypher += \" SET n += $props\"\n    return cypher\n\n\ndef build_node_delete(label: str, pk_fields: Sequence[str]) -> str:\n    \"\"\"``MATCH (n:`Label` {pk: $key_0, ...}) DETACH DELETE n``.\n\n    DETACH DELETE removes any incident edges as a safety measure for nodes\n    that are also referenced as relationship endpoints — without it, the\n    DELETE would fail on nodes that still have edges.\n    \"\"\"\n    if not pk_fields:\n        raise ValueError(\"build_node_delete requires at least one primary key field\")\n    return f\"MATCH (n:{_quote(label)} {_key_clause('key', pk_fields)}) DETACH DELETE n\"\n\n","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/cocoindex-io/cocoindex/blob/e84aa99b3292c5270a4b313b2a7137ad9ce8ab3b/python/cocoindex/connectors/neo4j/_cypher.py#L83-L119","documentation":"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.","triggerScenarios":"Calling build_node_upsert (or wiring a node table handler) with pk_fields=[] — i.e. the table spec has no primary key fields declared.","commonSituations":"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.","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"],"exampleFix":"// before\nbuild_node_upsert(label=\"Person\", pk_fields=[], has_value_fields=True)\n// after\nbuild_node_upsert(label=\"Person\", pk_fields=[\"id\"], has_value_fields=True)","handlingStrategy":"validation","validationCode":"pk = list(table_spec.primary_key)\nif not pk:\n    raise ValueError(\"node table must declare at least one primary key field before upsert\")\nbuild_node_upsert(label=label, pk_fields=pk, has_value_fields=True)","typeGuard":"def has_pk(schema) -> bool:\n    return len(getattr(schema, 'primary_key', ()) ) > 0","tryCatchPattern":"try:\n    cypher = build_node_upsert(label, pk_fields, has_value_fields)\nexcept ValueError as e:\n    if \"at least one primary key\" in str(e):\n        raise SystemExit(f\"configure a primary key for node {label!r}\") from e\n    raise","preventionTips":["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"],"tags":["neo4j","cypher","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"}