feat(transfer): add tree-based object selection

This commit is contained in:
GIWTO authored and GitHub committed 2026-08-02 16:34:50 +08:00
1 parent fb7b2878fd
commit 473ef910b6
28 files changed
+5395 -205

No files matched your search

+81
View File
@@ -2798,6 +2798,14 @@ fn wants_routine_objects(object_types: Option<&[String]>) -> bool {
requested_object_type(object_types, "PROCEDURE") || requested_object_type(object_types, "FUNCTION")
}
fn wants_trigger_objects(object_types: Option<&[String]>) -> bool {
requested_object_type(object_types, "TRIGGER")
}
fn wants_event_objects(object_types: Option<&[String]>) -> bool {
requested_object_type(object_types, "EVENT")
}
fn sql_pagination(limit: Option<usize>, offset: Option<usize>) -> String {
limit.map_or_else(String::new, |limit| format!(" LIMIT {limit} OFFSET {}", offset.unwrap_or(0)))
}
@@ -2888,6 +2896,32 @@ fn row_to_object(row: &mysql_async::Row, database: &str) -> ObjectInfo {
}
}
fn list_triggers_objects_sql(database: &str) -> String {
format!(
"SELECT TRIGGER_NAME AS object_name, 'TRIGGER' AS object_type, NULL AS object_comment, \
CREATED AS created_at, NULL AS updated_at, \
TRIGGER_SCHEMA AS parent_schema, EVENT_OBJECT_TABLE AS parent_name, \
5 AS sort_order \
FROM information_schema.TRIGGERS \
WHERE TRIGGER_SCHEMA = {} \
ORDER BY object_name",
quote_value(database)
)
}
fn list_events_objects_sql(database: &str) -> String {
format!(
"SELECT EVENT_NAME AS object_name, 'EVENT' AS object_type, NULL AS object_comment, \
CREATED AS created_at, LAST_ALTERED AS updated_at, \
EVENT_SCHEMA AS parent_schema, NULL AS parent_name, \
6 AS sort_order \
FROM information_schema.EVENTS \
WHERE EVENT_SCHEMA = {} \
ORDER BY object_name",
quote_value(database)
)
}
pub struct PagedObjectList {
pub objects: Vec<ObjectInfo>,
pub paging_applied: bool,
@@ -2985,6 +3019,40 @@ pub async fn list_objects(
}
}
if wants_trigger_objects(object_types) {
let triggers_sql = list_triggers_objects_sql(database);
match conn.query_iter(&triggers_sql).await {
Ok(result) => match result.collect_and_drop::<mysql_async::Row>().await {
Ok(trigger_rows) => {
objects.extend(trigger_rows.iter().map(|row| row_to_object(row, database)));
}
Err(e) => {
log::warn!("Skipping triggers for database `{}` in object browser: {}", database, e);
}
},
Err(e) => {
log::warn!("Skipping triggers for database `{}` in object browser: {}", database, e);
}
}
}
if wants_event_objects(object_types) {
let events_sql = list_events_objects_sql(database);
match conn.query_iter(&events_sql).await {
Ok(result) => match result.collect_and_drop::<mysql_async::Row>().await {
Ok(event_rows) => {
objects.extend(event_rows.iter().map(|row| row_to_object(row, database)));
}
Err(e) => {
log::warn!("Skipping events for database `{}` in object browser: {}", database, e);
}
},
Err(e) => {
log::warn!("Skipping events for database `{}` in object browser: {}", database, e);
}
}
}
Ok(PagedObjectList { objects, paging_applied })
}
@@ -5179,6 +5247,19 @@ mod tests {
assert!(sql.contains("TRIGGER_SCHEMA = 'app'"));
}
#[test]
fn lists_triggers_and_events_via_information_schema() {
let sql = list_triggers_objects_sql("shop");
assert!(sql.contains("information_schema.TRIGGERS"));
assert!(sql.contains("TRIGGER_SCHEMA = 'shop'"));
let sql = list_events_objects_sql("shop");
assert!(sql.contains("information_schema.EVENTS"));
assert!(sql.contains("EVENT_SCHEMA = 'shop'"));
assert!(wants_trigger_objects(Some(&["TRIGGER".to_string()])));
assert!(!wants_trigger_objects(Some(&["TABLE".to_string()])));
assert!(wants_event_objects(Some(&["EVENT".to_string()])));
}
#[test]
fn mysql_completion_like_pattern_uses_prefix_by_default() {
assert_eq!(mysql_completion_like_pattern("Temp", Some(&CompletionAssistantMatchMode::Prefix)), "Temp%");
File diff suppressed because it is too large. Load diff
@@ -218,6 +218,8 @@ async fn live_postgres_transfer_preserves_data_and_schema_objects() {
target_catalog: None,
tables: vec!["users".to_string(), "audit_logs".to_string(), "files".to_string()],
create_table: true,
content: dbx_core::transfer::TransferContent::default(),
objects: Vec::new(),
mode: TransferMode::Append,
target_table_name_case: TransferTableNameCase::Preserve,
ownership_policy: TransferOwnershipPolicy::Preserve,
@@ -501,6 +503,8 @@ async fn live_postgres_transfer_skips_create_ddl_for_existing_target_table() {
target_catalog: None,
tables: vec!["items".to_string()],
create_table: true,
content: dbx_core::transfer::TransferContent::default(),
objects: Vec::new(),
mode: TransferMode::Append,
target_table_name_case: TransferTableNameCase::Preserve,
ownership_policy: TransferOwnershipPolicy::Preserve,
@@ -1361,6 +1361,8 @@ async fn live_sqlserver_transfer_table_skips_rowversion_insert_column() {
target_catalog: None,
tables: vec![source_table.clone()],
create_table: true,
content: dbx_core::transfer::TransferContent::default(),
objects: Vec::new(),
mode: dbx_core::transfer::TransferMode::Append,
target_table_name_case: dbx_core::transfer::TransferTableNameCase::Upper,
ownership_policy: dbx_core::transfer::TransferOwnershipPolicy::Preserve,