risingwavelabs/risingwave · error · SinkError::Config

invalid value for `is_exactly_once`: expected `true` or…

Error message

invalid value for `is_exactly_once`: expected `true` or `false`, got `{value}`

What it means

The Iceberg sink's `is_exactly_once` helper parses the optional `is_exactly_once` sink property as a Rust bool. If the property is absent it defaults to true, but if present it must be exactly `true` or `false` (Rust bool parsing); any other string makes the sink construction fail with this config error.

Solutions

  1. Set the property to exactly `true` or `false` in lowercase, e.g. `'is_exactly_once'='true'`.
  2. Remove the property entirely if you want the default behavior (defaults to true).
  3. Check the ALTER SINK / CREATE SINK statement for stray quotes, whitespace, or templating artifacts around the value.

Example fix

-- before
CREATE SINK s FROM mv WITH (connector='iceberg', 'is_exactly_once'='True');
-- after
CREATE SINK s FROM mv WITH (connector='iceberg', 'is_exactly_once'='true');
Defensive patterns

Strategy: validation

Validate before calling

fn valid_bool_prop(props: &std::collections::BTreeMap<String, String>) -> Result<(), String> {
    if let Some(v) = props.get("is_exactly_once") {
        if v != "true" && v != "false" {
            return Err(format!("is_exactly_once must be 'true' or 'false', got {v:?}"));
        }
    }
    Ok(())
}

Prevention

When it happens

Trigger: Creating or altering an Iceberg sink with a property like `is_exactly_once = 'True'`, `'1'`, `'yes'`, or `' true'` (whitespace or capitalized). Rust's `str::parse::<bool>()` only accepts lowercase `true`/`false`.

Common situations: Users porting configs from systems that accept `True`/`1`/`yes`, templated SQL that interpolates an untyped value, or hand-edited sink DDL with typos.

Understand the failure class

Background: "unknown output mode", "invalid value for flag", "expects true/false": fixing invalid flag value errors in CLI tools — this error's family across 24 libraries.

Related errors


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

Appendix: source

Thrown at src/connector/src/sink/iceberg/mod.rs:221

            param,
            upsert_primary_key_column_names,
        })
    }
}

impl Sink for IcebergSink {
    type LogSinker = CoordinatedLogSinker<IcebergSinkWriter>;

    const SINK_NAME: &'static str = ICEBERG_SINK;

    crate::impl_validate_sink_unknown_fields!();

    fn is_exactly_once(properties: &BTreeMap<String, String>) -> Result<bool> {
        let Some(value) = properties.get("is_exactly_once") else {
            return Ok(true);
        };
        value.parse::<bool>().map_err(|_| {
            SinkError::Config(anyhow!(
                "invalid value for `is_exactly_once`: expected `true` or `false`, got `{value}`"
            ))
        })
    }

    async fn validate(&self) -> Result<()> {
        let catalog_kind = self.config.catalog_kind()?;
        if matches!(catalog_kind, IcebergCatalogKind::Snowflake) {
            bail!("Snowflake catalog only supports iceberg sources");
        }

        if matches!(catalog_kind, IcebergCatalogKind::Glue(_)) {
            risingwave_common::license::Feature::IcebergSinkWithGlue
                .check_available()
                .map_err(|e| anyhow::anyhow!(e))?;
        }

        // Enforce merge-on-read for append-only tables

View on GitHub (pinned to 6469eb736d)