From 58f05d91c3a135b432510f1867a2bdfd5d8bc2ac Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 12 Aug 2026 20:42:45 +0800 Subject: [PATCH] fix(gaussdb): retrieve M-mode view DDL --- crates/dbx-core/src/schema.rs | 136 ++++++++++++++++++++++++++++++++-- 1 file changed, 128 insertions(+), 8 deletions(-) diff --git a/crates/dbx-core/src/schema.rs b/crates/dbx-core/src/schema.rs index 8ab822f1ea..4ea7dfef78 100644 --- a/crates/dbx-core/src/schema.rs +++ b/crates/dbx-core/src/schema.rs @@ -2652,9 +2652,9 @@ mod tests { dameng_object_statistics_rows_only_sql, dameng_object_statistics_user_segments_sql, deduplicate_column_infos, ephemeral_agent_metadata_session_id, external_driver_uses_mysql_ddl, filter_mongodb_agent_collections, filter_mysql_system_databases_for_config, filter_object_infos, filter_table_infos, filter_visible_schema_names, - gbase8a_object_statistics_sql, is_agent_postgres_metadata_fallback_config, is_mysql_external_driver_config, - is_retryable_metadata_error, metadata_error_action, metadata_name_or_comment_matches, - mysql_external_driver_ddl_from_query_result, mysql_external_driver_ddl_sql, + gaussdb_m_view_object_source_sql, gbase8a_object_statistics_sql, is_agent_postgres_metadata_fallback_config, + is_mysql_external_driver_config, is_retryable_metadata_error, metadata_error_action, + metadata_name_or_comment_matches, mysql_external_driver_ddl_from_query_result, mysql_external_driver_ddl_sql, mysql_object_source_ddl_column_index, mysql_object_source_sql, mysql_table_list_source_for_config, mysql_table_metadata_catalog, normalize_information_schema_table_type, oracle_columns_from_query_result, oracle_columns_sql, oracle_object_statistics_dba_segments_sql, oracle_object_statistics_from_query_result, @@ -2988,6 +2988,32 @@ mod tests { assert!(!external_driver_uses_mysql_ddl(&config)); } + #[test] + fn gaussdb_m_view_object_source_sql_is_qualified_and_gated() { + let mut config = test_connection_config(DatabaseType::Gaussdb); + config.driver_profile = Some("gaussdb-m".to_string()); + + assert_eq!( + gaussdb_m_view_object_source_sql(&config, "tenant`db", "active`users", &db::ObjectSourceKind::View) + .as_deref(), + Some("SHOW CREATE VIEW `tenant``db`.`active``users`") + ); + assert_eq!( + gaussdb_m_view_object_source_sql(&config, "", "active`users", &db::ObjectSourceKind::View).as_deref(), + Some("SHOW CREATE VIEW `active``users`") + ); + assert_eq!( + gaussdb_m_view_object_source_sql(&config, "tenant_db", "refresh_users", &db::ObjectSourceKind::Function), + None + ); + + config.driver_profile = Some("gaussdb".to_string()); + assert_eq!( + gaussdb_m_view_object_source_sql(&config, "tenant_db", "active_users", &db::ObjectSourceKind::View), + None + ); + } + #[test] fn mysql_external_driver_ddl_sql_uses_catalog_and_escaped_identifiers() { assert_eq!( @@ -3022,7 +3048,10 @@ mod tests { messages: Vec::new(), }; - assert_eq!(mysql_external_driver_ddl_from_query_result(result).unwrap(), "CREATE TABLE `users` (`id` bigint);"); + assert_eq!( + mysql_external_driver_ddl_from_query_result(result, "Create Table").unwrap(), + "CREATE TABLE `users` (`id` bigint);" + ); } #[test] @@ -3044,11 +3073,66 @@ mod tests { }; assert_eq!( - mysql_external_driver_ddl_from_query_result(result).unwrap(), + mysql_external_driver_ddl_from_query_result(result, "Create Table").unwrap(), "CREATE TABLE `users` (`id` bigint);\n" ); } + #[test] + fn mysql_external_driver_view_ddl_reads_named_column_case_insensitively() { + let result = db::QueryResult { + columns: vec!["View".to_string(), "Extra".to_string(), "CREATE VIEW".to_string()], + column_types: Vec::new(), + column_sortables: Vec::new(), + spatial_columns: vec![], + spatial_values: vec![], + rows: vec![vec![ + serde_json::json!("active_users"), + serde_json::json!("ignored"), + serde_json::json!("CREATE VIEW `active_users` AS SELECT 1"), + ]], + affected_rows: 0, + execution_time_ms: 0, + truncated: false, + session_id: None, + has_more: false, + elasticsearch_raw_body: None, + messages: Vec::new(), + }; + + assert_eq!( + mysql_external_driver_ddl_from_query_result(result, "Create View").unwrap(), + "CREATE VIEW `active_users` AS SELECT 1;" + ); + } + + #[test] + fn mysql_external_driver_view_ddl_falls_back_to_second_column() { + let result = db::QueryResult { + columns: vec!["name".to_string(), "definition".to_string()], + column_types: Vec::new(), + column_sortables: Vec::new(), + spatial_columns: vec![], + spatial_values: vec![], + rows: vec![vec![ + serde_json::json!("active_users"), + serde_json::json!("CREATE VIEW `active_users` AS SELECT 1;\n"), + ]], + affected_rows: 0, + execution_time_ms: 0, + truncated: false, + session_id: None, + has_more: false, + elasticsearch_raw_body: None, + messages: Vec::new(), + }; + + assert_eq!( + mysql_external_driver_ddl_from_query_result(result, "Create View").unwrap(), + "CREATE VIEW `active_users` AS SELECT 1;\n" + ); + } + #[test] fn mysql_object_source_sql_qualifies_cross_database_objects() { assert_eq!( @@ -6658,13 +6742,26 @@ fn external_driver_uses_mysql_ddl(config: &ConnectionConfig) -> bool { is_mysql_external_driver_config(config) || gaussdb_uses_m_jdbc_driver(config) } +fn gaussdb_m_view_object_source_sql( + config: &ConnectionConfig, + database: &str, + name: &str, + kind: &db::ObjectSourceKind, +) -> Option { + (gaussdb_uses_m_jdbc_driver(config) && matches!(kind, db::ObjectSourceKind::View)) + .then(|| mysql_object_source_sql(database, name, kind)) +} + fn mysql_external_driver_ddl_sql(database: &str, schema: &str, table: &str) -> String { format!("SHOW CREATE TABLE {}", mysql_qualified_name(mysql_table_metadata_catalog(database, schema), table)) } -fn mysql_external_driver_ddl_from_query_result(result: db::QueryResult) -> Result { +fn mysql_external_driver_ddl_from_query_result( + result: db::QueryResult, + named_ddl_column: &str, +) -> Result { let row = result.rows.first().ok_or_else(|| "DDL not found".to_string())?; - let named_index = result.columns.iter().position(|column| column.trim().eq_ignore_ascii_case("Create Table")); + let named_index = result.columns.iter().position(|column| column.trim().eq_ignore_ascii_case(named_ddl_column)); let ddl = named_index .into_iter() .chain(std::iter::once(1)) @@ -7287,6 +7384,29 @@ async fn get_object_source_once( let config = config.clone(); let session = session.clone(); drop(connections); + if let Some(sql) = gaussdb_m_view_object_source_sql(config.as_ref(), database, name, &object_type) { + let result: db::QueryResult = session + .invoke_with_timeout( + "executeQuery", + serde_json::json!({ + "connection": config.as_ref(), + "database": database, + "schema": schema, + "sql": sql, + "maxRows": 1, + }), + agent_metadata_timeout(Some(config.as_ref())), + ) + .await?; + let source = mysql_external_driver_ddl_from_query_result(result, "Create View")?; + return Ok(db::ObjectSource { + name: name.to_string(), + object_type, + schema: if schema.is_empty() { None } else { Some(schema.to_string()) }, + source, + editable: None, + }); + } let result: db::ObjectSource = session .invoke_with_timeout( "getObjectSource", @@ -8814,7 +8934,7 @@ async fn external_driver_mysql_ddl( agent_metadata_timeout(Some(config)), ) .await?; - mysql_external_driver_ddl_from_query_result(result) + mysql_external_driver_ddl_from_query_result(result, "Create Table") } fn ensure_display_ddl_terminated(sql: String) -> String {