risingwavelabs/risingwave · error · SinkError::Config

Please specific either

Error message

Please specific either `{STREAM}` or `{STREAM_COLUMN}`. They are mutually exclusive options.

What it means

For `value type = 'stream'`, the Redis sink requires exactly one of two mutually exclusive options: a fixed stream name (`stream`) or a per-row stream name column (`stream column`). `validate` fails when neither is set, or when both are set at once.

Solutions

  1. Provide exactly one: either `stream = '<fixed stream name>'` or `stream column = '<varchar column>'`.
  2. Remove the duplicate option if both are present.
  3. Verify option key spelling (`stream`, `stream column`) so the intended one is actually parsed.

Example fix

// before
WITH (connector='redis', value type='stream', stream='alerts', stream column='topic')
// after
WITH (connector='redis', value type='stream', stream column='topic')
Defensive patterns

Strategy: validation

Validate before calling

const n = ['stream', 'stream column'].filter(k => k in opts).length;
if (opts['value type'] === 'stream' && n !== 1) throw new Error('set exactly one of stream / stream column');

Try / catch

try { await createSink(cfg); } catch (e) { if (/mutually exclusive/.test(String(e))) { delete cfg.stream; await createSink(cfg); } else throw e; }

Prevention

When it happens

Trigger: Create a sink with `value type='stream'` and: (a) no `stream`/`stream column` option at all, or (b) both `stream='name'` and `stream column='col'` supplied.

Common situations: Users migrating configs add `stream` while forgetting to remove `stream column`, or forget that stream sinks need a target unlike the pubsub flow; typos like `stream_name` leave neither option visible.

Related errors


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

Appendix: source

Thrown at src/connector/src/sink/redis.rs:457

                    }

                    let value_format =
                        self.format_desc.options.get(VALUE_FORMAT).ok_or_else(|| {
                            SinkError::Config(anyhow!("Cannot find `{VALUE_FORMAT}`"))
                        })?;
                    TemplateStringEncoder::check_string_format(value_format, &all_map)?;
                }
                Some(REDIS_VALUE_TYPE_STREAM) => {
                    tracing::error!("test:for bug");
                    risingwave_common::license::Feature::RedisSinkStream
                        .check_available()
                        .map_err(|e| anyhow::anyhow!(e))?;
                    let stream = self.format_desc.options.get(STREAM);
                    let stream_column = self.format_desc.options.get(STREAM_COLUMN);
                    if (stream.is_none() && stream_column.is_none())
                        || (stream.is_some() && stream_column.is_some())
                    {
                        return Err(SinkError::Config(anyhow!(
                            "Please specific either `{STREAM}` or `{STREAM_COLUMN}`. They are mutually exclusive options."
                        )));
                    }

                    if let Some(stream_column) = stream_column
                        && let Some(stream_column_type) = all_map.get(stream_column)
                        && (stream_column_type != &DataType::Varchar)
                    {
                        return Err(SinkError::Config(anyhow!(
                            "`{STREAM_COLUMN}` must be set to `varchar`"
                        )));
                    }

                    let value_format =
                        self.format_desc.options.get(VALUE_FORMAT).ok_or_else(|| {
                            SinkError::Config(anyhow!("Cannot find `{VALUE_FORMAT}`"))
                        })?;
                    let key_format = self.format_desc.options.get(KEY_FORMAT).ok_or_else(|| {

View on GitHub (pinned to 6469eb736d)