feat(backup): support table filters and MySQL compatibility

This commit is contained in:
CSXFanMeng authored and GitHub committed 2026-07-19 10:32:33 +08:00
1 parent 09c3f7f44c
commit 7abac0959f
15 files changed
+386 -32

No files matched your search

+63 -7
View File
@@ -2918,14 +2918,26 @@ fn postgres_transaction_begin_sql(consistent_snapshot: bool) -> &'static str {
}
}
fn mysql_transaction_begin_sql(consistent_snapshot: bool) -> &'static str {
fn mysql_transaction_begin_sql_candidates(consistent_snapshot: bool) -> &'static [&'static str] {
if consistent_snapshot {
"START TRANSACTION WITH CONSISTENT SNAPSHOT, READ ONLY"
&[
"START TRANSACTION WITH CONSISTENT SNAPSHOT, READ ONLY",
"START TRANSACTION WITH CONSISTENT SNAPSHOT",
"START TRANSACTION",
]
} else {
"START TRANSACTION"
&["START TRANSACTION"]
}
}
fn mysql_transaction_isolation_sql(consistent_snapshot: bool) -> Option<&'static str> {
consistent_snapshot.then_some("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ")
}
fn mysql_error_is_syntax_error(error: &mysql_async::Error) -> bool {
matches!(error, mysql_async::Error::Server(server_error) if server_error.code == 1064)
}
async fn begin_transaction_session(
state: &AppState,
connection_id: &str,
@@ -2968,8 +2980,25 @@ async fn begin_transaction_session(
}
TxnPoolHandle::Mysql(mysql_pool) => {
let mut conn = mysql_pool.get_conn().await.map_err(|e| format!("Failed to get MySQL connection: {e}"))?;
let begin_sql = mysql_transaction_begin_sql(consistent_snapshot);
conn.query_drop(begin_sql).await.map_err(|e| format!("START TRANSACTION failed: {e}"))?;
if let Some(isolation_sql) = mysql_transaction_isolation_sql(consistent_snapshot) {
conn.query_drop(isolation_sql).await.map_err(|e| format!("SET TRANSACTION failed: {e}"))?;
}
let mut syntax_errors = Vec::new();
for begin_sql in mysql_transaction_begin_sql_candidates(consistent_snapshot) {
match conn.query_drop(*begin_sql).await {
Ok(()) => {
syntax_errors.clear();
break;
}
Err(error) if mysql_error_is_syntax_error(&error) => {
syntax_errors.push(format!("{begin_sql}: {error}"));
}
Err(error) => return Err(format!("START TRANSACTION failed: {error}")),
}
}
if !syntax_errors.is_empty() {
return Err(format!("START TRANSACTION failed for all compatible forms: {}", syntax_errors.join("; ")));
}
TxnConnection::Mysql(conn)
}
};
@@ -4956,8 +4985,35 @@ mod tests {
}
#[test]
fn database_backup_transactions_request_consistent_read_only_snapshots() {
fn database_backup_transactions_request_consistent_snapshots() {
assert_eq!(postgres_transaction_begin_sql(true), "BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY");
assert_eq!(mysql_transaction_begin_sql(true), "START TRANSACTION WITH CONSISTENT SNAPSHOT, READ ONLY");
assert_eq!(mysql_transaction_isolation_sql(true), Some("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ"));
assert_eq!(
mysql_transaction_begin_sql_candidates(true),
[
"START TRANSACTION WITH CONSISTENT SNAPSHOT, READ ONLY",
"START TRANSACTION WITH CONSISTENT SNAPSHOT",
"START TRANSACTION",
]
);
assert_eq!(mysql_transaction_isolation_sql(false), None);
assert_eq!(mysql_transaction_begin_sql_candidates(false), ["START TRANSACTION"]);
}
#[test]
fn mysql_backup_transaction_only_falls_back_for_syntax_errors() {
let syntax_error = mysql_async::Error::Server(mysql_async::ServerError {
code: 1064,
message: "unsupported transaction characteristic".to_string(),
state: "42000".to_string(),
});
let permission_error = mysql_async::Error::Server(mysql_async::ServerError {
code: 1044,
message: "access denied".to_string(),
state: "42000".to_string(),
});
assert!(mysql_error_is_syntax_error(&syntax_error));
assert!(!mysql_error_is_syntax_error(&permission_error));
}
}