fix(dameng): normalize copied table target names

This commit is contained in:
t8y2 2026-08-01 23:43:58 +08:00
parent fcedfef63d
commit e33d8acb96
No known key found for this signature in database
6 changed files with 73 additions and 9 deletions

View File

@ -2115,6 +2115,7 @@ async function confirmPasteTable() {
schema,
sourceName: entry.sourceName,
targetName,
normalizeNewTargetName: mode === "structure-and-data",
...dataCopyColumnOptions,
});
const executed = await executeObjectBrowserSqlWithProductionGuard(dataSql, () => api.executeQuery(props.connection.id, props.database, dataSql, schema));

View File

@ -16,5 +16,6 @@ describe("ObjectBrowser cross-database table paste", () => {
expect(objectBrowserSource).toContain("if (canTransferTableClipboard()) {");
expect(objectBrowserSource).toContain("if (!canPasteTableClipboard() && !canTransferTableClipboard()) return;");
expect(objectBrowserSource).toContain("pasteTableMode.value = defaultPasteTableMode(effectiveDatabaseType.value);");
expect(objectBrowserSource).toContain('normalizeNewTargetName: mode === "structure-and-data"');
});
});

View File

@ -3075,6 +3075,7 @@ async function confirmPasteTable() {
schema: entry.schema,
sourceName: entry.sourceName,
targetName,
normalizeNewTargetName: mode === "structure-and-data",
...dataCopyColumnOptions,
});
const dataExecuted = await executeTreeNodeSqlWithProductionGuard(entry, dataSql, { database: entry.database, schema: entry.schema });

View File

@ -16,6 +16,7 @@ describe("cross-database table paste", () => {
it("retains the local duplicate-table paste path", () => {
expect(runtimeSource).toContain("if (canTransferTreeClipboardToCurrentNode()) return openTransferFromTreeClipboard();");
expect(runtimeSource).toContain("pasteTableMode.value = defaultPasteTableMode(currentDatabaseType());");
expect(runtimeSource).toContain('normalizeNewTargetName: mode === "structure-and-data"');
});
it("carries table comments through the local sidebar paste path", () => {

View File

@ -74,6 +74,7 @@ export interface CopyTableDataSqlOptions {
columns?: string[];
postgresOverridingSystemValue?: boolean;
sqlserverIdentityInsert?: boolean;
normalizeNewTargetName?: boolean;
}
export function buildDropObjectSql(options: DropObjectSqlOptions): Promise<string> {

View File

@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use crate::models::connection::DatabaseType;
use crate::sql_dialect::{is_schema_aware, qualified_table_name, quote_table_identifier};
use crate::sql_dialect::{is_schema_aware, profile_for, qualified_table_name, quote_table_identifier};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
@ -190,6 +190,8 @@ pub struct CopyTableDataSqlOptions {
pub postgres_overriding_system_value: bool,
#[serde(default)]
pub sqlserver_identity_insert: bool,
#[serde(default)]
pub normalize_new_target_name: bool,
}
const MYSQL_COMPATIBLE_PROFILES: &[&str] = &["mysql", "mariadb", "tidb", "oceanbase", "custom_mysql"];
@ -599,7 +601,8 @@ pub fn build_drop_schema_sql(options: SchemaNameSqlOptions) -> String {
pub fn build_duplicate_table_structure_sql(options: DuplicateTableStructureSqlOptions) -> String {
let source = qualified_name(options.database_type, options.schema.as_deref(), &options.source_name);
let target = qualified_name(options.database_type, options.schema.as_deref(), &options.target_name);
let target =
qualified_duplicate_target_name(options.database_type, options.schema.as_deref(), &options.target_name);
let structure_sql = if options.database_type == Some(DatabaseType::Mysql) {
format!("CREATE TABLE {target} LIKE {source};")
} else if options.database_type == Some(DatabaseType::Questdb) {
@ -642,7 +645,11 @@ pub fn build_duplicate_table_structure_sql(options: DuplicateTableStructureSqlOp
pub fn build_copy_table_data_sql(options: CopyTableDataSqlOptions) -> String {
let source = qualified_name(options.database_type, options.schema.as_deref(), &options.source_name);
let target = qualified_name(options.database_type, options.schema.as_deref(), &options.target_name);
let target = if options.normalize_new_target_name {
qualified_duplicate_target_name(options.database_type, options.schema.as_deref(), &options.target_name)
} else {
qualified_name(options.database_type, options.schema.as_deref(), &options.target_name)
};
let Some(columns) = options.columns.filter(|columns| !columns.is_empty()) else {
return format!("INSERT INTO {target} SELECT * FROM {source};");
};
@ -821,6 +828,18 @@ fn qualified_name(database_type: Option<DatabaseType>, schema: Option<&str>, nam
}
}
fn qualified_duplicate_target_name(database_type: Option<DatabaseType>, schema: Option<&str>, name: &str) -> String {
if database_type != Some(DatabaseType::Dameng) {
return qualified_name(database_type, schema, name);
}
let target = profile_for(DatabaseType::Dameng).quote_ident(name);
if schema.is_some_and(|schema| !schema.is_empty()) {
format!("{}.{}", quote_rename_identifier(database_type, schema.unwrap()), target)
} else {
target
}
}
fn iotdb_timeseries_pattern(path: &str) -> String {
let path = path.trim().trim_end_matches(';');
if path.ends_with(".*") || path.ends_with(".**") {
@ -1604,7 +1623,7 @@ mod tests {
database_type: Some(DatabaseType::Dameng),
schema: Some("APP".to_string()),
source_name: "USERS".to_string(),
target_name: "USERS_COPY".to_string(),
target_name: "users_copy".to_string(),
table_comment: None,
column_comments: vec![
DuplicateTableColumnComment {
@ -1617,17 +1636,27 @@ mod tests {
});
assert_eq!(
dameng_sql,
"CREATE TABLE \"APP\".\"USERS_COPY\" AS SELECT * FROM \"APP\".\"USERS\" WHERE 1=0;\nCOMMENT ON COLUMN \"APP\".\"USERS_COPY\".\"DISPLAY\"\"NAME\" IS ' Owner''s; display name';\nCOMMENT ON COLUMN \"APP\".\"USERS_COPY\".\"STATUS\" IS 'active ';"
"CREATE TABLE \"APP\".USERS_COPY AS SELECT * FROM \"APP\".\"USERS\" WHERE 1=0;\nCOMMENT ON COLUMN \"APP\".USERS_COPY.\"DISPLAY\"\"NAME\" IS ' Owner''s; display name';\nCOMMENT ON COLUMN \"APP\".USERS_COPY.\"STATUS\" IS 'active ';"
);
assert_eq!(
crate::sql::split_sql_statements_for_database(&dameng_sql, DatabaseType::Dameng),
vec![
"CREATE TABLE \"APP\".\"USERS_COPY\" AS SELECT * FROM \"APP\".\"USERS\" WHERE 1=0".to_string(),
"COMMENT ON COLUMN \"APP\".\"USERS_COPY\".\"DISPLAY\"\"NAME\" IS ' Owner''s; display name'"
.to_string(),
"COMMENT ON COLUMN \"APP\".\"USERS_COPY\".\"STATUS\" IS 'active '".to_string(),
"CREATE TABLE \"APP\".USERS_COPY AS SELECT * FROM \"APP\".\"USERS\" WHERE 1=0".to_string(),
"COMMENT ON COLUMN \"APP\".USERS_COPY.\"DISPLAY\"\"NAME\" IS ' Owner''s; display name'".to_string(),
"COMMENT ON COLUMN \"APP\".USERS_COPY.\"STATUS\" IS 'active '".to_string(),
]
);
assert_eq!(
build_duplicate_table_structure_sql(DuplicateTableStructureSqlOptions {
database_type: Some(DatabaseType::Dameng),
schema: Some("APP".to_string()),
source_name: "USERS".to_string(),
target_name: "UsersCopy".to_string(),
table_comment: None,
column_comments: vec![],
}),
"CREATE TABLE \"APP\".\"UsersCopy\" AS SELECT * FROM \"APP\".\"USERS\" WHERE 1=0"
);
for database_type in [
DatabaseType::Postgres,
DatabaseType::Redshift,
@ -1692,6 +1721,7 @@ mod tests {
columns: None,
postgres_overriding_system_value: false,
sqlserver_identity_insert: false,
normalize_new_target_name: false,
}),
"INSERT INTO \"users_copy\" SELECT * FROM \"users\";"
);
@ -1704,6 +1734,7 @@ mod tests {
columns: Some(vec!["id".to_string(), "name".to_string()]),
postgres_overriding_system_value: false,
sqlserver_identity_insert: false,
normalize_new_target_name: false,
}),
"INSERT INTO `users_copy` (`id`, `name`) SELECT `id`, `name` FROM `users`;"
);
@ -1716,6 +1747,7 @@ mod tests {
columns: Some(vec!["id".to_string(), "name".to_string()]),
postgres_overriding_system_value: true,
sqlserver_identity_insert: false,
normalize_new_target_name: false,
}),
"INSERT INTO \"public\".\"users_copy\" (\"id\", \"name\") OVERRIDING SYSTEM VALUE SELECT \"id\", \"name\" FROM \"public\".\"users\";"
);
@ -1728,9 +1760,36 @@ mod tests {
columns: Some(vec!["id".to_string(), "name".to_string()]),
postgres_overriding_system_value: false,
sqlserver_identity_insert: true,
normalize_new_target_name: false,
}),
"SET IDENTITY_INSERT [dbo].[users_copy] ON;\nINSERT INTO [dbo].[users_copy] ([id], [name]) SELECT [id], [name] FROM [dbo].[users];\nSET IDENTITY_INSERT [dbo].[users_copy] OFF;"
);
assert_eq!(
build_copy_table_data_sql(CopyTableDataSqlOptions {
database_type: Some(DatabaseType::Dameng),
schema: Some("APP".to_string()),
source_name: "users".to_string(),
target_name: "users_copy".to_string(),
columns: None,
postgres_overriding_system_value: false,
sqlserver_identity_insert: false,
normalize_new_target_name: true,
}),
"INSERT INTO \"APP\".USERS_COPY SELECT * FROM \"APP\".\"users\";"
);
assert_eq!(
build_copy_table_data_sql(CopyTableDataSqlOptions {
database_type: Some(DatabaseType::Dameng),
schema: Some("APP".to_string()),
source_name: "users".to_string(),
target_name: "users_copy".to_string(),
columns: None,
postgres_overriding_system_value: false,
sqlserver_identity_insert: false,
normalize_new_target_name: false,
}),
"INSERT INTO \"APP\".\"users_copy\" SELECT * FROM \"APP\".\"users\";"
);
}
#[test]