diff --git a/crates/dbx-drivers/src/db/mysql.rs b/crates/dbx-drivers/src/db/mysql.rs index b0c2833a65..3babe2ff6c 100644 --- a/crates/dbx-drivers/src/db/mysql.rs +++ b/crates/dbx-drivers/src/db/mysql.rs @@ -1489,6 +1489,11 @@ fn mysql_group_concat_setup_fallback_mode(setup_mode: MySqlSetupMode, error: &st && compact.contains(&format!( "strconv.parseint:parsing\"cast(greatest(@@session.group_concat_max_len,{MYSQL_GROUP_CONCAT_MAX_LEN})asunsigned)\":invalidsyntax" )); + // StarRocks 3.1 can fail expression folding without echoing the SET statement. + let starrocks_setup_expression_rejected = lower.contains("error 1064 (hy000)") + && lower.contains( + "class com.starrocks.analysis.castexpr cannot be cast to class com.starrocks.analysis.literalexpr", + ); // SphinxQL / Manticore reject the built-in `group_concat_max_len` setup with a // boolean-typed 1064 error. The quoted token after `near` depends on the exact // statement text, so accept any boolean rejection from SphinxQL that mentions @@ -1507,6 +1512,7 @@ fn mysql_group_concat_setup_fallback_mode(setup_mode: MySqlSetupMode, error: &st || compact.contains(&format!("..._len,{MYSQL_GROUP_CONCAT_MAX_LEN})asunsigned)")); if (floor_statement_rejected && (setup_query_rejected || setup_value_rejected)) || gaea_setup_expression_rejected + || starrocks_setup_expression_rejected || sphinxql_setup_query_rejected || gateway_session_variable_rejected { @@ -8412,6 +8418,31 @@ mod tests { } } + #[test] + fn mysql_group_concat_starrocks_cast_error_retries_without_session_variable() { + let error = "MySQL connection failed: Server error: `ERROR 1064 (HY000): class com.starrocks.analysis.CastExpr cannot be cast to class com.starrocks.analysis.LiteralExpr (com.starrocks.analysis.CastExpr and com.starrocks.analysis.LiteralExpr are in unnamed module of loader 'app')'"; + + assert_eq!( + mysql_group_concat_setup_fallback_mode(MySqlSetupMode::Standard, error), + Some(MySqlSetupMode::Compatible) + ); + assert_eq!(mysql_group_concat_setup_fallback_mode(MySqlSetupMode::Compatible, error), None); + } + + #[test] + fn mysql_group_concat_starrocks_cast_retry_requires_exact_error() { + for error in [ + "Server error: `ERROR 1105 (HY000): class com.starrocks.analysis.CastExpr cannot be cast to class com.starrocks.analysis.LiteralExpr'", + "Server error: `ERROR 1064 (42000): class com.starrocks.analysis.CastExpr cannot be cast to class com.starrocks.analysis.LiteralExpr'", + "Server error: `ERROR 1064 (HY000): class com.starrocks.analysis.SlotRef cannot be cast to class com.starrocks.analysis.LiteralExpr'", + "Server error: `ERROR 1064 (HY000): class com.starrocks.analysis.CastExpr cannot be cast to class com.starrocks.analysis.SlotRef'", + "Server error: `ERROR 1064 (HY000): class com.example.CastExpr cannot be cast to class com.example.LiteralExpr'", + "Server error: `ERROR 1064 (HY000): You have an error in your SQL syntax'", + ] { + assert_eq!(mysql_group_concat_setup_fallback_mode(MySqlSetupMode::Standard, error), None, "{error}"); + } + } + #[test] fn mysql_gateway_forbidden_global_variables_error_retries_without_session_variable() { let error = "MySQL connection failed: Server error: `ERROR 10192 (HY000): SET GLOBAL VARIABLES is forbidden'"; diff --git a/crates/dbx-drivers/tests/live_starrocks_setup_compatibility.rs b/crates/dbx-drivers/tests/live_starrocks_setup_compatibility.rs new file mode 100644 index 0000000000..2f630c70a4 --- /dev/null +++ b/crates/dbx-drivers/tests/live_starrocks_setup_compatibility.rs @@ -0,0 +1,56 @@ +use dbx_drivers::db::mysql; +use mysql_async::prelude::Queryable; +use std::time::Duration; + +#[tokio::test] +#[ignore = "requires DBX_LIVE_STARROCKS_URL for a remote StarRocks with the SET CastExpr failure"] +async fn live_starrocks_group_concat_setup_fallback() { + let url = std::env::var("DBX_LIVE_STARROCKS_URL").expect("DBX_LIVE_STARROCKS_URL"); + let timeout = Duration::from_secs(15); + let bare_pool = mysql::connect_bare(&url, timeout).await.expect("bare StarRocks connection"); + let mut bare_connection = bare_pool.get_conn().await.unwrap(); + let version: Option = bare_connection.query_first("SELECT current_version()").await.unwrap(); + println!("StarRocks version: {version:?}"); + let error = bare_connection + .query_drop( + "SET SESSION group_concat_max_len = \ + cast(greatest(@@session.group_concat_max_len, 1048576) as unsigned)", + ) + .await + .expect_err("server must reproduce the reported initialization failure") + .to_string(); + println!("Rejected built-in setup: {error}"); + assert!(error.contains("ERROR 1064 (HY000)"), "{error}"); + assert!( + error.contains( + "class com.starrocks.analysis.CastExpr cannot be cast to class com.starrocks.analysis.LiteralExpr" + ), + "{error}" + ); + drop(bare_connection); + bare_pool.disconnect().await.unwrap(); + + let extra_setup = ["SET query_timeout = 42".to_string()]; + let pool = mysql::connect_with_ca_cert_pool_limit_idle_and_setup(&url, None, timeout, 2, None, &extra_setup) + .await + .expect("normal DBX connection must retry without its optional GROUP_CONCAT setup"); + let mut first_connection = pool.get_conn().await.unwrap(); + let mut second_connection = pool.get_conn().await.unwrap(); + for connection in [&mut first_connection, &mut second_connection] { + let configured_timeout: Option = connection.query_first("SELECT @@query_timeout").await.unwrap(); + assert_eq!(configured_timeout, Some(42)); + let result: Option = connection.query_first("SELECT 1").await.unwrap(); + assert_eq!(result, Some(1)); + connection.query_drop("SHOW DATABASES").await.unwrap(); + } + drop(first_connection); + drop(second_connection); + pool.disconnect().await.unwrap(); + + let invalid_setup = ["SET dbx_nonexistent_setup_variable = 1".to_string()]; + let error = mysql::connect_with_ca_cert_pool_limit_idle_and_setup(&url, None, timeout, 2, None, &invalid_setup) + .await + .err() + .expect("invalid user setup must not be swallowed by the compatibility retry"); + assert!(error.contains("dbx_nonexistent_setup_variable"), "{error}"); +}