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
- Set the property to exactly `true` or `false` in lowercase, e.g. `'is_exactly_once'='true'`.
- Remove the property entirely if you want the default behavior (defaults to true).
- 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
- Always write boolean sink properties in lowercase: 'true'/'false'.
- Omit the property when you want the default instead of guessing a disable value.
- Test CREATE SINK DDL against a dev cluster before running in production.
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
- `compaction-interval-sec` must be greater than 0 when…
- `compaction.max_snapshots_num` must be greater than 0, got
- `compaction.target_file_size_mb` must be greater than 0
- `compaction.type` must not be set when `write_mode` is…
- `compaction.write_parquet_compression` must be one of
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 tablesView on GitHub (pinned to 6469eb736d)