risingwavelabs/risingwave · error · SinkError::Config

`{}` must be {}, or {}

Error message

`{}` must be {}, or {}

What it means

This error is raised by `IcebergConfig::from_btreemap` (src/connector/src/sink/iceberg/config.rs:580) when the `type` sink option is neither `'append-only'` nor `'upsert'`. The Iceberg sink supports exactly these two write types; any other value (including typos and empty strings) is rejected with `SinkError::Config` naming the option and the two allowed values.

Source

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

            return Err(SinkError::Config(anyhow!(
                "`enable_pk_index` cannot be true when `force_append_only` is true"
            )));
        }

        Ok(())
    }

    pub fn from_btreemap(values: BTreeMap<String, String>) -> Result<Self> {
        let mut config =
            serde_json::from_value::<IcebergConfig>(serde_json::to_value(&values).unwrap())
                .map_err(|e| SinkError::Config(anyhow!(e)))?;

        if config.enable_compaction && !values.contains_key(COMPACTION_MAX_SNAPSHOTS_NUM) {
            config.max_snapshots_num_before_compaction = Some(DEFAULT_COMPACTION_MAX_SNAPSHOTS_NUM);
        }

        if config.r#type != SINK_TYPE_APPEND_ONLY && config.r#type != SINK_TYPE_UPSERT {
            return Err(SinkError::Config(anyhow!(
                "`{}` must be {}, or {}",
                SINK_TYPE_OPTION,
                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

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Set `type = 'append-only'` or `type = 'upsert'` (exact lowercase spelling) in the sink WITH options.
  2. Remove the `type` option entirely if the default is acceptable and no custom value is needed.
  3. Check the exact string against SINK_TYPE_APPEND_ONLY / SINK_TYPE_UPSERT constants — values are matched case-sensitively, no normalization is applied.

Example fix

-- before
WITH (
  connector = 'iceberg',
  type = 'UPSERT'
);

-- after
WITH (
  connector = 'iceberg',
  type = 'upsert'
);
Defensive patterns

Strategy: validation

Validate before calling

-- allowed values for the iceberg sink type option:
-- type in ('append-only', 'upsert')  -- exact, lowercase, no underscores

Try / catch

-- SinkError::Config at CREATE SINK time; fix `type` to one of the listed values and retry

Prevention

When it happens

Trigger: Creating an Iceberg sink with `type` set to any value other than 'append-only' or 'upsert' — e.g. `type = 'upsert'` misspelled as 'upsert-only'/'Upsert' (values are compared case-sensitively), `type = ''`, or a type copied from a different connector (e.g. 'append_only' with an underscore).

Common situations: Typo in the WITH clause; copying `type` values from other sink connectors with different vocabularies; case mismatch ('UPSERT' vs 'upsert'); omitting the value's quotes so an unexpected literal lands in the map.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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