risingwavelabs/risingwave · error · SinkError
`{}` must be {}, or {}
Error message
`{}` must be {}, or {} What it means
S3 file sink config validation in from_btreemap: the `type` property (r#type) held a value other than the two supported sink types (append-only / deb-mutating, per the `{}` must be {}, or {} template). It is a strict enum guard run right after serde-parsing S3Config, so a typo'd or unsupported sink type string triggers it.
Source
Thrown at src/connector/src/sink/file_sink/s3.rs:144
impl UnknownFields for S3Config {
fn unknown_fields(&self) -> HashMap<String, String> {
self.unknown_fields.clone()
}
}
crate::impl_sink_unknown_fields!(S3Config);
impl OpendalSinkBackend for S3Sink {
type Properties = S3Config;
const SINK_NAME: &'static str = S3_SINK;
fn from_btreemap(btree_map: BTreeMap<String, String>) -> Result<Self::Properties> {
let config = serde_json::from_value::<S3Config>(serde_json::to_value(btree_map).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)
}
fn new_operator(properties: S3Config) -> Result<Operator> {
FileSink::<S3Sink>::new_s3_sink(&properties.common)
}
fn get_path(properties: Self::Properties) -> String {
properties.common.path.unwrap_or_default()
}
fn get_engine_type() -> super::opendal_sink::EngineType {View on GitHub (pinned to 6469eb736d)
Solutions
- Set `type='append-only'` or `type='upsert'` in the WITH clause
- Check exact spelling/hyphenation of the allowed values
- If unsure, omit `type` if your use case matches the default, or consult docs for sink type semantics
Example fix
// before WITH (connector='s3', type='append_only', ...) // after WITH (connector='s3', type='append-only', ...)
Defensive patterns
Strategy: validation
Validate before calling
const allowed = ['append-only', 'upsert'];
if (opts.type !== undefined && !allowed.includes(opts.type)) {
throw new Error(`type must be one of ${allowed.join(', ')}`);
} Type guard
const isValidSinkType = (t) => t === 'append-only' || t === 'upsert';
Try / catch
try { createSink(opts); } catch (e) { if (String(e).includes('must be append-only, or upsert')) { opts.type = 'append-only'; createSink(opts); } else { throw e; } } Prevention
- Use the exact hyphenated lowercase literals for sink `type`
- Standardize sink DDL templates per backend
- Map application semantics (append vs upsert) to the correct type in your deploy tooling
When it happens
Trigger: `CREATE SINK ... WITH (connector='s3', type='xxx')` where `xxx` is neither SINK_TYPE_APPEND_ONLY nor SINK_TYPE_UPSERT (including typos like 'append_only' vs 'append-only').
Common situations: Typo in the type option, mixing up RisingWave's sink `type` with an unrelated 's3 output type' option from another system, or omitting separator conventions.
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
- `commit_checkpoint_interval` must be greater than 0
- `{}` must be {}, or {}
- `{}` must be {}, or {}
- `{}` must be {}, or {}
- {e}
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/cd578468f5cc3d60.
Report an issue: GitHub.