risingwavelabs/risingwave · error · ConnectorError

Failed to get MySQL version

Error message

Failed to get MySQL version

What it means

get_mysql_version queries `SELECT VERSION()` and parses the string; if the query succeeds but no parseable `x.y...` version token is found, it returns this error. The reader needs the version (and MariaDB detection) for capability checks like mysql_native_password vs. caching_sha2 behavior.

Solutions

  1. Check what `SELECT VERSION()` returns on the target server and confirm it starts with a numeric version like '8.0.32'.
  2. Bypass or reconfigure proxies that rewrite the VERSION string.
  3. Upgrade the connector to handle the fork's version format, or pin a standard MySQL/MariaDB version for CDC.
  4. Verify the CDC user has permission to execute SELECT on the server (VERSION() is generally unrestricted, but proxies may block it).

Example fix

// before (proxy returns "mysql-advanced 8")
// after: fix VERSION() output or make parsing lenient
let version_str = /* row value */;
let Some((major, rest)) = version_str.split_once('.') else {
    return Err(anyhow!("Failed to get MySQL version: '{}'".to_string(), version_str).into());
};
Defensive patterns

Strategy: try-catch

Validate before calling

// Sanity-check the target server before connecting CDC:
let version: String = sqlx::query_scalar("SELECT VERSION()").fetch_one(&conn).await?;
if !version.chars().next().map_or(false, |c| c.is_ascii_digit()) {
    return Err(format!("server VERSION() is non-standard: {}", version));
}

Type guard

fn is_parsable_mysql_version(v: &str) -> bool {
    let mut parts = v.split('.');
    parts.next().map_or(false, |m| m.chars().all(|c| c.is_ascii_digit()) && !m.is_empty())
        && parts.next().map_or(false, |m| m.chars().all(|c| c.is_ascii_digit()) && !m.is_empty())
}

Try / catch

match connect_mysql_external_table(cfg).await {
    Err(e) if e.to_string().contains("Failed to get MySQL version") => {
        eprintln!("Check `SELECT VERSION()` output on the server/proxy: it must start with 'major.minor'");
        Err(e)
    }
    other => other,
}

Prevention

When it happens

Trigger: MySqlExternalTableReader::connect runs get_mysql_version and the server returns a VERSION() string that does not match the expected `major.minor[.patch]` prefix pattern, or the result set is empty.

Common situations: Pointing CDC at MySQL-compatible services (ProxySQL in front, Vitess, TiDB, unusual forks) that return nonstandard VERSION() strings; permission issues yielding empty results.

Related errors


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

Appendix: source

Thrown at src/connector/src/source/cdc/external/mysql.rs:545

    /// Get MySQL version from the connection
    async fn get_mysql_version(pool: &mysql_async::Pool) -> ConnectorResult<(u8, u8, bool)> {
        let mut conn = pool.get_conn().await?;
        let result: Option<String> = conn.query_first("SELECT VERSION()").await?;

        if let Some(version_str) = result {
            let parts: Vec<&str> = version_str.split('.').collect();
            if parts.len() >= 2 {
                let major_version = parts[0]
                    .parse::<u8>()
                    .context("Failed to parse major version")?;
                let minor_version = parts[1]
                    .parse::<u8>()
                    .context("Failed to parse minor version")?;
                let is_mariadb = version_str.to_lowercase().contains("mariadb");
                return Ok((major_version, minor_version, is_mariadb));
            }
        }
        Err(anyhow!("Failed to get MySQL version").into())
    }

    /// Check if MySQL version is 8.4 or later
    fn is_mysql_8_4_or_later(&self) -> bool {
        let (major, minor) = self.mysql_version;
        major > 8 || (major == 8 && minor >= 4)
    }

    pub async fn new(
        config: ExternalTableConfig,
        rw_schema: Schema,
        pk_indices: Vec<usize>,
    ) -> ConnectorResult<Self> {
        let database = config.database.clone();
        let table = config.table.clone();
        let pool = build_mysql_connection_pool(
            &config.host,
            config.port.parse::<u16>().unwrap(),

View on GitHub (pinned to 6469eb736d)