risingwavelabs/risingwave · error · SinkError::Config

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

Error message

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

What it means

After deserializing, `from_btreemap` checks that the `type` option equals `append-only` or `upsert`. Any other value raises this `SinkError::Config`, telling the user which values are accepted (`{}` placeholders expand to the option name 'type', 'append-only', 'upsert'). StarRocks sink semantics differ fundamentally between the two modes, so no other value is meaningful.

Source

Thrown at src/connector/src/sink/starrocks.rs:161

crate::impl_sink_unknown_fields!(StarrocksConfig);

impl EnforceSecret for StarrocksConfig {
    fn enforce_one(prop: &str) -> crate::error::ConnectorResult<()> {
        StarrocksCommon::enforce_one(prop)
    }
}

fn default_commit_checkpoint_interval() -> u64 {
    DEFAULT_COMMIT_CHECKPOINT_INTERVAL_WITH_SINK_DECOUPLE
}

impl StarrocksConfig {
    pub fn from_btreemap(properties: BTreeMap<String, String>) -> Result<Self> {
        let config =
            serde_json::from_value::<StarrocksConfig>(serde_json::to_value(properties).unwrap())
                .map_err(|e| SinkError::Config(anyhow!(e)))?;
        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.commit_checkpoint_interval == 0 {
            return Err(SinkError::Config(anyhow!(
                "`commit_checkpoint_interval` must be greater than 0"
            )));
        }
        if let Some(0) = config.max_batch_size_bytes {
            return Err(SinkError::Config(anyhow!(
                "`starrocks.max_batch_size_bytes` must be greater than 0"
            )));
        }
        Ok(config)
    }

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Set `type='append-only'` or `type='upsert'` (exact lowercase strings) in the WITH options.
  2. Use 'upsert' if the target StarRocks table has a key model and you need updates/deletes; 'append-only' for pure inserts.
  3. Check other connector examples you copied — the `type` vocabulary is sink-specific in RisingWave.

Example fix

-- before
CREATE SINK s FROM mv WITH (connector='starrocks', type='append', ...);
-- after
CREATE SINK s FROM mv WITH (connector='starrocks', type='append-only', ...);
Defensive patterns

Strategy: validation

Validate before calling

// Rust: check the type option before constructing the sink
fn valid_sink_type(props: &BTreeMap<String, String>) -> bool {
    matches!(props.get("type").map(String::as_str), Some("append-only") | Some("upsert"))
}

Type guard

fn is_valid_sink_type(v: &str) -> bool { v == "append-only" || v == "upsert" }

Try / catch

match StarrocksConfig::from_btreemap(props) {
    Err(e) if e.to_string().contains("must be append-only, or upsert") => {
        eprintln!("set type='append-only' or type='upsert'");
    }
    other => other,
}

Prevention

When it happens

Trigger: Creating a StarRocks sink with `type='insert'`, `type='append'`, `type='AppendOnly'` (wrong case), or any string other than the two accepted constants `SINK_TYPE_APPEND_ONLY` / `SINK_TYPE_UPSERT`.

Common situations: Copy-pasting connector examples from other sinks (e.g. Kafka's 'append'); case-sensitivity mistakes; assuming 'upsert' is spelled 'Upsert' or 'debezium'.

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/394a674c5de5f83d. Report an issue: GitHub.