risingwavelabs/risingwave · error

SQL Server LSN should be 10 bytes, got {} bytes

Error message

SQL Server LSN should be 10 bytes, got {} bytes

What it means

The `lsn_bytes_to_hex` closure validates that a SQL Server LSN is exactly 10 bytes before hex-encoding it. A differently sized byte array indicates the column did not contain a binary(10) LSN as expected, so the error is thrown with the actual byte count.

Source

Thrown at src/connector/src/source/cdc/enumerator/mod.rs:323

        config.trust_cert();

        let mut client = SqlServerClient::new_with_config(config).await?;
        let row = client
            .inner_client
            .simple_query(
                "SELECT \
                    sys.fn_cdc_get_max_lsn() AS max_lsn, \
                    (SELECT MIN(sys.fn_cdc_get_min_lsn(capture_instance)) FROM cdc.change_tables) AS min_lsn"
                    .to_owned(),
            )
            .await?
            .into_row()
            .await?
            .ok_or_else(|| anyhow!("No result returned when querying SQL Server max/min LSN"))?;

        let lsn_bytes_to_hex = |bytes: &[u8]| -> ConnectorResult<String> {
            if bytes.len() != 10 {
                return Err(anyhow!(
                    "SQL Server LSN should be 10 bytes, got {} bytes",
                    bytes.len()
                )
                .into());
            }
            let mut hex_string = String::with_capacity(22);
            for byte in &bytes[0..4] {
                hex_string.push_str(&format!("{:02x}", byte));
            }
            hex_string.push(':');
            for byte in &bytes[4..8] {
                hex_string.push_str(&format!("{:02x}", byte));
            }
            hex_string.push(':');
            for byte in &bytes[8..10] {
                hex_string.push_str(&format!("{:02x}", byte));
            }
            Ok(hex_string)

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Do not modify the built-in LSN query SQL; keep the SELECT expressions unchanged
  2. Verify the tiberius driver version matches what the connector expects and binds `&[u8]` correctly
  3. Log the raw bytes on failure to diagnose the actual column type/length
  4. If a driver upgrade changed binary decoding, adjust the try_get type or convert with an explicit cast in SQL (e.g. `CAST(... AS BINARY(10))`)
Defensive patterns

Strategy: try-catch

Type guard

fn is_valid_lsn(bytes: &[u8]) -> bool { bytes.len() == 10 }

Try / catch

match res {
    Err(e) if e.to_string().contains("LSN should be 10 bytes") => {
        error!("Unexpected LSN column shape from SQL Server: {}", e);
    }
    other => other?,
}

Prevention

When it happens

Trigger: The row returned by the LSN query contains a value at the max_lsn/min_lsn column whose length differs from 10 bytes — e.g. the query result shape changed, or a NULL-adjacent/wrong column was bound.

Common situations: SQL Server version differences in `sys.fn_cdc_get_max_lsn` output binding; tiberius returning the value as a different binary type; manual edits to the monitoring SQL that changed selected columns.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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