mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-02 02:34:42 +08:00
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:
@@ -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>
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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トークン",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 |
Reference in New Issue
Block a user