risingwavelabs/risingwave · error · SinkError::Config

Must set `primary-key` in {}

Error message

Must set `primary-key` in {}

What it means

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.

Source

Thrown at src/connector/src/sink/iceberg/config.rs:602

                SINK_TYPE_APPEND_ONLY,
                SINK_TYPE_UPSERT
            )));
        }

        if config.r#type == SINK_TYPE_UPSERT {
            if let Some(primary_key) = &config.primary_key {
                if primary_key.is_empty() {
                    return Err(SinkError::Config(anyhow!(
                        "`primary-key` must not be empty in {}",
                        SINK_TYPE_UPSERT
                    )));
                }
            } else if !config.enable_pk_index {
                // When `enable_pk_index = true`, the planner auto-derives the iceberg pk
                // from the upstream stream key, so the user does not need to spell it out
                // in WITH options. The derived pk is written back into properties before
                // this validation is consulted again at sink-construction time.
                return Err(SinkError::Config(anyhow!(
                    "Must set `primary-key` in {}",
                    SINK_TYPE_UPSERT
                )));
            }
        }

        // Enforce merge-on-read for append-only sinks
        Self::validate_append_only_write_mode(&config.r#type, config.write_mode)?;
        config.validate_enable_pk_index()?;
        config.validate_manifest_rewrite_format(config.format_version)?;

        // All configs start with "catalog." will be treated as java configs.
        config.java_catalog_props = iceberg_java_catalog_props_from_options(
            values
                .iter()
                .map(|(key, value)| (key.as_str(), value.as_str())),
        );
        config

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Add `primary_key='<col1,col2>'` to the WITH options naming the upsert key columns.
  2. Set `enable_pk_index=true` in the WITH options so the planner derives the Iceberg pk from the upstream stream key.
  3. Change `type` to `append-only` if upsert semantics are not needed.

Example fix

// before
WITH (connector = 'iceberg', type = 'upsert')
// after
WITH (connector = 'iceberg', type = 'upsert', primary_key = 'order_id')
Defensive patterns

Strategy: validation

Validate before calling

if (opts.type === 'upsert' && !opts.primary_key && !opts.enable_pk_index) {
  throw new Error("Must set `primary-key` in upsert");
}

Type guard

function upsertKeyReady(opts) {
  return (typeof opts.primary_key === 'string' && opts.primary_key.length > 0) || opts.enable_pk_index === true;
}

Try / catch

try {
  await createIcebergSink(opts);
} catch (e) {
  if (String(e).includes('Must set `primary-key`')) {
    opts.enable_pk_index = true;
    return createIcebergSink(opts);
  }
  throw e;
}

Prevention

When it happens

Trigger: CREATE SINK with `type='upsert'` on an Iceberg connector where the WITH options contain no `primary_key` and `enable_pk_index` is false/unset.

Common situations: 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.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/93fe7ce88908dc76. Report an issue: GitHub.