{"record":{"id":"4599570065c30d67","repo":"pathwaycom/pathway","slug":"primary-key-can-only-be-specified-for-the-snapshot-459957","errorCode":null,"errorMessage":"primary_key can only be specified for the snapshot table type","messagePattern":"primary_key can only be specified for the snapshot table type","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/pathway/io/postgres/__init__.py","lineNumber":881,"sourceCode":"\n    data_storage = api.DataStorage(\n        storage_type=\"postgres\",\n        connection_string=_connection_string_from_settings(postgres_settings),\n        max_batch_size=max_batch_size,\n        table_name=table_name,\n        schema_name=schema_name,\n        table_writer_init_mode=init_mode_from_str(init_mode),\n        snapshot_maintenance_on_output=is_snapshot_mode,\n        tls_settings=tls.settings,\n    )\n\n    if not is_snapshot_mode:\n        if _external_diff_column is not None:\n            raise ValueError(\n                \"_external_diff_column is only supported for the snapshot table type\"\n            )\n        if primary_key is not None:\n            raise ValueError(\n                \"primary_key can only be specified for the snapshot table type\"\n            )\n    else:\n        # Snapshot mode requires at least one primary-key column —\n        # the writer's INSERT ... ON CONFLICT (...) DO UPDATE\n        # statement is malformed without one. If we let an empty list\n        # reach the engine it would error out only AFTER ``init_mode``\n        # has already mutated the destination (CREATE TABLE for\n        # ``\"replace\"`` / ``\"create_if_not_exists\"``), and under\n        # multi-worker (PATHWAY_THREADS > 1) the worker that loses the\n        # CREATE race observes the partially-created table and\n        # surfaces a less specific error instead — making any\n        # message-based test flaky. Reject at call time so no DB side\n        # effect happens.\n        if primary_key is None or len(primary_key) == 0:\n            raise ValueError(\n                \"primary key field names must be specified for a snapshot mode\"\n            )","sourceCodeStart":863,"sourceCodeEnd":899,"githubUrl":"https://github.com/pathwaycom/pathway/blob/fa2f74a4649b7c5908690cf60137263d8d80de5f/python/pathway/io/postgres/__init__.py#L863-L899","documentation":"Raised by pw.io.postgres.write() when primary_key is passed while output_table_type is not \"snapshot\". Keys and UPSERT semantics exist only in snapshot mode; the default stream_of_changes mode appends every change, so a key would be meaningless and is rejected.","triggerScenarios":"pw.io.postgres.write(table, parts, \"tbl\", primary_key=[table.id]) with default output_table_type.","commonSituations":"Users expecting an ON CONFLICT upsert by default; copy-pasting a snapshot example call without the output_table_type line.","solutions":["Add output_table_type=\"snapshot\" to the call.","If you truly want append-only history, remove primary_key (the time/diff columns encode changes)."],"exampleFix":"# before\npw.io.postgres.write(t, parts, \"tbl\", primary_key=[t.id])\n# after\npw.io.postgres.write(t, parts, \"tbl\", output_table_type=\"snapshot\", primary_key=[t.id])","handlingStrategy":"validation","validationCode":"if primary_key is not None:\n    assert output_table_type == \"snapshot\", \"primary_key requires output_table_type='snapshot'\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Decide up front whether the destination is append-only history or a keyed current-state snapshot; set output_table_type accordingly.","Keep primary_key and output_table_type adjacent in write() call templates."],"tags":["postgres","pathway","snapshot","primary-key","api-misuse"],"backgroundTag":null,"analyzedSha":"fa2f74a4649b7c5908690cf60137263d8d80de5f","analyzedAt":"2026-08-15T01:48:17.006Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}