fix: allow mysql timestamp default null ddl

This commit is contained in:
vvrf 2026-05-27 13:31:22 +08:00 committed by GitHub
parent 745f37fa6d
commit bcfad319a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 86 additions and 2 deletions

View File

@ -306,6 +306,59 @@ fn mysql_setup_queries(url: &str) -> Vec<String> {
vec![format!("SET NAMES {charset}")]
}
fn should_enable_explicit_timestamp_defaults(sql: &str) -> bool {
if !starts_with_executable_sql_keyword(sql, &["CREATE", "ALTER"]) {
return false;
}
let lower = sql.split_whitespace().collect::<Vec<_>>().join(" ").to_ascii_lowercase();
lower.contains("timestamp") && lower.contains("default null")
}
fn explicit_timestamp_defaults_sql(enabled: bool) -> &'static str {
if enabled {
"SET SESSION explicit_defaults_for_timestamp = ON"
} else {
"SET SESSION explicit_defaults_for_timestamp = OFF"
}
}
async fn enable_explicit_timestamp_defaults_for_query(conn: &mut mysql_async::Conn, sql: &str) -> Option<bool> {
if !should_enable_explicit_timestamp_defaults(sql) {
return None;
}
let previous = match conn.query_first::<u8, _>("SELECT @@SESSION.explicit_defaults_for_timestamp").await {
Ok(Some(value)) => value != 0,
Ok(None) => {
log::debug!("Skipping MySQL explicit timestamp defaults compatibility setting: variable was empty");
return None;
}
Err(err) => {
log::debug!("Skipping MySQL explicit timestamp defaults compatibility setting: {err}");
return None;
}
};
if previous {
return None;
}
if let Err(err) = conn.query_drop(explicit_timestamp_defaults_sql(true)).await {
log::debug!("Skipping MySQL explicit timestamp defaults compatibility setting: {err}");
return None;
}
Some(previous)
}
async fn restore_explicit_timestamp_defaults_for_query(conn: &mut mysql_async::Conn, previous: Option<bool>) {
if let Some(previous) = previous {
if let Err(err) = conn.query_drop(explicit_timestamp_defaults_sql(previous)).await {
log::warn!("Failed to restore MySQL explicit timestamp defaults session setting: {err}");
}
}
}
fn mysql_connection_charset(url: &str) -> Option<&str> {
let (_, query) = url.split_once('?')?;
query.split('&').find_map(|segment| {
@ -710,9 +763,18 @@ pub async fn execute_query_with_max_rows(
}
} else {
let mut conn = pool.get_conn().await.map_err(|e| e.to_string())?;
let result = conn.query_iter(sql).await.map_err(|e| e.to_string())?;
let previous_explicit_timestamp_defaults = enable_explicit_timestamp_defaults_for_query(&mut conn, sql).await;
let result = match conn.query_iter(sql).await {
Ok(result) => result,
Err(err) => {
restore_explicit_timestamp_defaults_for_query(&mut conn, previous_explicit_timestamp_defaults).await;
return Err(err.to_string());
}
};
let affected_rows = result.affected_rows();
result.drop_result().await.map_err(|e| e.to_string())?;
let drop_result = result.drop_result().await;
restore_explicit_timestamp_defaults_for_query(&mut conn, previous_explicit_timestamp_defaults).await;
drop_result.map_err(|e| e.to_string())?;
Ok(QueryResult {
columns: vec![],
@ -989,6 +1051,28 @@ mod tests {
assert!(!requires_text_protocol_query("SELECT * FROM users"));
}
#[test]
fn mysql_timestamp_default_null_ddl_enables_explicit_defaults() {
let create_sql = r#"
CREATE TABLE `referral_record` (
`id` BINARY(16) NOT NULL,
`created_at` TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
`updated_at` TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
`deleted_at` TIMESTAMP(6) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB
"#;
assert!(should_enable_explicit_timestamp_defaults(create_sql));
assert!(should_enable_explicit_timestamp_defaults(
"ALTER TABLE referral_record ADD deleted_at TIMESTAMP DEFAULT NULL"
));
assert!(!should_enable_explicit_timestamp_defaults("CREATE TABLE t (deleted_at DATETIME(6) DEFAULT NULL)"));
assert!(!should_enable_explicit_timestamp_defaults("SELECT 'TIMESTAMP DEFAULT NULL'"));
assert_eq!(explicit_timestamp_defaults_sql(true), "SET SESSION explicit_defaults_for_timestamp = ON");
assert_eq!(explicit_timestamp_defaults_sql(false), "SET SESSION explicit_defaults_for_timestamp = OFF");
}
#[test]
fn mysql_tls_session_close_errors_retry_without_ssl() {
let error = "MySQL connection failed: error communicating with database: \