{"record":{"id":"308a8d908c615368","repo":"risingwavelabs/risingwave","slug":"sql-server-table-metadata-error","errorCode":null,"errorMessage":"SQL Server table {} metadata error","messagePattern":"SQL Server table (.+?) metadata error","errorType":"validation","errorClass":"SinkError::SqlServer","httpStatus":null,"severity":"error","filePath":"src/connector/src/sink/sqlserver.rs","lineNumber":592,"sourceCode":"                // we should not have more than one redirect, so we'll short-circuit here.\n                Client::connect(config, tcp.compat_write()).await?\n            }\n            Err(e) => return Err(e.into()),\n        };\n\n        Ok(Self {\n            inner_client: client,\n        })\n    }\n}\n\nasync fn query_sql_server_table_metadata(\n    sql_client: &mut SqlServerClient,\n    config: &SqlServerConfig,\n) -> Result<Vec<SqlServerColumnMetadata>> {\n    let mut sql_server_table_metadata = Vec::new();\n    let query_table_metadata_error = || {\n        SinkError::SqlServer(anyhow!(format!(\n            \"SQL Server table {} metadata error\",\n            config.full_object_path()\n        )))\n    };\n    // Query primary-key membership through a subquery filtered by `pk.is_primary_key = 1`.\n    // A column can appear in both the primary-key index and secondary indexes, and a naive\n    // join from `sys.columns` to all `sys.index_columns` would emit extra index rows or mark\n    // secondary-index-only columns as PK columns. Keep the PK filter inside the subquery so\n    // each table column is returned once with `IsPk` set only by the primary-key index.\n    static QUERY_TABLE_METADATA: &str = r#\"\nSELECT\n    col.name AS ColumnName,\n    CAST(CASE WHEN pk_col.column_id IS NULL THEN 0 ELSE 1 END AS int) AS IsPk,\n    typ.name AS DataType\nFROM\n    sys.columns col\nJOIN\n    sys.types typ ON typ.user_type_id = col.user_type_id","sourceCodeStart":574,"sourceCodeEnd":610,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/connector/src/sink/sqlserver.rs#L574-L610","documentation":"RisingWave's SQL Server sink wraps any failure while querying table metadata (columns, primary-key membership, types) from the downstream SQL Server instance in this error, naming the table via `config.full_object_path()`. The query joins sys.indexes/sys.index_columns (filtered by `pk.is_primary_key = 1`) with column metadata; a failure here means the sink cannot validate its schema against the target table. It can stem from connectivity problems, SQL errors, or a missing/unreachable table.","triggerScenarios":"Calling `validate` or `query_downstream_column_metadata` when the inner SQL query for table/primary-key metadata returns Err: network failure to SQL Server, wrong database/schema/table name in the config, SQL syntax or driver error, or the table not existing.","commonSituations":"Typo in `database.schema.table` in the sink `sqlserver.url`/table config; table dropped or renamed after sink creation; SQL Server unreachable (firewall, wrong port, TLS mismatch); login lacks permission to read catalog views (sys.*); ODBC/TDS driver misconfiguration.","solutions":["Verify the sink's SQL Server URL and full object path (database.schema.table) point to an existing table (run `SELECT 1 FROM <table>` with the same credentials).","Test connectivity from the RisingWave host: `sqlcmd -S <server> -U <user> -P <pass> -d <db>`.","Grant the sink user read access to catalog views (e.g. VIEW DEFINITION / SELECT on sys.columns, sys.indexes) in the target database.","Check RisingWave logs for the underlying driver error wrapped by this anyhow error to pinpoint network vs SQL failure."],"exampleFix":"// before (config with wrong path)\nCREATE SINK s FROM mv WITH (connector='sqlserver', sqlserver.url='...', table='dbo.orderss');\n// after\nCREATE SINK s FROM mv WITH (connector='sqlserver', sqlserver.url='...', table='dbo.orders');","handlingStrategy":"validation","validationCode":"-- run with the sink credentials before CREATE SINK\nSELECT c.name, c.is_identity FROM sys.columns c\nJOIN sys.tables t ON t.object_id = c.object_id\nWHERE t.name = 'orders' AND SCHEMA_NAME(t.schema_id) = 'dbo';","typeGuard":null,"tryCatchPattern":"// Rust: match on SinkError::SqlServer and check message contains \"metadata error\"\nmatch sink.validate().await {\n    Err(e) if e.to_string().contains(\"metadata error\") => verify_connectivity_and_table(),\n    Err(e) => return Err(e),\n    Ok(()) => {},\n}","preventionTips":["Ping SQL Server from the RisingWave host before creating the sink","Use fully qualified database.schema.table names","Grant catalog-view read access to the sink user","Lock downstream schema changes behind coordination with sink ownership"],"tags":["sqlserver","sink","metadata-query","database"],"backgroundTag":"database-query-failed","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}