diff --git a/apps/desktop/src/components/structure/TableStructureEditor.primaryKey.spec.ts b/apps/desktop/src/components/structure/TableStructureEditor.primaryKey.spec.ts index 44ed19fa9e..aa957db68f 100644 --- a/apps/desktop/src/components/structure/TableStructureEditor.primaryKey.spec.ts +++ b/apps/desktop/src/components/structure/TableStructureEditor.primaryKey.spec.ts @@ -226,7 +226,8 @@ import TableStructureEditor from "@/components/structure/TableStructureEditor.vu const mountedApps: App[] = []; -function draft(isPrimaryKey = false) { +function draft(isPrimaryKey = false, identity?: { seed: number; increment: number }) { + const isNullable = identity ? false : !isPrimaryKey; return { initialized: true, activeTab: "columns" as const, @@ -238,18 +239,18 @@ function draft(isPrimaryKey = false) { id: "existing:id", name: "id", dataType: "INT", - isNullable: !isPrimaryKey, + isNullable, defaultValue: "", comment: "", isPrimaryKey, - extra: {}, + extra: identity ? { autoIncrement: true, identity: { ...identity } } : {}, original: { name: "id", data_type: "INT", - is_nullable: !isPrimaryKey, + is_nullable: isNullable, column_default: null, is_primary_key: isPrimaryKey, - extra: null, + extra: identity ? `IDENTITY(${identity.seed}, ${identity.increment})` : null, comment: null, }, originalPosition: 0, @@ -262,7 +263,7 @@ function draft(isPrimaryKey = false) { }; } -async function mountEditor(databaseType: "sqlserver" | "postgres" | "sqlite" | "oracle" | "dameng" | "duckdb" | "informix", isPrimaryKey = false, options: { database?: string; dynamicTypes?: string[] } = {}) { +async function mountEditor(databaseType: "sqlserver" | "postgres" | "sqlite" | "oracle" | "dameng" | "duckdb" | "informix", isPrimaryKey = false, options: { database?: string; dynamicTypes?: string[]; identity?: { seed: number; increment: number } } = {}) { mocks.connection.db_type = databaseType; mocks.connection.name = databaseType; mocks.connection.driver_label = databaseType; @@ -277,7 +278,7 @@ async function mountEditor(databaseType: "sqlserver" | "postgres" | "sqlite" | " database: options.database ?? "test", schema: "SYSDBA", tableName: "users", - draft: draft(isPrimaryKey), + draft: draft(isPrimaryKey, options.identity), }); mountedApps.push(app); app.mount(root); @@ -324,6 +325,12 @@ function columnCheckbox(root: HTMLElement, header: string): HTMLInputElement { return checkbox; } +function columnPropertyCheckbox(root: HTMLElement, title: string): HTMLInputElement { + const checkbox = root.querySelector(`[data-column-row-index="0"] label[title="${title}"] input[type="checkbox"]`); + if (!checkbox) throw new Error(`Missing ${title} checkbox`); + return checkbox; +} + function buttonWithText(root: HTMLElement, text: string): HTMLButtonElement { const button = Array.from(root.querySelectorAll("button")).find((item) => item.textContent?.includes(text)); if (!button) throw new Error(`Missing ${text} button`); @@ -348,6 +355,57 @@ afterEach(() => { }); describe("TableStructureEditor primary key editing", () => { + it("allows enabling identity on an existing Dameng integer column", async () => { + const root = await mountEditor("dameng"); + const identity = columnPropertyCheckbox(root, "structureEditor.identity"); + const nullable = columnCheckbox(root, "structureEditor.nullable"); + + expect(identity.disabled).toBe(false); + expect(nullable.checked).toBe(true); + + identity.checked = true; + identity.dispatchEvent(new Event("change", { bubbles: true })); + await nextTick(); + + expect(identity.checked).toBe(true); + expect(nullable.checked).toBe(false); + expect(Array.from(root.querySelectorAll('[data-column-row-index="0"] input[type="number"]')).every((input) => !input.disabled)).toBe(true); + await vi.waitFor(() => expect(mocks.buildTableStructureChangeSql).toHaveBeenCalled()); + expect(mocks.buildTableStructureChangeSql).toHaveBeenLastCalledWith( + expect.objectContaining({ + columns: [ + expect.objectContaining({ + isNullable: false, + extra: expect.objectContaining({ autoIncrement: true, identity: { seed: 1, increment: 1 } }), + }), + ], + }), + ); + }); + + it("allows disabling existing Dameng identity while keeping its parameters read-only", async () => { + const root = await mountEditor("dameng", false, { identity: { seed: 10, increment: 2 } }); + const identity = columnPropertyCheckbox(root, "structureEditor.identity"); + const identityInputs = Array.from(root.querySelectorAll('[data-column-row-index="0"] input[type="number"]')); + + expect(identity.disabled).toBe(false); + expect(identity.checked).toBe(true); + expect(identityInputs).toHaveLength(2); + expect(identityInputs.every((input) => input.disabled)).toBe(true); + + identity.checked = false; + identity.dispatchEvent(new Event("change", { bubbles: true })); + await nextTick(); + + expect(identity.checked).toBe(false); + await vi.waitFor(() => expect(mocks.buildTableStructureChangeSql).toHaveBeenCalled()); + expect(mocks.buildTableStructureChangeSql).toHaveBeenLastCalledWith( + expect.objectContaining({ + columns: [expect.objectContaining({ extra: expect.objectContaining({ autoIncrement: false, identity: undefined }) })], + }), + ); + }); + it("enables the primary-key checkbox for an existing Dameng column and makes it not null", async () => { const root = await mountEditor("dameng"); const primaryKey = columnCheckbox(root, "structureEditor.primaryKey"); diff --git a/apps/desktop/src/components/structure/TableStructureEditor.vue b/apps/desktop/src/components/structure/TableStructureEditor.vue index dee2885e35..cdf4bcaf28 100644 --- a/apps/desktop/src/components/structure/TableStructureEditor.vue +++ b/apps/desktop/src/components/structure/TableStructureEditor.vue @@ -1993,12 +1993,20 @@ function isDamengIdentityChecked(column: EditableStructureColumn): boolean { return !!column.extra.autoIncrement || !!column.extra.identity; } +function originalHasDamengIdentity(column: EditableStructureColumn): boolean { + return column.original?.extra?.toLowerCase().includes("identity") ?? false; +} + function canEditDamengIdentity(column: EditableStructureColumn): boolean { - if (column.original || column.markedForDrop || !isDamengIdentityCompatibleDataType(column.dataType)) return false; + if (column.markedForDrop || !isDamengIdentityCompatibleDataType(column.dataType)) return false; // DM8 permits only one identity column per table, so prevent creating an invalid draft in the editor. return isDamengIdentityChecked(column) || !columns.value.some((candidate) => candidate !== column && !candidate.markedForDrop && isDamengIdentityChecked(candidate)); } +function canEditDamengIdentityParameters(column: EditableStructureColumn): boolean { + return canEditDamengIdentity(column) && !originalHasDamengIdentity(column); +} + function clearDamengIdentity(column: EditableStructureColumn) { column.extra.autoIncrement = false; column.extra.identity = undefined; @@ -2012,10 +2020,11 @@ function syncDamengIdentityForDataType(column: EditableStructureColumn) { } function ensureDamengIdentity(column: EditableStructureColumn) { + const originalIdentity = parseExtraToColumnExtra(column.original?.extra, "dameng").identity; column.extra.autoIncrement = true; column.extra.identity = { - seed: column.extra.identity?.seed ?? 1, - increment: column.extra.identity?.increment ?? 1, + seed: column.extra.identity?.seed ?? originalIdentity?.seed ?? 1, + increment: column.extra.identity?.increment ?? originalIdentity?.increment ?? 1, }; } @@ -2030,13 +2039,13 @@ function setDamengIdentity(column: EditableStructureColumn, checked: boolean) { } function updateDamengIdentitySeed(column: EditableStructureColumn, value: string | number) { - if (!canEditDamengIdentity(column)) return; + if (!canEditDamengIdentityParameters(column)) return; ensureDamengIdentity(column); column.extra.identity!.seed = parseOptionalNumberInput(value); } function updateDamengIdentityIncrement(column: EditableStructureColumn, value: string | number) { - if (!canEditDamengIdentity(column)) return; + if (!canEditDamengIdentityParameters(column)) return; ensureDamengIdentity(column); column.extra.identity!.increment = parseOptionalNumberInput(value); } @@ -3539,7 +3548,7 @@ watch([activeTab, ddlLoading], ([tab, loading]) => { type="number" :class="[structureControlClass, 'w-14']" :placeholder="t('structureEditor.identitySeed')" - :disabled="!canEditDamengIdentity(column)" + :disabled="!canEditDamengIdentityParameters(column)" @update:model-value="(v) => updateDamengIdentitySeed(column, v)" /> { type="number" :class="[structureControlClass, 'w-14']" :placeholder="t('structureEditor.identityIncrement')" - :disabled="!canEditDamengIdentity(column)" + :disabled="!canEditDamengIdentityParameters(column)" @update:model-value="(v) => updateDamengIdentityIncrement(column, v)" /> diff --git a/crates/dbx-core/src/table_structure_sql/column_alter.rs b/crates/dbx-core/src/table_structure_sql/column_alter.rs index 6893ccb5cf..e400210c12 100644 --- a/crates/dbx-core/src/table_structure_sql/column_alter.rs +++ b/crates/dbx-core/src/table_structure_sql/column_alter.rs @@ -1,6 +1,7 @@ use super::column_format::{ - clickhouse_column_type, column_data_type, column_definition, is_mysql_character_data_type, - original_is_mysql_generated_column, original_mysql_generated_clause, + clickhouse_column_type, column_data_type, column_definition, has_dameng_identity, + is_dameng_identity_compatible_type, is_mysql_character_data_type, original_is_mysql_generated_column, + original_mysql_generated_clause, }; use super::columns::build_drop_column_sql; use super::comments::build_sqlserver_column_comment_sql; @@ -100,7 +101,7 @@ pub fn build_single_column_alter_sql(options: SingleColumnAlterSqlOptions) -> Ta StructureDialect::Mysql => statements.extend(build_mysql_existing_column_sql(&table, &options.column, "")), StructureDialect::Doris => statements.extend(build_doris_existing_column_sql(&table, &options.column, "")), StructureDialect::Postgres => statements.extend(build_postgres_existing_column_sql(&table, &options.column)), - StructureDialect::Oracle | StructureDialect::Dameng => { + StructureDialect::Oracle => { if options.database_type == Some(crate::models::connection::DatabaseType::Iris) { statements.extend(build_iris_existing_column_sql(&table, &options.column)); } else if options.database_type == Some(crate::models::connection::DatabaseType::Xugu) { @@ -109,6 +110,9 @@ pub fn build_single_column_alter_sql(options: SingleColumnAlterSqlOptions) -> Ta statements.extend(build_oracle_like_existing_column_sql(dialect, &table, &options.column)) } } + StructureDialect::Dameng => { + statements.extend(build_dameng_existing_column_sql(&table, &options.column, true, &mut warnings)) + } StructureDialect::Oscar => statements.extend(build_oscar_existing_column_sql(dialect, &table, &options.column)), StructureDialect::H2 => statements.extend(build_h2_existing_column_sql(&table, &options.column)), StructureDialect::ClickHouse => { @@ -221,6 +225,78 @@ fn identity_matches_original(identity: &super::types::ColumnIdentity, original_e sqlserver_identity_matches_original(identity, original_extra) } +#[derive(Clone, Copy)] +enum DamengIdentityTransition { + None, + Add { seed: i64, increment: i64 }, + Drop, + ParametersChanged, +} + +fn dameng_identity_transition(column: &EditableStructureColumn) -> DamengIdentityTransition { + let Some(original) = &column.original else { + return DamengIdentityTransition::None; + }; + let original_extra = original.extra.as_deref().unwrap_or(""); + let original_identity = original_has_identity(original_extra); + let current_identity = has_dameng_identity(column); + match (original_identity, current_identity) { + (false, false) => DamengIdentityTransition::None, + (false, true) => { + let identity = column.extra.as_ref().and_then(|extra| extra.identity.as_ref()); + DamengIdentityTransition::Add { + seed: identity.and_then(|identity| identity.seed).unwrap_or(1), + increment: identity.and_then(|identity| identity.increment).unwrap_or(1), + } + } + (true, false) => DamengIdentityTransition::Drop, + (true, true) => match column.extra.as_ref().and_then(|extra| extra.identity.as_ref()) { + Some(identity) if !identity_matches_original(identity, original_extra) => { + DamengIdentityTransition::ParametersChanged + } + _ => DamengIdentityTransition::None, + }, + } +} + +pub(super) fn dameng_drops_identity(column: &EditableStructureColumn) -> bool { + matches!(dameng_identity_transition(column), DamengIdentityTransition::Drop) +} + +pub(super) fn validate_dameng_existing_identity_change( + column: &EditableStructureColumn, + warnings: &mut Vec, +) -> bool { + match dameng_identity_transition(column) { + DamengIdentityTransition::Add { increment, .. } => { + if !is_dameng_identity_compatible_type(&column.data_type) { + warnings.push(format!( + "Dameng identity column \"{}\" must use tinyint, smallint, int, integer, bigint, number, numeric, or decimal/dec with scale 0.", + column.name + )); + } else if column.is_nullable { + warnings.push(format!( + "Dameng identity column \"{}\" must be NOT NULL before identity can be enabled.", + column.name + )); + } else if increment == 0 { + warnings.push(format!("Dameng identity column \"{}\" increment cannot be 0.", column.name)); + } else { + return true; + } + false + } + DamengIdentityTransition::ParametersChanged => { + warnings.push(format!( + "Changing Dameng IDENTITY seed or increment for existing column \"{}\" is not supported from this editor.", + column.name + )); + false + } + DamengIdentityTransition::None | DamengIdentityTransition::Drop => true, + } +} + pub(super) fn has_column_extra_change(column: &EditableStructureColumn) -> bool { let Some(original) = &column.original else { return false }; let current_extra = column.extra.as_ref(); @@ -502,6 +578,32 @@ pub(super) fn build_oracle_like_existing_column_sql( statements } +pub(super) fn build_dameng_existing_column_sql( + table: &str, + column: &EditableStructureColumn, + emit_identity_drop: bool, + warnings: &mut Vec, +) -> Vec { + let transition = dameng_identity_transition(column); + if !validate_dameng_existing_identity_change(column, warnings) { + return Vec::new(); + } + let mut statements = Vec::new(); + if emit_identity_drop && matches!(transition, DamengIdentityTransition::Drop) { + statements.push(format!("ALTER TABLE {table} DROP IDENTITY;")); + } + + statements.extend(build_oracle_like_existing_column_sql(StructureDialect::Dameng, table, column)); + + if let DamengIdentityTransition::Add { seed, increment } = transition { + statements.push(format!( + "ALTER TABLE {table} ADD COLUMN {} IDENTITY({seed}, {increment});", + quote_ident(StructureDialect::Dameng, &column.name) + )); + } + statements +} + pub(super) fn build_iris_existing_column_sql(table: &str, column: &EditableStructureColumn) -> Vec { let Some(original) = &column.original else { return Vec::new(); diff --git a/crates/dbx-core/src/table_structure_sql/columns.rs b/crates/dbx-core/src/table_structure_sql/columns.rs index 8cdde22ad4..43b8a0c991 100644 --- a/crates/dbx-core/src/table_structure_sql/columns.rs +++ b/crates/dbx-core/src/table_structure_sql/columns.rs @@ -1,9 +1,10 @@ use super::column_alter::{ - build_clickhouse_existing_column_sql, build_doris_existing_column_sql, build_h2_existing_column_sql, - build_informix_existing_column_sql, build_iris_existing_column_sql, build_mysql_existing_column_clause, - build_oracle_like_existing_column_sql, build_oscar_existing_column_sql, build_postgres_existing_column_sql, - build_questdb_existing_column_sql, build_sqlite_existing_column_sql, build_sqlserver_existing_column_sql, - build_xugu_existing_column_sql, has_column_extra_change, has_existing_column_attribute_change, + build_clickhouse_existing_column_sql, build_dameng_existing_column_sql, build_doris_existing_column_sql, + build_h2_existing_column_sql, build_informix_existing_column_sql, build_iris_existing_column_sql, + build_mysql_existing_column_clause, build_oracle_like_existing_column_sql, build_oscar_existing_column_sql, + build_postgres_existing_column_sql, build_questdb_existing_column_sql, build_sqlite_existing_column_sql, + build_sqlserver_existing_column_sql, build_xugu_existing_column_sql, dameng_drops_identity, + has_column_extra_change, has_existing_column_attribute_change, validate_dameng_existing_identity_change, }; use super::column_format::{ column_definition, has_dameng_identity, is_dameng_identity_compatible_type, is_mysql_character_data_type, @@ -25,6 +26,32 @@ pub(super) fn build_column_sql(options: &TableStructureSqlOptions, warnings: &mu let table = qualified_table(dialect, options.schema.as_deref(), &options.table_name); let database_label = database_label(options.database_type); let active_columns: Vec<_> = options.columns.iter().filter(|column| !column.marked_for_drop).collect(); + if dialect == StructureDialect::Dameng { + let identity_columns: Vec<_> = active_columns.iter().filter(|column| has_dameng_identity(column)).collect(); + if identity_columns.len() > 1 + || identity_columns.iter().any(|column| { + column.extra.as_ref().and_then(|extra| extra.identity.as_ref()).and_then(|identity| identity.increment) + == Some(0) + }) + { + return Vec::new(); + } + let mut valid_identity_changes = true; + for column in &active_columns { + if has_dameng_identity(column) && !is_dameng_identity_compatible_type(&column.data_type) { + warnings.push(format!( + "Dameng identity column \"{}\" must use tinyint, smallint, int, integer, bigint, number, numeric, or decimal/dec with scale 0.", + column.name + )); + valid_identity_changes = false; + } else if column.original.is_some() && !validate_dameng_existing_identity_change(column, warnings) { + valid_identity_changes = false; + } + } + if !valid_identity_changes { + return Vec::new(); + } + } if is_oracle_like(dialect) && active_columns.is_empty() && options.columns.iter().any(|column| column.marked_for_drop) @@ -56,6 +83,13 @@ pub(super) fn build_column_sql(options: &TableStructureSqlOptions, warnings: &mu .filter(|change| options.columns.iter().any(|column| mysql_auto_increment_touches_primary_key(column, change))); let mut mysql_primary_key_column_clauses = Vec::new(); let mut statements = Vec::new(); + // DM8 owns identity at table level, so remove the old identity before any per-column ADD, + // even when the target column appears earlier in the submitted draft. + if dialect == StructureDialect::Dameng + && options.columns.iter().any(|column| !column.marked_for_drop && dameng_drops_identity(column)) + { + statements.push(format!("ALTER TABLE {table} DROP IDENTITY;")); + } for column in &options.columns { if column.marked_for_drop { @@ -109,16 +143,6 @@ pub(super) fn build_column_sql(options: &TableStructureSqlOptions, warnings: &mu )); continue; } - if dialect == StructureDialect::Dameng - && has_dameng_identity(column) - && !is_dameng_identity_compatible_type(&column.data_type) - { - warnings.push(format!( - "Dameng identity column \"{}\" must use tinyint, smallint, int, integer, bigint, number, numeric, or decimal/dec with scale 0.", - column.name - )); - continue; - } if !capabilities.comment && !clean(&column.comment).is_empty() { warnings.push(format!( "Column comments are not supported for {database_label} from this editor; the comment for \"{}\" was ignored.", @@ -215,7 +239,7 @@ pub(super) fn build_column_sql(options: &TableStructureSqlOptions, warnings: &mu } StructureDialect::Doris => statements.extend(build_doris_existing_column_sql(&table, column, "")), StructureDialect::Postgres => statements.extend(build_postgres_existing_column_sql(&table, column)), - StructureDialect::Oracle | StructureDialect::Dameng => { + StructureDialect::Oracle => { if options.database_type == Some(crate::models::connection::DatabaseType::Iris) { statements.extend(build_iris_existing_column_sql(&table, column)); } else if options.database_type == Some(crate::models::connection::DatabaseType::Xugu) { @@ -224,6 +248,9 @@ pub(super) fn build_column_sql(options: &TableStructureSqlOptions, warnings: &mu statements.extend(build_oracle_like_existing_column_sql(dialect, &table, column)) } } + StructureDialect::Dameng => { + statements.extend(build_dameng_existing_column_sql(&table, column, false, warnings)) + } // 神通 MODIFY 语法与 Oracle 有差异(NULL/NOT NULL 须单独一条),用专属实现。 StructureDialect::Oscar => statements.extend(build_oscar_existing_column_sql(dialect, &table, column)), StructureDialect::H2 => statements.extend(build_h2_existing_column_sql(&table, column)), diff --git a/crates/dbx-core/src/table_structure_sql/tests.rs b/crates/dbx-core/src/table_structure_sql/tests.rs index cd266b8979..303182ec26 100644 --- a/crates/dbx-core/src/table_structure_sql/tests.rs +++ b/crates/dbx-core/src/table_structure_sql/tests.rs @@ -2833,6 +2833,212 @@ fn dameng_unchanged_identity_extra_does_not_mark_existing_column_changed() { assert_eq!(result.statements, Vec::::new()); } +#[test] +fn dameng_enables_identity_on_existing_not_null_column() { + let mut id = existing_pk_column("ID", "INT", false, false); + id.extra = Some(ColumnExtra { + auto_increment: Some(true), + identity: Some(ColumnIdentity { generation: None, seed: Some(10), increment: Some(2) }), + ..Default::default() + }); + + let result = build_table_structure_change_sql(structure_change_options( + DatabaseType::Dameng, + Some("SYSDBA"), + "TEST", + vec![id], + )); + + assert_eq!(result.warnings, Vec::::new()); + assert_eq!(result.statements, vec!["ALTER TABLE \"SYSDBA\".\"TEST\" ADD COLUMN \"ID\" IDENTITY(10, 2);"]); +} + +#[test] +fn dameng_makes_existing_column_not_null_before_enabling_identity() { + let mut id = column("ID"); + id.data_type = "INT".to_string(); + id.is_nullable = false; + id.extra = Some(ColumnExtra { + auto_increment: Some(true), + identity: Some(ColumnIdentity { generation: None, seed: Some(1), increment: Some(1) }), + ..Default::default() + }); + id.original = Some(ColumnInfo { + name: "ID".to_string(), + data_type: "INT".to_string(), + is_nullable: true, + column_default: None, + is_primary_key: false, + extra: None, + comment: None, + ..Default::default() + }); + + let result = build_table_structure_change_sql(structure_change_options( + DatabaseType::Dameng, + Some("SYSDBA"), + "TEST", + vec![id], + )); + + assert_eq!(result.warnings, Vec::::new()); + assert_eq!( + result.statements, + vec![ + "ALTER TABLE \"SYSDBA\".\"TEST\" MODIFY (\"ID\" INT NOT NULL);", + "ALTER TABLE \"SYSDBA\".\"TEST\" ADD COLUMN \"ID\" IDENTITY(1, 1);", + ] + ); +} + +#[test] +fn dameng_disables_identity_on_existing_column() { + let mut id = existing_pk_column("ID", "INT", false, false); + id.extra = Some(ColumnExtra::default()); + id.original.as_mut().unwrap().extra = Some("IDENTITY(10, 2)".to_string()); + + let result = build_table_structure_change_sql(structure_change_options( + DatabaseType::Dameng, + Some("SYSDBA"), + "TEST", + vec![id], + )); + + assert_eq!(result.warnings, Vec::::new()); + assert_eq!(result.statements, vec!["ALTER TABLE \"SYSDBA\".\"TEST\" DROP IDENTITY;"]); +} + +#[test] +fn dameng_moves_identity_with_drop_before_add_regardless_of_column_order() { + let mut target = existing_pk_column("TARGET_ID", "BIGINT", false, false); + target.extra = Some(ColumnExtra { + auto_increment: Some(true), + identity: Some(ColumnIdentity { generation: None, seed: Some(100), increment: Some(5) }), + ..Default::default() + }); + + let mut source = existing_pk_column("SOURCE_ID", "INT", false, false); + source.extra = Some(ColumnExtra::default()); + source.original.as_mut().unwrap().extra = Some("IDENTITY(1, 1)".to_string()); + + let result = build_table_structure_change_sql(structure_change_options( + DatabaseType::Dameng, + Some("SYSDBA"), + "TEST", + vec![target, source], + )); + + assert_eq!(result.warnings, Vec::::new()); + assert_eq!( + result.statements, + vec![ + "ALTER TABLE \"SYSDBA\".\"TEST\" DROP IDENTITY;", + "ALTER TABLE \"SYSDBA\".\"TEST\" ADD COLUMN \"TARGET_ID\" IDENTITY(100, 5);", + ] + ); +} + +#[test] +fn dameng_rejects_identity_on_incompatible_existing_column() { + let mut code = column("CODE"); + code.data_type = "VARCHAR(255)".to_string(); + code.is_nullable = false; + code.extra = Some(ColumnExtra { + auto_increment: Some(true), + identity: Some(ColumnIdentity { generation: None, seed: Some(1), increment: Some(1) }), + ..Default::default() + }); + code.original = Some(ColumnInfo { + name: "CODE".to_string(), + data_type: "VARCHAR(255)".to_string(), + is_nullable: false, + column_default: None, + is_primary_key: false, + extra: None, + comment: None, + ..Default::default() + }); + + let result = build_table_structure_change_sql(structure_change_options( + DatabaseType::Dameng, + Some("SYSDBA"), + "TEST", + vec![code], + )); + + assert!(result.statements.is_empty()); + assert_eq!( + result.warnings, + vec!["Dameng identity column \"CODE\" must use tinyint, smallint, int, integer, bigint, number, numeric, or decimal/dec with scale 0."] + ); +} + +#[test] +fn dameng_rejects_zero_increment_when_enabling_existing_identity() { + let mut id = existing_pk_column("ID", "INT", false, false); + id.extra = Some(ColumnExtra { + auto_increment: Some(true), + identity: Some(ColumnIdentity { generation: None, seed: Some(1), increment: Some(0) }), + ..Default::default() + }); + + let result = build_table_structure_change_sql(structure_change_options( + DatabaseType::Dameng, + Some("SYSDBA"), + "TEST", + vec![id], + )); + + assert!(result.statements.is_empty()); + assert_eq!(result.warnings, vec!["Dameng identity column \"ID\" increment cannot be 0."]); +} + +#[test] +fn dameng_rejects_changing_existing_identity_parameters() { + let mut id = existing_pk_column("ID", "INT", false, false); + id.extra = Some(ColumnExtra { + auto_increment: Some(true), + identity: Some(ColumnIdentity { generation: None, seed: Some(10), increment: Some(3) }), + ..Default::default() + }); + id.original.as_mut().unwrap().extra = Some("IDENTITY(10, 2)".to_string()); + + let result = build_table_structure_change_sql(structure_change_options( + DatabaseType::Dameng, + Some("SYSDBA"), + "TEST", + vec![id], + )); + + assert!(result.statements.is_empty()); + assert_eq!( + result.warnings, + vec![ + "Changing Dameng IDENTITY seed or increment for existing column \"ID\" is not supported from this editor." + ] + ); +} + +#[test] +fn oracle_does_not_adopt_dameng_existing_identity_ddl() { + let mut id = existing_pk_column("ID", "NUMBER(10)", false, false); + id.extra = Some(ColumnExtra { + auto_increment: Some(true), + identity: Some(ColumnIdentity { generation: None, seed: Some(1), increment: Some(1) }), + ..Default::default() + }); + + let result = build_table_structure_change_sql(structure_change_options( + DatabaseType::Oracle, + Some("APP"), + "USERS", + vec![id], + )); + + assert_eq!(result.warnings, Vec::::new()); + assert_eq!(result.statements, Vec::::new()); +} + #[test] fn dameng_rejects_adding_second_identity_column() { let mut existing = column("ID"); @@ -2876,6 +3082,7 @@ fn dameng_rejects_adding_second_identity_column() { is_gaussdb_m_mode: false, }); + assert_eq!(result.statements, Vec::::new()); assert_eq!(result.warnings, vec!["Dameng tables can have only one identity column."]); }