risingwavelabs/risingwave · error · SinkError::Config
`primary-key` must not be empty in {}
Error message
`primary-key` must not be empty in {} What it means
This error is raised by IcebergSinkConfig::from_btreemap when a sink is created with type = 'upsert' and the `primary_key` option is present but set to an empty string. RisingWave requires an explicit non-empty primary key for upsert sinks so it can deduplicate and merge rows in the Iceberg table. The check rejects empty strings before the sink is constructed.
Source
Thrown at src/connector/src/sink/iceberg/config.rs:592
.map_err(|e| SinkError::Config(anyhow!(e)))?;
if config.enable_compaction && !values.contains_key(COMPACTION_MAX_SNAPSHOTS_NUM) {
config.max_snapshots_num_before_compaction = Some(DEFAULT_COMPACTION_MAX_SNAPSHOTS_NUM);
}
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.r#type == SINK_TYPE_UPSERT {
if let Some(primary_key) = &config.primary_key {
if primary_key.is_empty() {
return Err(SinkError::Config(anyhow!(
"`primary-key` must not be empty in {}",
SINK_TYPE_UPSERT
)));
}
} else if !config.enable_pk_index {
// When `enable_pk_index = true`, the planner auto-derives the iceberg pk
// from the upstream stream key, so the user does not need to spell it out
// in WITH options. The derived pk is written back into properties before
// this validation is consulted again at sink-construction time.
return Err(SinkError::Config(anyhow!(
"Must set `primary-key` in {}",
SINK_TYPE_UPSERT
)));
}
}
// Enforce merge-on-read for append-only sinks
Self::validate_append_only_write_mode(&config.r#type, config.write_mode)?;View on GitHub (pinned to 6469eb736d)
Solutions
- Provide a non-empty `primary_key` in the WITH options listing the upsert key columns, e.g. `primary_key='id'` or `primary_key='id,name'`.
- If no meaningful pk exists, switch the sink `type` from `upsert` to `append-only`.
- If the stream key should be used automatically, set `enable_pk_index=true` (or omit primary_key with pk index enabled) so the planner derives the pk.
Example fix
// before WITH ( connector = 'iceberg', type = 'upsert', primary_key = '' ) // after WITH ( connector = 'iceberg', type = 'upsert', primary_key = 'user_id' )
Defensive patterns
Strategy: validation
Validate before calling
const pk = opts['primary_key'];
if (opts.type === 'upsert' && pk !== undefined && pk.trim() === '') {
throw new Error("`primary-key` must not be empty in upsert");
} Type guard
function hasNonEmptyPk(opts) {
return typeof opts.primary_key === 'string' && opts.primary_key.length > 0;
} Try / catch
try {
await createIcebergSink(opts);
} catch (e) {
if (String(e).includes('`primary-key` must not be empty')) {
opts.primary_key = inferPrimaryKeyColumns(opts);
return createIcebergSink(opts);
}
throw e;
} Prevention
- Only emit the primary_key option when a non-empty column list is available.
- Default to append-only sink type when no key exists.
- Validate WITH options in your SQL-generation tooling before submitting DDL.
When it happens
Trigger: Creating an Iceberg sink with `connector='iceberg'`, `type='upsert'`, and `primary_key=''` (empty string) in the WITH options, e.g. via CREATE SINK. The value parses as Some("") which fails `primary_key.is_empty()`.
Common situations: Templates or generated SQL that interpolate a pk column list variable that is empty; users copying upsert sink DDL and deleting the key list; tooling that writes `primary_key` unconditionally even when no pk columns were selected.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- Must set `primary-key` in {}
- `enable_pk_index` is only supported for upsert iceberg sink
- `enable_pk_index` is only supported for upsert iceberg sink
- `enable_pk_index` is only supported for upsert iceberg sink
- `enable_pk_index` cannot be true when `force_append_only` is
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/0950ef83f5e7cdc8.
Report an issue: GitHub.