fix(mysql): 显式配置的 group_concat_max_len 稳定生效并补充连接表单提示 (#9582)

DBX 连接 MySQL 时会执行 SET SESSION group_concat_max_len = 1048576,
避免 GROUP_CONCAT 结果被静默截断(#2775)。但当用户在 URL 参数里用
Connector/J 语法 sessionVariables=group_concat_max_len=... 显式指定值时,
内置语句与用户语句的执行先后取决于底层驱动,覆盖行为不具备确定性;
连接表单也没有任何提示,导致按服务端配置排查问题时被误导(#9412)。

- 新增显式覆盖检测:解析 sessionVariables 中的赋值目标,命中
  group_concat_max_len(含大小写、@ 前缀、session 限定)时不再下发内置的
  SET SESSION,用户/服务端的值始终生效;@user 变量与 @@global. 赋值不影响
  默认行为。
- 连接表单 URL 参数下方为 MySQL 增加提示,说明可用的覆盖写法
  (sessionVariables=group_concat_max_len=@@global.group_concat_max_len 即
  完全跟随服务端配置),并补齐 10 个语言包文案。

Co-authored-by: zipg <4047349+zipg@users.noreply.github.com>
This commit is contained in:
zipg
2026-09-19 15:23:16 +08:00
committed by GitHub
co-authored by zipg
parent 1f3beb11dc
commit 687b988b7d
14 changed files with 136 additions and 10 deletions
@@ -8200,6 +8200,9 @@ function openExternalUrl(url: string) {
<p v-if="showGenericUrlParamsHint" class="text-xs leading-5 text-muted-foreground">
{{ t("connection.localInfilePathHint") }}
</p>
<p v-if="form.db_type === 'mysql'" class="text-xs leading-5 text-muted-foreground">
{{ t("connection.sessionVariablesHint") }}
</p>
</div>
</div>
+2
View File
@@ -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",
+2
View File
@@ -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",
+2
View File
@@ -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",
+2
View File
@@ -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",
+2
View File
@@ -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トークン",
+2
View File
@@ -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",
+2
View File
@@ -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",
+2
View File
@@ -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",
+1
View File
@@ -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",
+1
View File
@@ -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",
+115 -10
View File
@@ -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<String> {
fn group_concat_max_len_query(self, url: &str) -> Option<String> {
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<String> {
})
}
fn mysql_connection_session_variables(url: &str) -> Option<String> {
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<String> {
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<String> {
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!(
Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB