{"record":{"id":"93fe7ce88908dc76","repo":"risingwavelabs/risingwave","slug":"must-set-primary-key-in","errorCode":null,"errorMessage":"Must set `primary-key` in {}","messagePattern":"Must set `primary-key` in (.+?)","errorType":"validation","errorClass":"SinkError::Config","httpStatus":null,"severity":"error","filePath":"src/connector/src/sink/iceberg/config.rs","lineNumber":602,"sourceCode":"                SINK_TYPE_APPEND_ONLY,\n                SINK_TYPE_UPSERT\n            )));\n        }\n\n        if config.r#type == SINK_TYPE_UPSERT {\n            if let Some(primary_key) = &config.primary_key {\n                if primary_key.is_empty() {\n                    return Err(SinkError::Config(anyhow!(\n                        \"`primary-key` must not be empty in {}\",\n                        SINK_TYPE_UPSERT\n                    )));\n                }\n            } else if !config.enable_pk_index {\n                // When `enable_pk_index = true`, the planner auto-derives the iceberg pk\n                // from the upstream stream key, so the user does not need to spell it out\n                // in WITH options. The derived pk is written back into properties before\n                // this validation is consulted again at sink-construction time.\n                return Err(SinkError::Config(anyhow!(\n                    \"Must set `primary-key` in {}\",\n                    SINK_TYPE_UPSERT\n                )));\n            }\n        }\n\n        // Enforce merge-on-read for append-only sinks\n        Self::validate_append_only_write_mode(&config.r#type, config.write_mode)?;\n        config.validate_enable_pk_index()?;\n        config.validate_manifest_rewrite_format(config.format_version)?;\n\n        // All configs start with \"catalog.\" will be treated as java configs.\n        config.java_catalog_props = iceberg_java_catalog_props_from_options(\n            values\n                .iter()\n                .map(|(key, value)| (key.as_str(), value.as_str())),\n        );\n        config","sourceCodeStart":584,"sourceCodeEnd":620,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/connector/src/sink/iceberg/config.rs#L584-L620","documentation":"This error is raised by IcebergSinkConfig::from_btreemap when sink type is 'upsert' but the `primary_key` option is entirely absent and pk-index auto-derivation is not enabled. An upsert Iceberg sink must know which columns form the primary key to merge rows; without it the configuration is invalid.","triggerScenarios":"CREATE SINK with `type='upsert'` on an Iceberg connector where the WITH options contain no `primary_key` and `enable_pk_index` is false/unset.","commonSituations":"Users forgetting the `primary_key` option when converting an append-only sink to upsert; older DDL scripts predating `enable_pk_index`; schemas where the upstream has no natural key.","solutions":["Add `primary_key='<col1,col2>'` to the WITH options naming the upsert key columns.","Set `enable_pk_index=true` in the WITH options so the planner derives the Iceberg pk from the upstream stream key.","Change `type` to `append-only` if upsert semantics are not needed."],"exampleFix":"// before\nWITH (connector = 'iceberg', type = 'upsert')\n// after\nWITH (connector = 'iceberg', type = 'upsert', primary_key = 'order_id')","handlingStrategy":"validation","validationCode":"if (opts.type === 'upsert' && !opts.primary_key && !opts.enable_pk_index) {\n  throw new Error(\"Must set `primary-key` in upsert\");\n}","typeGuard":"function upsertKeyReady(opts) {\n  return (typeof opts.primary_key === 'string' && opts.primary_key.length > 0) || opts.enable_pk_index === true;\n}","tryCatchPattern":"try {\n  await createIcebergSink(opts);\n} catch (e) {\n  if (String(e).includes('Must set `primary-key`')) {\n    opts.enable_pk_index = true;\n    return createIcebergSink(opts);\n  }\n  throw e;\n}","preventionTips":["Always pair type='upsert' with either primary_key or enable_pk_index=true.","Lint generated sink DDL for required upsert options.","Derive the pk from the stream key at DDL-generation time."],"tags":["iceberg","sink","config-validation","upsert"],"backgroundTag":"missing-required-config-field","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}