{"record":{"id":"ef42db6df07d27a8","repo":"risingwavelabs/risingwave","slug":"please-specific-either-stream-or-stream-colu","errorCode":null,"errorMessage":"Please specific either `{STREAM}` or `{STREAM_COLUMN}`. They are mutually exclusive options.","messagePattern":"Please specific either `(.+?)` or `(.+?)`\\. They are mutually exclusive options\\.","errorType":"validation","errorClass":"SinkError::Config","httpStatus":null,"severity":"error","filePath":"src/connector/src/sink/redis.rs","lineNumber":457,"sourceCode":"                    }\n\n                    let value_format =\n                        self.format_desc.options.get(VALUE_FORMAT).ok_or_else(|| {\n                            SinkError::Config(anyhow!(\"Cannot find `{VALUE_FORMAT}`\"))\n                        })?;\n                    TemplateStringEncoder::check_string_format(value_format, &all_map)?;\n                }\n                Some(REDIS_VALUE_TYPE_STREAM) => {\n                    tracing::error!(\"test:for bug\");\n                    risingwave_common::license::Feature::RedisSinkStream\n                        .check_available()\n                        .map_err(|e| anyhow::anyhow!(e))?;\n                    let stream = self.format_desc.options.get(STREAM);\n                    let stream_column = self.format_desc.options.get(STREAM_COLUMN);\n                    if (stream.is_none() && stream_column.is_none())\n                        || (stream.is_some() && stream_column.is_some())\n                    {\n                        return Err(SinkError::Config(anyhow!(\n                            \"Please specific either `{STREAM}` or `{STREAM_COLUMN}`. They are mutually exclusive options.\"\n                        )));\n                    }\n\n                    if let Some(stream_column) = stream_column\n                        && let Some(stream_column_type) = all_map.get(stream_column)\n                        && (stream_column_type != &DataType::Varchar)\n                    {\n                        return Err(SinkError::Config(anyhow!(\n                            \"`{STREAM_COLUMN}` must be set to `varchar`\"\n                        )));\n                    }\n\n                    let value_format =\n                        self.format_desc.options.get(VALUE_FORMAT).ok_or_else(|| {\n                            SinkError::Config(anyhow!(\"Cannot find `{VALUE_FORMAT}`\"))\n                        })?;\n                    let key_format = self.format_desc.options.get(KEY_FORMAT).ok_or_else(|| {","sourceCodeStart":439,"sourceCodeEnd":475,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/connector/src/sink/redis.rs#L439-L475","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Provide exactly one: either `stream = '<fixed stream name>'` or `stream column = '<varchar column>'`.","Remove the duplicate option if both are present.","Verify option key spelling (`stream`, `stream column`) so the intended one is actually parsed."],"exampleFix":"// before\nWITH (connector='redis', value type='stream', stream='alerts', stream column='topic')\n// after\nWITH (connector='redis', value type='stream', stream column='topic')","handlingStrategy":"validation","validationCode":"const n = ['stream', 'stream column'].filter(k => k in opts).length;\nif (opts['value type'] === 'stream' && n !== 1) throw new Error('set exactly one of stream / stream column');","typeGuard":null,"tryCatchPattern":"try { await createSink(cfg); } catch (e) { if (/mutually exclusive/.test(String(e))) { delete cfg.stream; await createSink(cfg); } else throw e; }","preventionTips":["Build sink configs via a helper that enforces one-of constraints","When migrating configs, diff old vs new options to catch leftovers","Check for typos that make neither option actually present"],"tags":["redis","sink","config-validation","mutually-exclusive"],"backgroundTag":"mutually-exclusive-options","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}