risingwavelabs/risingwave · error · SinkError::Config

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

Error message

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

What it means

Required-option guard in SqlServerConfig::from_btreemap: an enumerated with-clause option (such as the sink `type`) was set to a value outside the allowed alternatives; the message lists the accepted values. Fires when the property passes serde but fails the explicit value check.

Source

Thrown at src/connector/src/sink/sqlserver.rs:88

    pub r#type: String, // accept "append-only" or "upsert"

    #[serde(flatten)]
    pub unknown_fields: std::collections::HashMap<String, String>,
}

crate::impl_sink_unknown_fields!(SqlServerConfig);

pub fn sql_server_default_schema() -> String {
    "dbo".to_owned()
}

impl SqlServerConfig {
    pub fn from_btreemap(properties: BTreeMap<String, String>) -> Result<Self> {
        let config =
            serde_json::from_value::<SqlServerConfig>(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
            )));
        }
        Ok(config)
    }

    pub fn full_object_path(&self) -> String {
        format!("[{}].[{}].[{}]", self.database, self.schema, self.table)
    }
}

impl EnforceSecret for SqlServerConfig {
    const ENFORCE_SECRET_PROPERTIES: Set<&'static str> = phf_set! {
        "sqlserver.password"
    };

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Set the `type` option to exactly 'append_only' or 'upsert' in the sink WITH clause
  2. Check for typos/extra whitespace/case mismatch in the value
  3. Review the SQL Server sink documentation for the accepted sink types

Example fix

// before
WITH ( 'type' = 'append-only' )
// after
WITH ( 'type' = 'append_only' )
Defensive patterns

Strategy: validation

Validate before calling

fn check_sink_type(props: &BTreeMap<String, String>) -> Result<(), String> {
    match props.get("type").map(String::as_str) {
        Some("append_only") | Some("upsert") => Ok(()),
        Some(other) => Err(format!("type must be append_only or upsert, got {}", other)),
        None => Ok(()),
    }
}

Type guard

fn is_valid_sink_type(s: &str) -> bool { s == "append_only" || s == "upsert" }

Try / catch

match create_sink(cfg) {
    Err(e) if e.to_string().contains("must be append_only, or upsert") => fix_and_retry_with_valid_sink_type(),
    other => other,
}

Prevention

When it happens

Trigger: Creating a SQL Server sink with WITH option `type` set to anything other than SINK_TYPE_APPEND_ONLY ('append_only') or SINK_TYPE_UPSERT ('upsert').

Common situations: Typo like 'append-only' or 'upsert_only', copying `type='debezium'` or other sink-type values from a different connector's config.

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/39292ba128d95687. Report an issue: GitHub.