{"record":{"id":"0fc30685887fc74f","repo":"risingwavelabs/risingwave","slug":"e-0fc306","errorCode":null,"errorMessage":"{e}","messagePattern":"\\{e\\}","errorType":"validation","errorClass":"SinkError::Config","httpStatus":null,"severity":"error","filePath":"src/connector/src/sink/sqlserver.rs","lineNumber":86,"sourceCode":"    #[serde_as(as = \"DisplayFromStr\")]\n    pub max_batch_rows: usize,\n    pub r#type: String, // accept \"append-only\" or \"upsert\"\n\n    #[serde(flatten)]\n    pub unknown_fields: std::collections::HashMap<String, String>,\n}\n\ncrate::impl_sink_unknown_fields!(SqlServerConfig);\n\npub fn sql_server_default_schema() -> String {\n    \"dbo\".to_owned()\n}\n\nimpl SqlServerConfig {\n    pub fn from_btreemap(properties: BTreeMap<String, String>) -> Result<Self> {\n        let config =\n            serde_json::from_value::<SqlServerConfig>(serde_json::to_value(properties).unwrap())\n                .map_err(|e| SinkError::Config(anyhow!(e)))?;\n        if config.r#type != SINK_TYPE_APPEND_ONLY && config.r#type != SINK_TYPE_UPSERT {\n            return Err(SinkError::Config(anyhow!(\n                \"`{}` must be {}, or {}\",\n                SINK_TYPE_OPTION,\n                SINK_TYPE_APPEND_ONLY,\n                SINK_TYPE_UPSERT\n            )));\n        }\n        Ok(config)\n    }\n\n    pub fn full_object_path(&self) -> String {\n        format!(\"[{}].[{}].[{}]\", self.database, self.schema, self.table)\n    }\n}\n\nimpl EnforceSecret for SqlServerConfig {\n    const ENFORCE_SECRET_PROPERTIES: Set<&'static str> = phf_set! {","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/connector/src/sink/sqlserver.rs#L68-L104","documentation":"SqlServerConfig::from_btreemap deserializes the user-supplied property map into the SqlServerConfig struct via serde_json. If any property fails to deserialize (wrong type, unknown/misspelled field, malformed value), the serde error is wrapped as SinkError::Config.","triggerScenarios":"Calling SqlServerConfig::from_btreemap with properties whose keys/values do not match the serde expectations of SqlServerConfig, e.g. non-numeric max_batch_rows, unknown option names (without deny_unknown_fields leniency), or wrong types.","commonSituations":"Typos in sink WITH options, passing a string where a number is expected, copying options from another sink connector (e.g. Kafka-style options) into the SQL Server sink.","solutions":["Read the serde error message in `e` — it names the offending field and expected type","Fix the option name/value in the CREATE SINK ... WITH (...) clause to match SqlServerConfig fields","Compare against the documented SQL Server sink options list and remove unrelated options"],"exampleFix":"// before\nWITH ( 'max_batch_rows' = 'many' )\n// after\nWITH ( 'max_batch_rows' = '1000' )","handlingStrategy":"validation","validationCode":"fn validate_sqlserver_props(props: &BTreeMap<String, String>) -> Result<(), String> {\n    for k in props.keys() {\n        if !KNOWN_SQLSERVER_OPTIONS.contains(&k.as_str()) { return Err(format!(\"unknown option: {}\", k)); }\n    }\n    if let Some(v) = props.get(\"max_batch_rows\") {\n        v.parse::<usize>().map_err(|_| format!(\"max_batch_rows must be numeric, got {}\", v))?;\n    }\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":"match SqlServerConfig::from_btreemap(props) {\n    Err(SinkError::Config(e)) => { log_config_error(&e); Err(e) }\n    other => other,\n}","preventionTips":["Copy sink options only from the SQL Server sink docs","Validate numeric options parse before submitting","Keep a canonical example WITH clause and diff against it"],"tags":["sqlserver","sink","config","serde","deserialization"],"backgroundTag":"config-type-mismatch","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}