fix(sql): handle database drag and routine export

This commit is contained in:
zipg authored and GitHub committed 2026-07-07 15:54:48 +08:00
1 parent 14c383cb43
commit d50f1761d6
5 files changed
+152 -15

No files matched your search

+19 -6
View File
@@ -5,6 +5,7 @@ use std::io::Write;
use tokio::sync::RwLock;
use crate::models::connection::DatabaseType;
use crate::object_source_sql::build_export_object_source_sql;
use crate::sql_dialect::{qualified_table_name, quote_table_identifier, uses_single_row_insert_statements};
use crate::transfer::{
format_ch_array_sql_literal, format_pg_array_sql_literal, is_identity_column_extra, quote_identifier,
@@ -1224,8 +1225,10 @@ pub async fn export_database_sql_core(
.await
{
Ok(obj_source) => {
if !obj_source.source.is_empty() {
writeln!(file, "{};\n", obj_source.source).map_err(|e| format!("Failed to write file: {e}"))?;
let source =
build_export_object_source_sql(db_type, crate::db::ObjectSourceKind::View, &obj_source.source);
if !source.is_empty() {
writeln!(file, "{source}\n").map_err(|e| format!("Failed to write file: {e}"))?;
}
}
Err(e) => {
@@ -1265,8 +1268,13 @@ pub async fn export_database_sql_core(
.await
{
Ok(obj_source) => {
if !obj_source.source.is_empty() {
writeln!(file, "{};\n", obj_source.source).map_err(|e| format!("Failed to write file: {e}"))?;
let source = build_export_object_source_sql(
db_type,
crate::db::ObjectSourceKind::Procedure,
&obj_source.source,
);
if !source.is_empty() {
writeln!(file, "{source}\n").map_err(|e| format!("Failed to write file: {e}"))?;
}
}
Err(e) => {
@@ -1306,8 +1314,13 @@ pub async fn export_database_sql_core(
.await
{
Ok(obj_source) => {
if !obj_source.source.is_empty() {
writeln!(file, "{};\n", obj_source.source).map_err(|e| format!("Failed to write file: {e}"))?;
let source = build_export_object_source_sql(
db_type,
crate::db::ObjectSourceKind::Function,
&obj_source.source,
);
if !source.is_empty() {
writeln!(file, "{source}\n").map_err(|e| format!("Failed to write file: {e}"))?;
}
}
Err(e) => {
+66
View File
@@ -227,6 +227,21 @@ pub fn build_view_ddl_sql(input: BuildViewDdlInput) -> String {
format!("CREATE VIEW {qualified_name} AS\n{}", ensure_semicolon(source))
}
pub fn build_export_object_source_sql(
database_type: DatabaseType,
object_type: ObjectSourceKind,
source: &str,
) -> String {
let source = source.trim();
if source.is_empty() {
return String::new();
}
if is_mysql_like(database_type) && matches!(object_type, ObjectSourceKind::Procedure | ObjectSourceKind::Function) {
return mysql_delimited_routine_source(source);
}
ensure_semicolon(source)
}
pub fn object_source_save_execution_mode(_database_type: DatabaseType) -> ObjectSourceSaveExecutionMode {
ObjectSourceSaveExecutionMode::Single
}
@@ -317,6 +332,23 @@ fn ensure_semicolon(sql: &str) -> String {
}
}
fn mysql_delimited_routine_source(source: &str) -> String {
let trimmed = source.trim();
if Regex::new(r"(?i)^\s*DELIMITER\b").unwrap().is_match(trimmed) {
return trimmed.to_string();
}
let body = trimmed.trim_end_matches(';').trim_end();
let delimiter = mysql_routine_script_delimiter(body);
format!("DELIMITER {delimiter}\n{body}{delimiter}\nDELIMITER ;")
}
fn mysql_routine_script_delimiter(source: &str) -> &'static str {
["//", "$$", ";;", "__DBX_DELIMITER__"]
.into_iter()
.find(|delimiter| !source.contains(delimiter))
.unwrap_or("__DBX_DELIMITER__")
}
fn source_starts_with_create_or_alter(source: &str) -> bool {
Regex::new(r"(?i)^\s*(?:CREATE|ALTER)\s+").unwrap().is_match(source)
}
@@ -1217,6 +1249,40 @@ mod tests {
);
}
#[test]
fn mysql_routine_export_uses_delimiter_script() {
let sql = build_export_object_source_sql(
DatabaseType::Mysql,
ObjectSourceKind::Procedure,
"CREATE DEFINER=`root`@`%` PROCEDURE `refresh_cache`()\nBEGIN\n SELECT 1;\nEND",
);
assert_eq!(
sql,
"DELIMITER //\nCREATE DEFINER=`root`@`%` PROCEDURE `refresh_cache`()\nBEGIN\n SELECT 1;\nEND//\nDELIMITER ;"
);
}
#[test]
fn mysql_routine_export_does_not_double_wrap_delimiter_script() {
let source = "DELIMITER //\nCREATE PROCEDURE `refresh_cache`()\nBEGIN\n SELECT 1;\nEND//\nDELIMITER ;";
let sql = build_export_object_source_sql(DatabaseType::Mysql, ObjectSourceKind::Procedure, source);
assert_eq!(sql, source);
}
#[test]
fn mysql_view_export_keeps_regular_statement_terminator() {
let sql = build_export_object_source_sql(
DatabaseType::Mysql,
ObjectSourceKind::View,
"CREATE VIEW `active_users` AS SELECT `id` FROM `users`",
);
assert_eq!(sql, "CREATE VIEW `active_users` AS SELECT `id` FROM `users`;");
}
#[test]
fn sqlserver_view_source_opened_for_editing_shows_alter_view() {
let sql = build_editable_object_source(EditableObjectSourceSqlInput {