From 0c6fc610cd934ff763d212f77bce9fd8a02463c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=88=E7=A7=8B?= <49514152+yiqiui@users.noreply.github.com> Date: Mon, 21 Sep 2026 19:04:29 +0800 Subject: [PATCH] fix(dameng): wrap identity columns when pasting table data --- apps/desktop/src/lib/database/dbAdminSql.ts | 1 + apps/desktop/src/lib/table/tableClipboard.ts | 2 + crates/dbx-sql/src/db_admin_sql.rs | 44 +++++++++++++++++++- packages/app-tests/tableClipboard.test.ts | 12 ++++++ 4 files changed, 58 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/lib/database/dbAdminSql.ts b/apps/desktop/src/lib/database/dbAdminSql.ts index f5a1658e9..6a48cb7ea 100644 --- a/apps/desktop/src/lib/database/dbAdminSql.ts +++ b/apps/desktop/src/lib/database/dbAdminSql.ts @@ -334,6 +334,7 @@ export interface CopyTableDataSqlOptions { columns?: string[]; postgresOverridingSystemValue?: boolean; sqlserverIdentityInsert?: boolean; + damengIdentityInsert?: boolean; normalizeNewTargetName?: boolean; /** Quote character reported by the connected server, for types whose quote is not fixed by the * database type alone (Cloud Spanner's two dialects differ). Mirrors `identifierQuote` on the diff --git a/apps/desktop/src/lib/table/tableClipboard.ts b/apps/desktop/src/lib/table/tableClipboard.ts index ba4afd6ce..a2eeaa614 100644 --- a/apps/desktop/src/lib/table/tableClipboard.ts +++ b/apps/desktop/src/lib/table/tableClipboard.ts @@ -18,6 +18,7 @@ export interface TableDataCopyColumnOptions { columns: string[]; postgresOverridingSystemValue: boolean; sqlserverIdentityInsert: boolean; + damengIdentityInsert: boolean; } export interface TablePasteFeedback { @@ -88,6 +89,7 @@ export function tableDataCopyColumnOptions(databaseType: DatabaseType | undefine columns: writableColumns.map((column) => column.name), postgresOverridingSystemValue: databaseType === "postgres" && writableColumns.some(isIdentityColumn), sqlserverIdentityInsert: databaseType === "sqlserver" && writableColumns.some(isIdentityColumn), + damengIdentityInsert: databaseType === "dameng" && writableColumns.some(isIdentityColumn), }; } diff --git a/crates/dbx-sql/src/db_admin_sql.rs b/crates/dbx-sql/src/db_admin_sql.rs index 06b727dba..d81009268 100644 --- a/crates/dbx-sql/src/db_admin_sql.rs +++ b/crates/dbx-sql/src/db_admin_sql.rs @@ -241,6 +241,8 @@ pub struct CopyTableDataSqlOptions { #[serde(default)] pub sqlserver_identity_insert: bool, #[serde(default)] + pub dameng_identity_insert: bool, + #[serde(default)] pub normalize_new_target_name: bool, #[serde(default, skip_serializing_if = "Option::is_none")] pub identifier_quote: Option, @@ -895,7 +897,10 @@ pub fn build_copy_table_data_sql(options: CopyTableDataSqlOptions) -> String { let insert_sql = format!( "INSERT INTO {target} ({target_column_list}){postgres_override} SELECT {source_column_list} FROM {source};" ); - if options.sqlserver_identity_insert && options.database_type == Some(DatabaseType::SqlServer) { + let needs_identity_insert = (options.sqlserver_identity_insert + && options.database_type == Some(DatabaseType::SqlServer)) + || (options.dameng_identity_insert && options.database_type == Some(DatabaseType::Dameng)); + if needs_identity_insert { return format!("SET IDENTITY_INSERT {target} ON;\n{insert_sql}\nSET IDENTITY_INSERT {target} OFF;"); } insert_sql @@ -2554,6 +2559,7 @@ mod tests { columns: None, postgres_overriding_system_value: false, sqlserver_identity_insert: false, + dameng_identity_insert: false, normalize_new_target_name: false, identifier_quote: None, }), @@ -2568,6 +2574,7 @@ mod tests { columns: Some(vec!["id".to_string(), "name".to_string()]), postgres_overriding_system_value: false, sqlserver_identity_insert: false, + dameng_identity_insert: false, normalize_new_target_name: false, identifier_quote: None, }), @@ -2582,6 +2589,7 @@ mod tests { columns: Some(vec!["id".to_string(), "name".to_string()]), postgres_overriding_system_value: true, sqlserver_identity_insert: false, + dameng_identity_insert: false, normalize_new_target_name: false, identifier_quote: None, }), @@ -2596,11 +2604,40 @@ mod tests { columns: Some(vec!["id".to_string(), "name".to_string()]), postgres_overriding_system_value: false, sqlserver_identity_insert: true, + dameng_identity_insert: false, normalize_new_target_name: false, identifier_quote: None, }), "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;" ); + { + let dameng = build_copy_table_data_sql(CopyTableDataSqlOptions { + database_type: Some(DatabaseType::Dameng), + schema: Some("DCSS".to_string()), + source_name: "users".to_string(), + target_name: "users_copy".to_string(), + columns: Some(vec!["id".to_string(), "name".to_string()]), + postgres_overriding_system_value: false, + sqlserver_identity_insert: false, + dameng_identity_insert: true, + normalize_new_target_name: false, + identifier_quote: None, + }); + assert!(dameng.starts_with("SET IDENTITY_INSERT "), "dameng copy should enable identity insert: {dameng}"); + assert!(dameng.ends_with("OFF;"), "dameng copy should disable identity insert: {dameng}"); + assert!( + dameng.contains("INSERT INTO ") && dameng.contains(" SELECT "), + "dameng copy should carry an INSERT..SELECT: {dameng}" + ); + // Dameng rejects assigning an identity column unless a column list is specified. + assert!( + dameng.contains("(\"id\", \"name\")") + || dameng.contains("(`id`, `name`)") + || dameng.contains("(id, name)") + || dameng.contains("([id], [name])"), + "dameng copy must use an explicit column list: {dameng}" + ); + } assert_eq!( build_copy_table_data_sql(CopyTableDataSqlOptions { database_type: Some(DatabaseType::Dameng), @@ -2610,6 +2647,7 @@ mod tests { columns: None, postgres_overriding_system_value: false, sqlserver_identity_insert: false, + dameng_identity_insert: false, normalize_new_target_name: true, identifier_quote: None, }), @@ -2624,6 +2662,7 @@ mod tests { columns: None, postgres_overriding_system_value: false, sqlserver_identity_insert: false, + dameng_identity_insert: false, normalize_new_target_name: false, identifier_quote: None, }), @@ -2638,6 +2677,7 @@ mod tests { columns: Some(vec!["user_id".to_string(), "userName".to_string(), "order total".to_string()]), postgres_overriding_system_value: false, sqlserver_identity_insert: false, + dameng_identity_insert: false, normalize_new_target_name: true, identifier_quote: None, }), @@ -2652,6 +2692,7 @@ mod tests { columns: Some(vec!["user_id".to_string()]), postgres_overriding_system_value: false, sqlserver_identity_insert: false, + dameng_identity_insert: false, normalize_new_target_name: false, identifier_quote: None, }), @@ -2711,6 +2752,7 @@ mod tests { columns: Some(vec!["user_id".to_string(), "userName".to_string()]), postgres_overriding_system_value: false, sqlserver_identity_insert: false, + dameng_identity_insert: false, normalize_new_target_name: true, identifier_quote: None, }); diff --git a/packages/app-tests/tableClipboard.test.ts b/packages/app-tests/tableClipboard.test.ts index 6e0b5165e..e6304729b 100644 --- a/packages/app-tests/tableClipboard.test.ts +++ b/packages/app-tests/tableClipboard.test.ts @@ -66,16 +66,25 @@ test("table data copy uses only writable columns for first-class databases", () columns: ["id", "name"], postgresOverridingSystemValue: false, sqlserverIdentityInsert: true, + damengIdentityInsert: false, + }); + assert.deepEqual(tableDataCopyColumnOptions("dameng", columns), { + columns: ["id", "name"], + postgresOverridingSystemValue: false, + sqlserverIdentityInsert: false, + damengIdentityInsert: true, }); assert.deepEqual(tableDataCopyColumnOptions("postgres", [{ ...columns[0], extra: "generated always as identity" }, { ...columns[1] }, { ...columns[2], extra: "generated always as (name) stored" }]), { columns: ["id", "name"], postgresOverridingSystemValue: true, sqlserverIdentityInsert: false, + damengIdentityInsert: false, }); assert.deepEqual(tableDataCopyColumnOptions("mysql", [{ ...columns[0], extra: "auto_increment" }, { ...columns[1] }, { ...columns[2], extra: "STORED GENERATED" }, column("expr_default", "DEFAULT_GENERATED"), column("expr_default_on_update", "DEFAULT_GENERATED on update CURRENT_TIMESTAMP"), column("virtual_full_name", "VIRTUAL GENERATED"), column("stored_full_name", "GENERATED ALWAYS AS (concat(first_name, ' ', last_name)) STORED")]), { columns: ["id", "name", "expr_default", "expr_default_on_update"], postgresOverridingSystemValue: false, sqlserverIdentityInsert: false, + damengIdentityInsert: false, }); }); @@ -94,15 +103,18 @@ test("table data copy skips only SQL Server rowversion types", () => { columns: ["id", "name", "fixed_binary", "variable_binary"], postgresOverridingSystemValue: false, sqlserverIdentityInsert: true, + damengIdentityInsert: false, }); assert.deepEqual(tableDataCopyColumnOptions("postgres", [column("updated_at", "timestamp")]), { columns: ["updated_at"], postgresOverridingSystemValue: false, sqlserverIdentityInsert: false, + damengIdentityInsert: false, }); assert.deepEqual(tableDataCopyColumnOptions("mysql", [column("updated_at", "timestamp")]), { columns: ["updated_at"], postgresOverridingSystemValue: false, sqlserverIdentityInsert: false, + damengIdentityInsert: false, }); });