diff --git a/crates/dbx-core/src/db/mysql.rs b/crates/dbx-core/src/db/mysql.rs index 5c90ddc7db..e76f9a3846 100644 --- a/crates/dbx-core/src/db/mysql.rs +++ b/crates/dbx-core/src/db/mysql.rs @@ -262,7 +262,11 @@ pub async fn connect(url: &str, fallback_timeout: Duration) -> Result, fallback_timeout: Duration) -> Result { +pub async fn connect_with_ca_cert( + url: &str, + ca_cert_path: Option<&str>, + fallback_timeout: Duration, +) -> Result { let timeout = super::parse_connect_timeout_with_fallback(url, fallback_timeout); let pool = create_pool(url, ca_cert_path)?; let result = verify_pool_connection(&pool, timeout).await; @@ -1028,6 +1032,11 @@ mod tests { assert!(is_result_set_query(sql)); } + #[test] + fn mysql_desc_queries_are_treated_as_result_sets() { + assert!(is_result_set_query("DESC users")); + } + #[test] fn numeric_metadata_accepts_unsigned_information_schema_values() { assert_eq!(numeric_metadata_u64_to_i32(Some(65)), Some(65)); diff --git a/crates/dbx-core/src/sql.rs b/crates/dbx-core/src/sql.rs index 58cc14bd4d..c0b0068ef2 100644 --- a/crates/dbx-core/src/sql.rs +++ b/crates/dbx-core/src/sql.rs @@ -701,7 +701,12 @@ pub fn starts_with_executable_sql_keyword_with_options( let Some(token) = first_executable_sql_token_with_options(sql, options) else { return false; }; - keywords.iter().any(|keyword| token.eq_ignore_ascii_case(keyword)) + keywords.iter().any(|keyword| executable_sql_keyword_matches(token, keyword)) +} + +fn executable_sql_keyword_matches(token: &str, keyword: &str) -> bool { + token.eq_ignore_ascii_case(keyword) + || (keyword.eq_ignore_ascii_case("DESCRIBE") && token.eq_ignore_ascii_case("DESC")) } fn is_mysql_compatible_import_target(db_type: &DatabaseType, driver_profile: Option<&str>) -> bool { @@ -1170,6 +1175,12 @@ mod tests { assert!(starts_with_executable_sql_keyword("/*M! SELECT 1 */", &["SELECT"])); } + #[test] + fn describe_keyword_detection_accepts_desc_shorthand() { + assert!(starts_with_executable_sql_keyword("DESC users", &["DESCRIBE"])); + assert!(starts_with_executable_sql_keyword("-- comment\nDESC users", &["DESCRIBE"])); + } + #[test] fn mysql_hash_comments_are_ignored_for_keyword_detection() { assert!(starts_with_executable_sql_keyword_for_database( diff --git a/crates/dbx-core/src/transfer.rs b/crates/dbx-core/src/transfer.rs index ed15c397d7..bc2318fd96 100644 --- a/crates/dbx-core/src/transfer.rs +++ b/crates/dbx-core/src/transfer.rs @@ -6,6 +6,7 @@ use crate::connection::{AppState, PoolKind}; use crate::db; use crate::models::connection::DatabaseType; use crate::query::{agent_execute_query_params, QueryExecutionOptions}; +use crate::sql::starts_with_executable_sql_keyword; static CANCELLED: std::sync::LazyLock>> = std::sync::LazyLock::new(|| RwLock::new(HashSet::new())); @@ -821,13 +822,7 @@ pub async fn execute_on_pool(state: &AppState, pool_key: &str, sql: &str) -> Res tokio::task::spawn_blocking(move || { let con = con.lock().map_err(|e| e.to_string())?; let start = std::time::Instant::now(); - let trimmed = sql.trim().to_uppercase(); - if trimmed.starts_with("SELECT") - || trimmed.starts_with("SHOW") - || trimmed.starts_with("DESCRIBE") - || trimmed.starts_with("WITH") - || trimmed.starts_with("PRAGMA") - { + if starts_with_executable_sql_keyword(&sql, &["SELECT", "SHOW", "DESCRIBE", "WITH", "PRAGMA"]) { let mut stmt = con.prepare(&sql).map_err(|e| e.to_string())?; let mut rows = stmt.query([]).map_err(|e| e.to_string())?; let stmt_ref = rows.as_ref().ok_or("DuckDB statement unavailable")?;