diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index 41a8c60d05..e89f453d6a 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -8200,6 +8200,9 @@ function openExternalUrl(url: string) {

{{ t("connection.localInfilePathHint") }}

+

+ {{ t("connection.sessionVariablesHint") }} +

diff --git a/apps/desktop/src/i18n/locales/az.ts b/apps/desktop/src/i18n/locales/az.ts index 883fca6a3e..a45311866e 100644 --- a/apps/desktop/src/i18n/locales/az.ts +++ b/apps/desktop/src/i18n/locales/az.ts @@ -602,6 +602,8 @@ export default withEnglishFallback({ driverName: "Drayverin adı", driverNamePlaceholder: "Təchizatçının və ya mühitin adı", urlParams: "URL parametrləri", + sessionVariablesHint: + "DBX qoşulma zamanı group_concat_max_len sessiya dəyişənini 1048576-ya qaldırır ki, GROUP_CONCAT nəticələri səssizcə kəsilməsin. Server konfiqurasiyasına uyğun olmaq üçün sessionVariables=group_concat_max_len={'@'}{'@'}global.group_concat_max_len yazın (MySQL Connector/J sintaksisi; bir neçə dəyişəni vergüllə ayırın).", localInfilePathHint: "LOAD DATA LOCAL INFILE üçün hər faylı localInfilePath=/absolute/path/file.csv ilə ayrıca icazə siyahısına əlavə edin. Əlavə fayllar üçün parametri təkrarlayın.", d1AccountId: "Hesabın ID-si", d1DatabaseId: "Verilənlər bazasının ID-si", diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 1fab9e010b..1b3a491a2e 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -745,6 +745,8 @@ export default { driverName: "Driver Name", driverNamePlaceholder: "Vendor or environment name", urlParams: "URL Params", + sessionVariablesHint: + "DBX raises the session variable group_concat_max_len to 1048576 so GROUP_CONCAT results are not silently truncated. To follow the server configuration instead, use sessionVariables=group_concat_max_len={'@'}{'@'}global.group_concat_max_len (MySQL Connector/J syntax; separate multiple variables with commas).", localInfilePathHint: "For LOAD DATA LOCAL INFILE, whitelist each file explicitly with localInfilePath=/absolute/path/file.csv. Repeat the parameter for additional files.", d1AccountId: "Account ID", d1DatabaseId: "Database ID", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index 4d1925a374..69bf2ccd7d 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -724,6 +724,8 @@ export default withEnglishFallback({ driverName: "Nombre del driver", driverNamePlaceholder: "Nombre del proveedor o entorno", urlParams: "Parámetros de URL", + sessionVariablesHint: + "DBX eleva la variable de sesión group_concat_max_len a 1048576 para que los resultados de GROUP_CONCAT no se trunquen de forma silenciosa. Para seguir la configuración del servidor, use sessionVariables=group_concat_max_len={'@'}{'@'}global.group_concat_max_len (sintaxis de MySQL Connector/J; separe varias variables con comas).", d1AccountId: "ID de cuenta", d1DatabaseId: "ID de base de datos", d1ApiToken: "Token de API", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index b90df8a802..06c34fd4c8 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -723,6 +723,8 @@ export default withEnglishFallback({ driverName: "Nome Driver", driverNamePlaceholder: "Nome del fornitore o dell'ambiente", urlParams: "Parametri URL", + sessionVariablesHint: + "DBX innalza la variabile di sessione group_concat_max_len a 1048576 per evitare che i risultati di GROUP_CONCAT vengano troncati silenziosamente. Per seguire la configurazione del server, usare sessionVariables=group_concat_max_len={'@'}{'@'}global.group_concat_max_len (sintassi MySQL Connector/J; separare più variabili con virgole).", d1AccountId: "ID account", d1DatabaseId: "ID database", d1ApiToken: "Token API", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index 7f9d55db7a..6c28514173 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -723,6 +723,8 @@ export default withEnglishFallback({ driverName: "ドライバー名", driverNamePlaceholder: "ベンダー名または環境名", urlParams: "URLパラメータ", + sessionVariablesHint: + "DBX は接続時にセッション変数 group_concat_max_len を 1048576 に引き上げ、GROUP_CONCAT の結果が黙って切り捨てられるのを防ぎます。サーバー設定に合わせる場合は sessionVariables=group_concat_max_len={'@'}{'@'}global.group_concat_max_len を指定してください(MySQL Connector/J と同じ構文、複数の変数はカンマ区切り)。", d1AccountId: "アカウントID", d1DatabaseId: "データベースID", d1ApiToken: "APIトークン", diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts index ae9ef12546..9a52318f30 100644 --- a/apps/desktop/src/i18n/locales/ko.ts +++ b/apps/desktop/src/i18n/locales/ko.ts @@ -737,6 +737,8 @@ export default withEnglishFallback({ driverName: "드라이버 이름", driverNamePlaceholder: "공급자 또는 환경 이름", urlParams: "URL 매개변수", + sessionVariablesHint: + "DBX는 연결 시 세션 변수 group_concat_max_len을 1048576으로 올려 GROUP_CONCAT 결과가 조용히 잘리는 것을 방지합니다. 서버 설정을 따르려면 sessionVariables=group_concat_max_len={'@'}{'@'}global.group_concat_max_len를 입력하세요(MySQL Connector/J 문법, 여러 변수는 쉼표로 구분).", localInfilePathHint: "For LOAD DATA LOCAL INFILE, whitelist each file explicitly with localInfilePath=/absolute/path/file.csv. Repeat the parameter for additional files.", d1AccountId: "계정 ID", d1DatabaseId: "데이터베이스 ID", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index 8213bac1c4..3d0732e31c 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -724,6 +724,8 @@ export default withEnglishFallback({ driverName: "Nome do Driver", driverNamePlaceholder: "Nome do fornecedor ou ambiente", urlParams: "Parâmetros da URL", + sessionVariablesHint: + "O DBX eleva a variável de sessão group_concat_max_len para 1048576 para que os resultados de GROUP_CONCAT não sejam truncados silenciosamente. Para seguir a configuração do servidor, use sessionVariables=group_concat_max_len={'@'}{'@'}global.group_concat_max_len (sintaxe do MySQL Connector/J; separe várias variáveis com vírgulas).", d1AccountId: "ID da conta", d1DatabaseId: "ID do banco de dados", d1ApiToken: "Token da API", diff --git a/apps/desktop/src/i18n/locales/tr.ts b/apps/desktop/src/i18n/locales/tr.ts index b94dc22b5b..78796659b4 100644 --- a/apps/desktop/src/i18n/locales/tr.ts +++ b/apps/desktop/src/i18n/locales/tr.ts @@ -604,6 +604,8 @@ export default withEnglishFallback({ driverName: "Sürücü Adı", driverNamePlaceholder: "Sağlayıcı veya ortam adı", urlParams: "URL Parametreleri", + sessionVariablesHint: + "DBX bağlantı sırasında group_concat_max_len oturum değişkenini 1048576 değerine yükseltir; böylece GROUP_CONCAT sonuçları sessizce kesilmez. Sunucu yapılandırmasını izlemek için sessionVariables=group_concat_max_len={'@'}{'@'}global.group_concat_max_len kullanın (MySQL Connector/J söz dizimi; birden fazla değişkeni virgülle ayırın).", localInfilePathHint: "LOAD DATA LOCAL INFILE için her dosyayı localInfilePath=/mutlak/yol/dosya.csv ile açıkça izin listesine ekleyin. Ek dosyalar için parametreyi tekrarlayın.", d1AccountId: "Hesap Kimliği", d1DatabaseId: "Veritabanı Kimliği", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 760da747bd..e4a5312f41 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -661,6 +661,7 @@ export default withEnglishFallback({ driverName: "驱动名称", driverNamePlaceholder: "厂商或环境名称", urlParams: "URL 参数", + sessionVariablesHint: "DBX 连接时会把会话变量 group_concat_max_len 提升到 1048576,避免 GROUP_CONCAT 结果被静默截断。如需跟随服务端配置,可填写 sessionVariables=group_concat_max_len={'@'}{'@'}global.group_concat_max_len(语法同 MySQL Connector/J,多个变量用逗号分隔)。", localInfilePathHint: "使用 LOAD DATA LOCAL INFILE 时,请通过 localInfilePath=/绝对路径/file.csv 明确授权文件;多个文件需重复填写该参数。", d1AccountId: "Account ID", d1DatabaseId: "Database ID", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index 1063efdc5a..1a396b2f60 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -727,6 +727,7 @@ export default withEnglishFallback({ driverName: "驅動程式名稱", driverNamePlaceholder: "廠商或環境名稱", urlParams: "URL 參數", + sessionVariablesHint: "DBX 連線時會把工作階段變數 group_concat_max_len 提升到 1048576,避免 GROUP_CONCAT 結果被靜默截斷。若要跟隨伺服器設定,可填寫 sessionVariables=group_concat_max_len={'@'}{'@'}global.group_concat_max_len(語法同 MySQL Connector/J,多個變數以逗號分隔)。", d1AccountId: "帳戶 ID", d1DatabaseId: "資料庫 ID", d1ApiToken: "API Token", diff --git a/crates/dbx-drivers/src/db/mysql.rs b/crates/dbx-drivers/src/db/mysql.rs index d5f5e7a816..0a395248c4 100644 --- a/crates/dbx-drivers/src/db/mysql.rs +++ b/crates/dbx-drivers/src/db/mysql.rs @@ -1257,10 +1257,16 @@ impl MySqlTcpKeepaliveMode { const MYSQL_GROUP_CONCAT_MAX_LEN: u64 = 1_048_576; impl MySqlSetupMode { - fn group_concat_max_len_query(self) -> Option { + fn group_concat_max_len_query(self, url: &str) -> Option { match self { - Self::Standard => Some(format!("SET SESSION group_concat_max_len = {MYSQL_GROUP_CONCAT_MAX_LEN}")), - Self::Compatible => None, + // An explicit Connector/J style `sessionVariables=group_concat_max_len=...` + // is the user's own choice (for example following the server + // configuration with `@@global.group_concat_max_len`), so the built-in + // safety default must not overwrite it. + Self::Standard if !mysql_session_variables_override_group_concat_max_len(url) => { + Some(format!("SET SESSION group_concat_max_len = {MYSQL_GROUP_CONCAT_MAX_LEN}")) + } + Self::Standard | Self::Compatible => None, } } } @@ -1532,8 +1538,9 @@ fn mysql_setup_queries_for_database_with_mode( queries.push(format!("SET NAMES {charset}")); // MySQL defaults group_concat_max_len to 1024, which silently truncates // GROUP_CONCAT results. Skip it for MySQL protocol-compatible databases - // such as old StarRocks versions that reject unknown MySQL variables. - if let Some(query) = setup_mode.group_concat_max_len_query() { + // such as old StarRocks versions that reject unknown MySQL variables, and + // for connections that configure the variable themselves. + if let Some(query) = setup_mode.group_concat_max_len_query(url) { queries.push(query); } queries.extend(extra_setup_queries.iter().cloned()); @@ -1677,15 +1684,49 @@ fn mysql_connection_catalog(url: &str) -> Option { }) } -fn mysql_connection_session_variables(url: &str) -> Option { - let (_, query) = url.split_once('?')?; +/// Parses the Connector/J style `sessionVariables=` URL parameter into raw +/// `name=value` assignments, keeping user variables (`@name=...`) untrimmed. +fn mysql_connection_session_variable_assignments(url: &str) -> Vec { + let Some((_, query)) = url.split_once('?') else { + return Vec::new(); + }; let query = query.split('#').next().unwrap_or(query); - let value = query.split('&').find_map(|segment| { + let Some(value) = query.split('&').find_map(|segment| { let (key, value) = segment.split_once('=')?; percent_decode_str(key).decode_utf8().ok().filter(|key| key.eq_ignore_ascii_case("sessionVariables"))?; percent_decode_str(value).decode_utf8().ok().map(|value| value.into_owned()) - })?; - let assignments = split_mysql_session_variables(&value); + }) else { + return Vec::new(); + }; + + split_mysql_session_variables(&value) +} + +/// Detects an explicit `group_concat_max_len` assignment in the connection URL. +/// DBX raises the variable during connection setup, but a value the user (or the +/// server) asks for must always win, independent of statement execution order. +fn mysql_session_variables_override_group_concat_max_len(url: &str) -> bool { + mysql_connection_session_variable_assignments(url).into_iter().any(|assignment| { + let Some((name, _)) = assignment.trim().split_once('=') else { + return false; + }; + let name = name.trim(); + // `@@global.name=...` only changes the global value and `@name=...` + // declares a session user variable; neither replaces DBX's session SET. + if name.starts_with("@@global.") { + return false; + } + let name = name.strip_prefix("@@").unwrap_or(name); + if name.starts_with('@') { + return false; + } + let name = name.strip_prefix("session.").unwrap_or(name); + name.trim().eq_ignore_ascii_case("group_concat_max_len") + }) +} + +fn mysql_connection_session_variables(url: &str) -> Option { + let assignments = mysql_connection_session_variable_assignments(url); if assignments.is_empty() { return None; } @@ -8354,6 +8395,70 @@ mod tests { ); } + #[test] + fn mysql_setup_queries_respect_explicit_group_concat_max_len() { + // An explicit value from the connection wins over the safety default. + assert_eq!( + mysql_setup_queries("mysql://host:3306/db?sessionVariables=group_concat_max_len%3D2048", &[]), + vec!["USE `db`", "SET SESSION group_concat_max_len=2048", "SET NAMES utf8mb4"] + ); + assert_eq!( + mysql_setup_queries( + "mysql://host:3306/db?sessionVariables=query_timeout%3D60%2Cgroup_concat_max_len%3D512", + &[] + ), + vec!["USE `db`", "SET SESSION query_timeout=60,SESSION group_concat_max_len=512", "SET NAMES utf8mb4"] + ); + } + + #[test] + fn mysql_setup_queries_follow_server_group_concat_max_len() { + // `@@global.group_concat_max_len` keeps DBX aligned with the server value. + assert_eq!( + mysql_setup_queries( + "mysql://host:3306/db?sessionVariables=group_concat_max_len%3D%40%40global.group_concat_max_len", + &[], + ), + vec!["USE `db`", "SET SESSION group_concat_max_len=@@global.group_concat_max_len", "SET NAMES utf8mb4",] + ); + } + + #[test] + fn mysql_setup_queries_match_group_concat_max_len_case_insensitively() { + assert_eq!( + mysql_setup_queries("mysql://host:3306/db?sessionVariables=GROUP_CONCAT_MAX_LEN%3D512", &[]), + vec!["USE `db`", "SET SESSION GROUP_CONCAT_MAX_LEN=512", "SET NAMES utf8mb4"] + ); + assert_eq!( + mysql_setup_queries("mysql://host:3306/db?sessionVariables=%40%40session.group_concat_max_len%3D512", &[],), + vec!["USE `db`", "SET @@session.group_concat_max_len=512", "SET NAMES utf8mb4"] + ); + } + + #[test] + fn mysql_setup_queries_keep_group_concat_default_for_other_session_variables() { + // A session user variable (or a global-only assignment) must not disable + // the built-in safety default. + assert_eq!( + mysql_setup_queries("mysql://host:3306/db?sessionVariables=%40group_concat_max_len%3D2048", &[]), + vec![ + "USE `db`", + "SET @group_concat_max_len=2048", + "SET NAMES utf8mb4", + "SET SESSION group_concat_max_len = 1048576", + ] + ); + assert_eq!( + mysql_setup_queries("mysql://host:3306/db?sessionVariables=%40%40global.group_concat_max_len%3D512", &[]), + vec![ + "USE `db`", + "SET @@global.group_concat_max_len=512", + "SET NAMES utf8mb4", + "SET SESSION group_concat_max_len = 1048576", + ] + ); + } + #[test] fn mysql_setup_queries_apply_explicit_time_zone() { assert_eq!( diff --git a/docs/screenshots/issue-9412-connection-dialog.png b/docs/screenshots/issue-9412-connection-dialog.png new file mode 100644 index 0000000000..1dd04a5fec Binary files /dev/null and b/docs/screenshots/issue-9412-connection-dialog.png differ diff --git a/docs/screenshots/issue-9412-session-variables-hint.png b/docs/screenshots/issue-9412-session-variables-hint.png new file mode 100644 index 0000000000..b547934e7f Binary files /dev/null and b/docs/screenshots/issue-9412-session-variables-hint.png differ