diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index b10acaaa07..a1e77a3f1b 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -10640,6 +10640,7 @@ async function copyAlterColumnSql() { const options: BuildSingleColumnAlterSqlOptions = { databaseType: props.databaseType, + driverProfile: props.connectionId ? connectionStore.getConfig(props.connectionId)?.driver_profile : undefined, schema: props.tableMeta?.schema, tableName: props.tableMeta!.tableName, column: draft, diff --git a/apps/desktop/src/components/structure/TableStructureEditor.vue b/apps/desktop/src/components/structure/TableStructureEditor.vue index b8dbc70e79..866f94dcc7 100644 --- a/apps/desktop/src/components/structure/TableStructureEditor.vue +++ b/apps/desktop/src/components/structure/TableStructureEditor.vue @@ -1623,6 +1623,7 @@ function scheduleSqlPreviewRefresh() { function structureChangeOptions(): BuildTableStructureChangeSqlOptions { return { databaseType: databaseType.value, + driverProfile: connection.value?.driver_profile, schema: props.schema, tableName: isCreateMode.value ? newTableName.value.trim() : props.tableName || "", // Do not let a draft created by an older build submit properties that the diff --git a/apps/desktop/src/lib/table/tableStructureEditorSql.ts b/apps/desktop/src/lib/table/tableStructureEditorSql.ts index 18006c7d99..47f24ad210 100644 --- a/apps/desktop/src/lib/table/tableStructureEditorSql.ts +++ b/apps/desktop/src/lib/table/tableStructureEditorSql.ts @@ -77,6 +77,10 @@ export interface EditableStructureTrigger { export interface BuildTableStructureChangeSqlOptions { databaseType?: DatabaseType; + /** Driver profile reported by the connection (e.g. `"gbase8s"`). GBase 8s is + * Informix-compatible rather than MySQL-compatible like the rest of the + * `Gbase` family, so this disambiguates which dialect the backend generates. */ + driverProfile?: string | null; schema?: string; tableName: string; columns: EditableStructureColumn[]; @@ -118,6 +122,7 @@ export interface SqliteTableStructureChangePreview extends TableStructureChangeS export interface BuildSingleColumnAlterSqlOptions { databaseType?: DatabaseType; + driverProfile?: string | null; schema?: string; tableName: string; column: EditableStructureColumn; diff --git a/crates/dbx-core/src/db_admin_sql.rs b/crates/dbx-core/src/db_admin_sql.rs index dee0060b24..57cc453649 100644 --- a/crates/dbx-core/src/db_admin_sql.rs +++ b/crates/dbx-core/src/db_admin_sql.rs @@ -2392,6 +2392,7 @@ mod tests { let create = crate::table_structure_sql::build_create_table_sql(crate::table_structure_sql::TableStructureSqlOptions { database_type: Some(DatabaseType::Oracle), + driver_profile: None, schema: Some("APP".to_string()), table_name: "orders_copy".to_string(), columns: vec![column("user_id"), column("userName")], diff --git a/crates/dbx-core/src/table_structure_sql.rs b/crates/dbx-core/src/table_structure_sql.rs index cb64f2c8b2..6cbcb2f9c0 100644 --- a/crates/dbx-core/src/table_structure_sql.rs +++ b/crates/dbx-core/src/table_structure_sql.rs @@ -82,7 +82,7 @@ pub fn build_table_structure_change_sql(mut options: TableStructureSqlOptions) - /// Callers using this as an introspection signal must corroborate it against /// what was actually collected rather than trust the flag alone. pub(crate) fn supports_comments(database_type: crate::models::connection::DatabaseType) -> bool { - dialect::capabilities_for(Some(database_type)).comment + dialect::capabilities_for(Some(database_type), None).comment } /// Whether this engine's structure editor can generate foreign key DDL — a @@ -94,7 +94,7 @@ pub(crate) fn supports_comments(database_type: crate::models::connection::Databa /// engine where DDL support and introspection support diverge — callers /// should corroborate against what was actually collected. pub(crate) fn supports_foreign_keys(database_type: crate::models::connection::DatabaseType) -> bool { - dialect::capabilities_for(Some(database_type)).foreign_key + dialect::capabilities_for(Some(database_type), None).foreign_key } /// Canonical display label for a database engine (e.g. `postgres`, 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 66fb133101..d1adeb48e8 100644 --- a/crates/dbx-core/src/table_structure_sql/column_alter.rs +++ b/crates/dbx-core/src/table_structure_sql/column_alter.rs @@ -14,7 +14,7 @@ use super::util::{ use crate::table_structure_sql::ColumnExtra; pub fn build_single_column_alter_sql(options: SingleColumnAlterSqlOptions) -> TableStructureSqlResult { - let capabilities = capabilities_for(options.database_type); + let capabilities = capabilities_for(options.database_type, options.driver_profile.as_deref()); let dialect = capabilities.dialect; let table = qualified_table(dialect, options.schema.as_deref(), &options.table_name); let database_label = database_label(options.database_type); diff --git a/crates/dbx-core/src/table_structure_sql/columns.rs b/crates/dbx-core/src/table_structure_sql/columns.rs index 2eeab03c66..8f7c59aec8 100644 --- a/crates/dbx-core/src/table_structure_sql/columns.rs +++ b/crates/dbx-core/src/table_structure_sql/columns.rs @@ -22,7 +22,7 @@ use crate::models::connection::DatabaseType; use std::collections::HashSet; pub(super) fn build_column_sql(options: &TableStructureSqlOptions, warnings: &mut Vec) -> Vec { - let capabilities = capabilities_for(options.database_type); + let capabilities = capabilities_for(options.database_type, options.driver_profile.as_deref()); let dialect = capabilities.dialect; let table = qualified_table(dialect, options.schema.as_deref(), &options.table_name); let database_label = database_label(options.database_type); @@ -383,7 +383,7 @@ pub(super) fn validate_primary_key_change_scope(options: &TableStructureSqlOptio } let Some(change) = primary_key_change(options) else { return Vec::new() }; - let capabilities = capabilities_for(options.database_type); + let capabilities = capabilities_for(options.database_type, options.driver_profile.as_deref()); let supported = if change.old_ids.is_empty() { capabilities.add_primary_key } else { capabilities.alter_primary_key }; if supported { @@ -452,7 +452,7 @@ pub(super) fn build_primary_key_sql( table: &str, warnings: &mut Vec, ) -> Vec { - let capabilities = capabilities_for(options.database_type); + let capabilities = capabilities_for(options.database_type, options.driver_profile.as_deref()); // Membership by draft id (set equality): pure rename / local reorder of the same key // columns is not a PK change. A draft that drops a PK column is also rejected here. diff --git a/crates/dbx-core/src/table_structure_sql/comments.rs b/crates/dbx-core/src/table_structure_sql/comments.rs index 70b70c3146..df20d9b76e 100644 --- a/crates/dbx-core/src/table_structure_sql/comments.rs +++ b/crates/dbx-core/src/table_structure_sql/comments.rs @@ -3,7 +3,7 @@ use super::types::TableStructureSqlOptions; use super::util::{clean, qualified_table, quote_string}; pub(super) fn build_table_comment_sql(options: &TableStructureSqlOptions, warnings: &mut Vec) -> Vec { - let capabilities = capabilities_for(options.database_type); + let capabilities = capabilities_for(options.database_type, options.driver_profile.as_deref()); let new_comment = options.table_comment.as_deref().unwrap_or(""); let original_comment = options.original_table_comment.as_deref().unwrap_or(""); if clean(new_comment) == clean(original_comment) { diff --git a/crates/dbx-core/src/table_structure_sql/create_table.rs b/crates/dbx-core/src/table_structure_sql/create_table.rs index d8931462a5..7e08d12687 100644 --- a/crates/dbx-core/src/table_structure_sql/create_table.rs +++ b/crates/dbx-core/src/table_structure_sql/create_table.rs @@ -22,7 +22,7 @@ pub fn build_create_table_sql(mut options: TableStructureSqlOptions) -> TableStr capabilities = super::dialect::gaussdb_m_capabilities(); dialect = StructureDialect::GaussdbM; } else { - capabilities = capabilities_for(options.database_type); + capabilities = capabilities_for(options.database_type, options.driver_profile.as_deref()); dialect = capabilities.dialect; } options.table_name = clean(&options.table_name); diff --git a/crates/dbx-core/src/table_structure_sql/dialect.rs b/crates/dbx-core/src/table_structure_sql/dialect.rs index 1fe2885115..b4bd058d6b 100644 --- a/crates/dbx-core/src/table_structure_sql/dialect.rs +++ b/crates/dbx-core/src/table_structure_sql/dialect.rs @@ -93,7 +93,10 @@ pub(super) fn gaussdb_m_capabilities() -> TableStructureCapabilities { } } -pub(super) fn capabilities_for(database_type: Option) -> TableStructureCapabilities { +pub(super) fn capabilities_for( + database_type: Option, + driver_profile: Option<&str>, +) -> TableStructureCapabilities { let base = TableStructureCapabilities::default(); match database_type { Some( @@ -129,6 +132,23 @@ pub(super) fn capabilities_for(database_type: Option) -> TableStru comment: true, ..base }, + // GBase 8s is Informix-compatible, unlike the rest of the `Gbase` + // family (GBase 8a and friends) which speak MySQL wire protocol and + // SQL dialect. `driver_profile` is the only signal that distinguishes + // them (see the same check in `table_export.rs` / `table_select.rs`). + Some(DatabaseType::Gbase) if driver_profile.is_some_and(|profile| profile.eq_ignore_ascii_case("gbase8s")) => { + TableStructureCapabilities { + dialect: StructureDialect::Informix, + add_column: true, + drop_column: true, + rename_column: true, + alter_existing_column: true, + create_index: true, + drop_index: true, + rebuild_index: true, + ..base + } + } Some(DatabaseType::Gbase) => TableStructureCapabilities { dialect: StructureDialect::Mysql, add_column: true, diff --git a/crates/dbx-core/src/table_structure_sql/foreign_keys.rs b/crates/dbx-core/src/table_structure_sql/foreign_keys.rs index dde4147d36..26b24a269b 100644 --- a/crates/dbx-core/src/table_structure_sql/foreign_keys.rs +++ b/crates/dbx-core/src/table_structure_sql/foreign_keys.rs @@ -23,7 +23,7 @@ fn build_foreign_key_sql_with_mode( } let database_label = database_label(options.database_type); - let capabilities = super::dialect::capabilities_for(options.database_type); + let capabilities = super::dialect::capabilities_for(options.database_type, options.driver_profile.as_deref()); let dialect = capabilities.dialect; if !capabilities.foreign_key { if has_foreign_key_edits(&options.foreign_keys) { diff --git a/crates/dbx-core/src/table_structure_sql/indexes.rs b/crates/dbx-core/src/table_structure_sql/indexes.rs index 15ff0c3640..3d79f02491 100644 --- a/crates/dbx-core/src/table_structure_sql/indexes.rs +++ b/crates/dbx-core/src/table_structure_sql/indexes.rs @@ -12,7 +12,7 @@ pub(super) fn build_index_sql(options: &TableStructureSqlOptions, warnings: &mut capabilities = caps; dialect = StructureDialect::GaussdbM; } else { - let caps = capabilities_for(options.database_type); + let caps = capabilities_for(options.database_type, options.driver_profile.as_deref()); capabilities = caps; dialect = caps.dialect; } @@ -346,7 +346,7 @@ pub(super) fn build_create_index_statements( concurrently_supported: bool, for_new_table: bool, ) -> Vec { - let capabilities = capabilities_for(database_type_for_dialect(dialect)); + let capabilities = capabilities_for(database_type_for_dialect(dialect), None); let name = clean(&index.name); let columns: Vec = index.columns.iter().map(|column| clean(column)).filter(|column| !column.is_empty()).collect(); diff --git a/crates/dbx-core/src/table_structure_sql/sqlite_rebuild.rs b/crates/dbx-core/src/table_structure_sql/sqlite_rebuild.rs index cdbe077f56..a45a407feb 100644 --- a/crates/dbx-core/src/table_structure_sql/sqlite_rebuild.rs +++ b/crates/dbx-core/src/table_structure_sql/sqlite_rebuild.rs @@ -1484,6 +1484,7 @@ mod tests { ) -> TableStructureSqlOptions { TableStructureSqlOptions { database_type: Some(DatabaseType::Sqlite), + driver_profile: None, schema: None, table_name: table_name.to_string(), columns: vec![EditableStructureColumn { diff --git a/crates/dbx-core/src/table_structure_sql/tests.rs b/crates/dbx-core/src/table_structure_sql/tests.rs index 2e39f287a1..c23f66e7a6 100644 --- a/crates/dbx-core/src/table_structure_sql/tests.rs +++ b/crates/dbx-core/src/table_structure_sql/tests.rs @@ -51,6 +51,7 @@ fn structure_change_options( ) -> TableStructureSqlOptions { TableStructureSqlOptions { database_type: Some(database_type), + driver_profile: None, schema: schema.map(str::to_string), table_name: table_name.to_string(), columns, @@ -160,6 +161,7 @@ fn index_change_options( ) -> TableStructureSqlOptions { TableStructureSqlOptions { database_type: Some(database_type), + driver_profile: None, schema: schema.map(str::to_string), table_name: "USERS".to_string(), columns: Vec::new(), @@ -240,6 +242,7 @@ fn builds_mysql_column_and_index_changes() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![renamed, email], @@ -435,6 +438,7 @@ fn builds_xugu_type_change_with_native_syntax() { let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::Xugu), + driver_profile: None, schema: Some("public".to_string()), table_name: "info_x".to_string(), column: code, @@ -457,6 +461,7 @@ fn builds_xugu_type_change_with_native_syntax() { }); let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Xugu), + driver_profile: None, schema: Some("public".to_string()), table_name: "info_x".to_string(), columns: vec![code], @@ -488,6 +493,7 @@ fn builds_xugu_type_change_with_native_syntax() { }); let postgres_result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::Postgres), + driver_profile: None, schema: Some("public".to_string()), table_name: "info_x".to_string(), column: postgres_code, @@ -513,6 +519,7 @@ fn builds_postgres_explicit_type_cast_for_renamed_column() { let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::Postgres), + driver_profile: None, schema: Some("public".to_string()), table_name: "items".to_string(), column: code, @@ -542,6 +549,7 @@ fn builds_postgres_atomic_type_change_with_existing_default() { let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::Postgres), + driver_profile: None, schema: Some("public".to_string()), table_name: "items".to_string(), column: code, @@ -567,6 +575,7 @@ fn builds_postgres_type_change_that_drops_default() { let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::Postgres), + driver_profile: None, schema: None, table_name: "items".to_string(), column: code, @@ -610,6 +619,7 @@ fn builds_xugu_timezone_temporal_precision_in_final_ddl() { }); let altered = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::Xugu), + driver_profile: None, schema: Some("public".to_string()), table_name: "events".to_string(), column: altered_at, @@ -632,6 +642,7 @@ fn builds_postgres_array_and_domain_type_casts_without_affecting_xugu() { }); let postgres = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::Postgres), + driver_profile: None, schema: Some("catalog".to_string()), table_name: "items".to_string(), column: tags, @@ -651,6 +662,7 @@ fn builds_postgres_array_and_domain_type_casts_without_affecting_xugu() { }); let postgres = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::Postgres), + driver_profile: None, schema: Some("catalog".to_string()), table_name: "items".to_string(), column: status, @@ -668,6 +680,7 @@ fn builds_mysql_unsigned_integer_column_with_length_before_attribute() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![score], @@ -704,6 +717,7 @@ fn doris_table_editor_renames_column_without_mysql_change_syntax() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Doris), + driver_profile: None, schema: Some("qybiprod".to_string()), table_name: "dim_prod_sp_vkorg".to_string(), columns: vec![renamed], @@ -740,6 +754,7 @@ fn doris_single_column_alter_renames_then_modifies_column_definition() { let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::Doris), + driver_profile: None, schema: Some("qybiprod".to_string()), table_name: "dim_prod_sp_vkorg".to_string(), column: renamed, @@ -764,6 +779,7 @@ fn dameng_integer_column_omits_mysql_display_width() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Dameng), + driver_profile: None, schema: Some("SYSDBA".to_string()), table_name: "users".to_string(), columns: vec![age, amount], @@ -807,6 +823,7 @@ fn builds_highgo_foreign_key_changes_with_postgres_syntax() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Highgo), + driver_profile: None, schema: Some("public".to_string()), table_name: "orders".to_string(), columns: Vec::new(), @@ -880,6 +897,7 @@ fn builds_informix_column_and_index_changes() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Informix), + driver_profile: None, schema: Some("gbasedbt".to_string()), table_name: "users".to_string(), columns: vec![renamed, email, old_col], @@ -937,6 +955,7 @@ fn oracle_does_not_generate_drop_sql_for_all_columns() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Oracle), + driver_profile: None, schema: Some("DBX_TEST".to_string()), table_name: "test".to_string(), columns: vec![id, name], @@ -978,6 +997,7 @@ fn oracle_timestamp_default_precedes_nullability_in_modify_sql() { let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::Oracle), + driver_profile: None, schema: Some("DBX_TEST".to_string()), table_name: "test".to_string(), column: col, @@ -999,6 +1019,7 @@ fn oracle_create_table_preserves_character_length_units() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Oracle), + driver_profile: None, schema: Some("DBX_APP".to_string()), table_name: "DBX_ISSUE_4739".to_string(), columns: vec![byte_col, char_col], @@ -1035,6 +1056,7 @@ fn oracle_create_table_uses_unquoted_identifiers_for_new_objects() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Oracle), + driver_profile: None, schema: Some("APP".to_string()), table_name: "orders".to_string(), columns: vec![user_id, user_name, upper_id, dollar_id, hash_id], @@ -1072,6 +1094,7 @@ fn oracle_create_table_leaves_uppercase_regular_identifier_unquoted() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Oracle), + driver_profile: None, schema: None, table_name: "USERS".to_string(), columns: vec![user_id], @@ -1108,6 +1131,7 @@ fn oracle_create_table_quotes_special_and_reserved_identifiers() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Oracle), + driver_profile: None, schema: Some("APP".to_string()), table_name: "order detail".to_string(), columns: vec![special, escaped, select, from, table, leading_digit], @@ -1147,6 +1171,7 @@ fn oracle_create_table_distinguishes_new_and_referenced_foreign_key_identifiers( let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Oracle), + driver_profile: None, schema: Some("APP".to_string()), table_name: "orders".to_string(), columns: vec![user_id], @@ -1189,6 +1214,7 @@ fn oracle_existing_quoted_identifiers_keep_exact_spelling() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Oracle), + driver_profile: None, schema: Some("CaseSchema".to_string()), table_name: "CaseTable".to_string(), columns: vec![column], @@ -1222,6 +1248,7 @@ fn oracle_new_identifier_formatting_does_not_change_other_dialects() { user_id.data_type = "INTEGER".to_string(); let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(database_type), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![user_id], @@ -1258,6 +1285,7 @@ fn oracle_alter_column_preserves_character_length_unit() { let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::Oracle), + driver_profile: None, schema: Some("DBX_APP".to_string()), table_name: "DBX_ISSUE_4739".to_string(), column, @@ -1286,6 +1314,7 @@ fn oracle_timestamp_precision_change_does_not_repeat_unchanged_nullability() { let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::Oracle), + driver_profile: None, schema: Some("DBX_TEST".to_string()), table_name: "test".to_string(), column: col, @@ -1314,6 +1343,7 @@ fn iris_drop_index_includes_table_name() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Iris), + driver_profile: None, schema: Some("SQLUSER".to_string()), table_name: "tb_a".to_string(), columns: Vec::new(), @@ -1356,6 +1386,7 @@ fn iris_ignores_comment_changes_but_keeps_supported_column_alters() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Iris), + driver_profile: None, schema: Some("SQLUSER".to_string()), table_name: "DBX_ISSUE_1678".to_string(), columns: vec![renamed, created_at], @@ -1406,6 +1437,7 @@ fn iris_comment_only_change_returns_warning_without_sql() { let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::Iris), + driver_profile: None, schema: Some("SQLUSER".to_string()), table_name: "DBX_ISSUE_1678".to_string(), column: name, @@ -1438,6 +1470,7 @@ fn oracle_compatible_databases_keep_comment_on_sql() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(database_type), + driver_profile: None, schema: Some("APP".to_string()), table_name: "USERS".to_string(), columns: vec![name], @@ -1473,6 +1506,7 @@ fn mysql_create_index_with_comment() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![col], @@ -1507,6 +1541,7 @@ fn manticoresearch_builds_create_table_sql_only() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::ManticoreSearch), + driver_profile: None, schema: None, table_name: "materials".to_string(), columns: vec![title, views], @@ -1551,6 +1586,7 @@ fn manticoresearch_builds_add_and_drop_column_sql() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::ManticoreSearch), + driver_profile: None, schema: None, table_name: "materials".to_string(), columns: vec![old_code, name, resource], @@ -1619,6 +1655,7 @@ fn gbase8a_uses_limited_mysql_ddl() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Gbase), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![renamed, new_col, old_col], @@ -1690,6 +1727,7 @@ fn gbase8a_allows_mysql_style_column_reorder() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Gbase), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![id, email, name], @@ -1708,6 +1746,84 @@ fn gbase8a_allows_mysql_style_column_reorder() { assert_eq!(result.statements, vec!["ALTER TABLE `users` MODIFY COLUMN `name` varchar(255) AFTER `email`;"]); } +// Regression test for issue #8003: GBase 8s is Informix-compatible, not +// MySQL-compatible like the rest of the `Gbase` family (GBase 8a), and was +// generating backtick-quoted MySQL DDL that real GBase 8s servers reject +// outright with a syntax error. Verified against a live GBase 8s 8.8 instance: +// the MySQL-style output below (backticks, bare `datetime`) fails to execute, +// while unquoted identifiers plus an Informix-qualified `datetime year to +// second` type succeed. +#[test] +fn gbase8s_uses_informix_ddl_not_mysql() { + let result = build_create_table_sql(TableStructureSqlOptions { + database_type: Some(DatabaseType::Gbase), + driver_profile: Some("gbase8s".to_string()), + schema: Some("gbasedbt".to_string()), + table_name: "issue8003_repro".to_string(), + columns: vec![ + { + let mut id = column("id"); + id.data_type = "int".to_string(); + id.is_primary_key = true; + id + }, + { + let mut created_at = column("created_at"); + created_at.data_type = "datetime year to second".to_string(); + created_at.is_nullable = true; + created_at + }, + ], + indexes: Vec::new(), + foreign_keys: Vec::new(), + triggers: Vec::new(), + table_comment: None, + original_table_comment: None, + mysql_engine: None, + partitioned: false, + is_gaussdb_m_mode: false, + table_collation: None, + }); + + assert_eq!(result.warnings, Vec::::new()); + assert_eq!( + result.statements, + vec!["CREATE TABLE gbasedbt.issue8003_repro (\n id int,\n created_at datetime year to second,\n PRIMARY KEY (id)\n);"] + ); + // The MySQL-family driver_profile-less case must be unaffected. + for statement in &result.statements { + assert!(!statement.contains('`'), "GBase 8s DDL must not use MySQL backtick quoting: {statement}"); + } +} + +#[test] +fn gbase_without_driver_profile_still_uses_mysql_ddl() { + let result = build_create_table_sql(TableStructureSqlOptions { + database_type: Some(DatabaseType::Gbase), + driver_profile: None, + schema: None, + table_name: "issue8003_repro".to_string(), + columns: vec![{ + let mut id = column("id"); + id.data_type = "int".to_string(); + id.is_primary_key = true; + id + }], + indexes: Vec::new(), + foreign_keys: Vec::new(), + triggers: Vec::new(), + table_comment: None, + original_table_comment: None, + mysql_engine: None, + partitioned: false, + is_gaussdb_m_mode: false, + table_collation: None, + }); + + assert_eq!(result.warnings, Vec::::new()); + assert_eq!(result.statements, vec!["CREATE TABLE `issue8003_repro` (\n `id` int,\n PRIMARY KEY (`id`)\n);"]); +} + #[test] fn manticoresearch_does_not_drop_id_column() { let mut id = column("id"); @@ -1726,6 +1842,7 @@ fn manticoresearch_does_not_drop_id_column() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::ManticoreSearch), + driver_profile: None, schema: None, table_name: "materials".to_string(), columns: vec![id], @@ -1795,6 +1912,7 @@ fn manticoresearch_warns_when_existing_column_properties_change() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::ManticoreSearch), + driver_profile: None, schema: None, table_name: "materials".to_string(), columns: vec![name, resource, old_resource], @@ -1831,6 +1949,7 @@ fn manticoresearch_ignores_mysql_column_options() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::ManticoreSearch), + driver_profile: None, schema: None, table_name: "materials".to_string(), columns: vec![title], @@ -1870,6 +1989,7 @@ fn manticoresearch_builds_text_column_properties() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::ManticoreSearch), + driver_profile: None, schema: None, table_name: "materials".to_string(), columns: vec![title, sku, name], @@ -1901,6 +2021,7 @@ fn manticoresearch_builds_json_secondary_index_property() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::ManticoreSearch), + driver_profile: None, schema: None, table_name: "materials".to_string(), columns: vec![metadata], @@ -1928,6 +2049,7 @@ fn mysql_create_unique_index_with_comment_and_btree() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: Vec::new(), @@ -1957,6 +2079,7 @@ fn mysql_create_functional_index_preserves_key_part_syntax() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "test".to_string(), columns: Vec::new(), @@ -1986,6 +2109,7 @@ fn mysql_add_timestamp_column_drops_invalid_precision() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![created_at], @@ -2015,6 +2139,7 @@ fn mysql_add_timestamp_column_preserves_valid_precision() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![created_at], @@ -2051,6 +2176,7 @@ fn builds_postgres_create_table_with_comments_and_index() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Postgres), + driver_profile: None, schema: Some("public".to_string()), table_name: "users".to_string(), columns: vec![id, name], @@ -2084,6 +2210,7 @@ fn quotes_expression_like_new_index_columns_without_provenance() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Kingbase), + driver_profile: None, schema: Some("public".to_string()), table_name: "tankong_data".to_string(), columns: vec![column(expression_like_column)], @@ -2157,6 +2284,7 @@ fn create_table_trims_table_name_whitespace_for_all_statements() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: " users ".to_string(), columns: vec![id], @@ -2195,6 +2323,7 @@ fn warns_for_sqlite_unsafe_column_changes() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Sqlite), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![col], @@ -2238,6 +2367,7 @@ fn qualifies_attached_sqlite_table_and_index_changes() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Sqlite), + driver_profile: None, schema: Some("analytics".to_string()), table_name: "users".to_string(), columns: vec![email], @@ -2308,6 +2438,7 @@ fn builds_rqlite_changes_with_sqlite_dialect() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Rqlite), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![email], @@ -2339,6 +2470,7 @@ fn builds_kingbase_add_column_without_column_keyword() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Kingbase), + driver_profile: None, schema: Some("dbo".to_string()), table_name: "dw_bill_info_copy".to_string(), columns: vec![flag], @@ -2405,6 +2537,7 @@ fn builds_mysql_column_reorder_statements() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![id, email, name], @@ -2462,6 +2595,7 @@ fn mysql_add_column_before_existing_column_does_not_reorder_shifted_column() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "infra_api_error_log".to_string(), columns: vec![deleted, new_column, tenant_id], @@ -2526,6 +2660,7 @@ fn mysql_existing_column_reorder_does_not_reorder_columns_shifted_by_prior_move( let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![id, email, name], @@ -2602,6 +2737,7 @@ fn mysql_moving_first_column_to_end_uses_single_reorder_statement() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![col_1, col_2, col_3, col_0], @@ -2628,6 +2764,7 @@ fn builds_sql_server_quoted_column_and_index_statements() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::SqlServer), + driver_profile: None, schema: Some("dbo".to_string()), table_name: "users".to_string(), columns: vec![email], @@ -2660,6 +2797,7 @@ fn sqlserver_strips_mysql_display_width_from_fixed_integer_types() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::SqlServer), + driver_profile: None, schema: Some("dbo".to_string()), table_name: "users".to_string(), columns: vec![id], @@ -2686,6 +2824,7 @@ fn sqlserver_strips_scale_from_float() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::SqlServer), + driver_profile: None, schema: Some("dbo".to_string()), table_name: "orders".to_string(), columns: vec![amount], @@ -2712,6 +2851,7 @@ fn sqlserver_preserves_float_mantissa_bits() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::SqlServer), + driver_profile: None, schema: Some("dbo".to_string()), table_name: "measurements".to_string(), columns: vec![value], @@ -2763,6 +2903,7 @@ fn sqlserver_default_changes_drop_old_constraints_with_isolated_batches() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::SqlServer), + driver_profile: None, schema: Some("core".to_string()), table_name: "products".to_string(), columns: vec![sku, active], @@ -2824,6 +2965,7 @@ fn sqlserver_type_change_preserves_existing_default_constraint() { let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::SqlServer), + driver_profile: None, schema: Some("dbo".to_string()), table_name: "issue_3714".to_string(), column: check_value, @@ -2859,6 +3001,7 @@ fn sqlserver_type_and_default_change_drops_before_alter_and_adds_new_default() { let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::SqlServer), + driver_profile: None, schema: Some("dbo".to_string()), table_name: "inventory".to_string(), column: quantity, @@ -2889,6 +3032,7 @@ fn sqlserver_generated_default_constraint_escapes_identifiers_and_unicode_values let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::SqlServer), + driver_profile: None, schema: Some("dbo".to_string()), table_name: "order]s".to_string(), column: owner, @@ -2919,6 +3063,7 @@ fn sqlserver_rename_and_nullability_change_restores_default_on_new_column_name() let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::SqlServer), + driver_profile: None, schema: Some("dbo".to_string()), table_name: "settings".to_string(), column: renamed, @@ -2947,6 +3092,7 @@ fn sqlserver_type_change_without_default_keeps_direct_alter_behavior() { let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::SqlServer), + driver_profile: None, schema: Some("dbo".to_string()), table_name: "metrics".to_string(), column: value, @@ -2976,6 +3122,7 @@ fn sqlserver_unchanged_foreign_key_does_not_warn_when_saving_other_changes() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::SqlServer), + driver_profile: None, schema: Some("dbo".to_string()), table_name: "orders".to_string(), columns: vec![email], @@ -3007,6 +3154,7 @@ fn sqlserver_add_column_with_identity() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::SqlServer), + driver_profile: None, schema: Some("dbo".to_string()), table_name: "orders".to_string(), columns: vec![id], @@ -3038,6 +3186,7 @@ fn dameng_add_column_with_identity() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Dameng), + driver_profile: None, schema: Some("SYSDBA".to_string()), table_name: "TEST".to_string(), columns: vec![id], @@ -3063,6 +3212,7 @@ fn dameng_uppercases_lowercase_column_type() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Dameng), + driver_profile: None, schema: Some("SYSDBA".to_string()), table_name: "TEST".to_string(), columns: vec![status], @@ -3095,6 +3245,7 @@ fn dameng_rejects_identity_on_incompatible_type() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Dameng), + driver_profile: None, schema: Some("SYSDBA".to_string()), table_name: "TEST".to_string(), columns: vec![column], @@ -3129,6 +3280,7 @@ fn sqlserver_rejects_identity_on_incompatible_type() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::SqlServer), + driver_profile: None, schema: Some("core".to_string()), table_name: "products".to_string(), columns: vec![column], @@ -3166,6 +3318,7 @@ fn sqlserver_changed_foreign_key_still_warns_as_unsupported() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::SqlServer), + driver_profile: None, schema: Some("dbo".to_string()), table_name: "orders".to_string(), columns: Vec::new(), @@ -3208,6 +3361,7 @@ fn sqlserver_unchanged_identity_extra_does_not_mark_existing_column_changed() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::SqlServer), + driver_profile: None, schema: Some("dbo".to_string()), table_name: "orders".to_string(), columns: vec![id], @@ -3250,6 +3404,7 @@ fn dameng_unchanged_identity_extra_does_not_mark_existing_column_changed() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Dameng), + driver_profile: None, schema: Some("SYSDBA".to_string()), table_name: "TEST".to_string(), columns: vec![id], @@ -3505,6 +3660,7 @@ fn dameng_rejects_adding_second_identity_column() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Dameng), + driver_profile: None, schema: Some("SYSDBA".to_string()), table_name: "TEST".to_string(), columns: vec![existing, added], @@ -3559,6 +3715,7 @@ fn sqlserver_existing_column_identity_change_warns_without_unchanged_foreign_key let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::SqlServer), + driver_profile: None, schema: Some("dbo".to_string()), table_name: "orders".to_string(), columns: vec![id], @@ -3592,6 +3749,7 @@ fn builds_duckdb_create_table_statements() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::DuckDb), + driver_profile: None, schema: None, table_name: "events".to_string(), columns: vec![name, created_at], @@ -3639,6 +3797,7 @@ fn builds_clickhouse_nullable_comment_and_reorder_statements() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::ClickHouse), + driver_profile: None, schema: None, table_name: "events".to_string(), columns: vec![source, status], @@ -3687,6 +3846,7 @@ fn builds_h2_schema_qualified_existing_column_statements() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::H2), + driver_profile: None, schema: Some("PUBLIC".to_string()), table_name: "USERS".to_string(), columns: vec![name], @@ -4628,6 +4788,7 @@ fn mysql_create_table_with_auto_increment() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![col], @@ -4657,6 +4818,7 @@ fn mysql_create_table_keeps_column_charset_collation_and_comment() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![name], @@ -4690,6 +4852,7 @@ fn mysql_compatible_databases_do_not_emit_mysql_column_charset_clauses() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(database_type), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![name], @@ -4720,6 +4883,7 @@ fn mysql_create_table_with_on_update_current_timestamp() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![col], @@ -4750,6 +4914,7 @@ fn postgres_create_table_with_identity() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Postgres), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![col], @@ -4782,6 +4947,7 @@ fn dameng_create_table_with_identity() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Dameng), + driver_profile: None, schema: Some("SYSDBA".to_string()), table_name: "USERS".to_string(), columns: vec![col], @@ -4810,6 +4976,7 @@ fn dameng_create_table_preserves_character_length_units() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Dameng), + driver_profile: None, schema: Some("SYSDBA".to_string()), table_name: "USERS".to_string(), columns: vec![name, code], @@ -4846,6 +5013,7 @@ fn dameng_alter_column_preserves_character_length_unit() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Dameng), + driver_profile: None, schema: Some("SYSDBA".to_string()), table_name: "USERS".to_string(), columns: vec![name], @@ -4883,6 +5051,7 @@ fn dameng_rejects_multiple_identity_columns() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Dameng), + driver_profile: None, schema: Some("SYSDBA".to_string()), table_name: "USERS".to_string(), columns: vec![first, second], @@ -4913,6 +5082,7 @@ fn dameng_rejects_zero_identity_increment() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Dameng), + driver_profile: None, schema: Some("SYSDBA".to_string()), table_name: "USERS".to_string(), columns: vec![col], @@ -4944,6 +5114,7 @@ fn sqlserver_create_table_with_identity() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::SqlServer), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![col], @@ -4970,6 +5141,7 @@ fn mysql_quotes_datetime_literal_default() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "events".to_string(), columns: vec![col], @@ -4996,6 +5168,7 @@ fn mysql_does_not_quote_current_timestamp() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "events".to_string(), columns: vec![col], @@ -5023,6 +5196,7 @@ fn mysql_does_not_quote_temporal_function_with_parens() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "events".to_string(), columns: vec![col], @@ -5049,6 +5223,7 @@ fn mysql_date_literal_default_is_quoted() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![col], @@ -5075,6 +5250,7 @@ fn mysql_time_literal_default_is_quoted() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "shifts".to_string(), columns: vec![col], @@ -5101,6 +5277,7 @@ fn non_temporal_types_are_not_quoted() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "games".to_string(), columns: vec![col], @@ -5138,6 +5315,7 @@ fn postgres_timestamp_literal_is_quoted() { let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::Postgres), + driver_profile: None, schema: Some("public".to_string()), table_name: "events".to_string(), column: col, @@ -5164,6 +5342,7 @@ fn mysql_single_column_alter_quotes_datetime_literal() { let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "events".to_string(), column: col, @@ -5190,6 +5369,7 @@ fn mysql_single_generated_column_change_is_blocked_without_expression_metadata() let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "products".to_string(), column: generated, @@ -5228,6 +5408,7 @@ fn builds_mysql_foreign_key_changes() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "orders".to_string(), columns: Vec::new(), @@ -5259,6 +5440,7 @@ fn builds_mysql_composite_foreign_key() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "order_items".to_string(), columns: Vec::new(), @@ -5293,6 +5475,7 @@ fn builds_oracle_foreign_key_with_supported_actions() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Oracle), + driver_profile: None, schema: Some("HR".to_string()), table_name: "ORDERS_COPY".to_string(), columns: vec![customer_id], @@ -5331,6 +5514,7 @@ fn builds_oracle_foreign_key_replacement() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Oracle), + driver_profile: None, schema: Some("HR".to_string()), table_name: "ORDERS".to_string(), columns: Vec::new(), @@ -5368,6 +5552,7 @@ fn builds_mysql_trigger_changes() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "orders".to_string(), columns: Vec::new(), @@ -5396,6 +5581,7 @@ fn builds_mysql_trigger_changes() { fn builds_sqlserver_trigger_with_multiple_events() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::SqlServer), + driver_profile: None, schema: Some("dbo".to_string()), table_name: "orders".to_string(), columns: Vec::new(), @@ -5440,6 +5626,7 @@ fn rebuilds_changed_sqlserver_trigger_from_complete_metadata_source() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::SqlServer), + driver_profile: None, schema: Some("dbo".to_string()), table_name: "orders".to_string(), columns: Vec::new(), @@ -5478,6 +5665,7 @@ fn sqlserver_trigger_edit_restores_disabled_state() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::SqlServer), + driver_profile: None, schema: Some("dbo".to_string()), table_name: "orders".to_string(), columns: Vec::new(), @@ -5522,6 +5710,7 @@ fn unchanged_postgres_trigger_does_not_block_column_rename() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Postgres), + driver_profile: None, schema: Some("public".to_string()), table_name: "users".to_string(), columns: vec![renamed], @@ -5553,6 +5742,7 @@ fn changed_postgres_trigger_remains_unsupported() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Postgres), + driver_profile: None, schema: Some("public".to_string()), table_name: "users".to_string(), columns: Vec::new(), @@ -5589,6 +5779,7 @@ fn rejects_editing_existing_oracle_trigger_without_complete_source() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Oracle), + driver_profile: None, schema: Some("APP".to_string()), table_name: "DBX_TRIGGER_4320".to_string(), columns: Vec::new(), @@ -5614,6 +5805,7 @@ fn rejects_editing_existing_oracle_trigger_without_complete_source() { fn builds_oracle_statement_trigger_without_row_clause() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Oracle), + driver_profile: None, schema: Some("APP".to_string()), table_name: "ORDERS".to_string(), columns: Vec::new(), @@ -5651,6 +5843,7 @@ fn drops_existing_oracle_trigger_without_reconstructing_it() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Oracle), + driver_profile: None, schema: Some("APP".to_string()), table_name: "ORDERS".to_string(), columns: Vec::new(), @@ -5673,6 +5866,7 @@ fn drops_existing_oracle_trigger_without_reconstructing_it() { fn rejects_unsupported_oracle_compound_trigger_shape() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Oracle), + driver_profile: None, schema: Some("APP".to_string()), table_name: "ORDERS".to_string(), columns: Vec::new(), @@ -5699,6 +5893,7 @@ fn mysql_varchar_default_is_quoted() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![col], @@ -5726,6 +5921,7 @@ fn mysql_char_default_is_quoted() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "items".to_string(), columns: vec![col], @@ -5752,6 +5948,7 @@ fn mysql_text_default_is_quoted() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "products".to_string(), columns: vec![col], @@ -5778,6 +5975,7 @@ fn mysql_enum_default_is_quoted() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![col], @@ -5804,6 +6002,7 @@ fn mysql_int_default_is_not_quoted() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "games".to_string(), columns: vec![col], @@ -5841,6 +6040,7 @@ fn postgres_varchar_default_is_quoted() { let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::Postgres), + driver_profile: None, schema: None, table_name: "items".to_string(), column: col, @@ -5867,6 +6067,7 @@ fn postgres_empty_string_default_is_not_quoted_again() { let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::Postgres), + driver_profile: None, schema: Some("core".to_string()), table_name: "products".to_string(), column: col, @@ -5893,6 +6094,7 @@ fn postgres_string_default_cast_matches_plain_literal() { let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::Postgres), + driver_profile: None, schema: Some("core".to_string()), table_name: "products".to_string(), column: col, @@ -5919,6 +6121,7 @@ fn postgres_integer_default_is_not_quoted() { let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions { database_type: Some(DatabaseType::Postgres), + driver_profile: None, schema: Some("core".to_string()), table_name: "products".to_string(), column: col, @@ -5936,6 +6139,7 @@ fn mysql_character_column_add_with_charset_collation() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![col], @@ -5970,6 +6174,7 @@ fn mysql_numeric_column_omits_charset_collation_in_column_definition() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "games".to_string(), columns: vec![col], @@ -6014,6 +6219,7 @@ fn mysql_numeric_column_ignores_charset_collation_in_change_detection() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "games".to_string(), columns: vec![col], @@ -6053,6 +6259,7 @@ fn mysql_character_column_detects_charset_collation_change() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![col], @@ -6097,6 +6304,7 @@ fn mysql_character_column_preserves_charset_collation_on_other_change() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![col], @@ -6143,6 +6351,7 @@ fn mysql_inherited_column_charset_is_omitted_from_generated_ddl() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![col], @@ -6188,6 +6397,7 @@ fn mysql_explicit_column_charset_survives_the_table_default_comparison() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![col], @@ -6234,6 +6444,7 @@ fn mysql_inherited_column_charset_does_not_register_as_a_change() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![col], @@ -6273,6 +6484,7 @@ fn mysql_collation_switched_away_from_the_table_default_is_emitted() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![col], @@ -6317,6 +6529,7 @@ fn mysql_column_charset_switched_to_the_table_default_drops_the_clause() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![col], @@ -6358,6 +6571,7 @@ fn mysql_column_charset_is_kept_when_the_table_default_is_unknown() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![col], @@ -6394,6 +6608,7 @@ fn mysql_create_table_omits_inherited_column_charset() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "users".to_string(), columns: vec![inherited, explicit], @@ -6545,6 +6760,7 @@ fn oscar_create_table_with_primary_key_and_comments() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Oscar), + driver_profile: None, schema: Some("SYSDBA".to_string()), table_name: "USERS".to_string(), columns: vec![id, name], @@ -6740,6 +6956,7 @@ fn oscar_drop_index_with_schema_qualifier() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Oscar), + driver_profile: None, schema: Some("SYSDBA".to_string()), table_name: "users".to_string(), columns: Vec::new(), @@ -6762,6 +6979,7 @@ fn oscar_drop_index_with_schema_qualifier() { fn oscar_table_comment_uses_comment_on_table() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Oscar), + driver_profile: None, schema: Some("SYSDBA".to_string()), table_name: "users".to_string(), columns: Vec::new(), @@ -6846,6 +7064,7 @@ fn postgres_partitioned_parent_concurrent_request_rejected() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Postgres), + driver_profile: None, schema: Some("public".to_string()), table_name: "USERS".to_string(), columns: Vec::new(), @@ -6878,6 +7097,7 @@ fn postgres_partitioned_parent_plain_index_unchanged() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Postgres), + driver_profile: None, schema: Some("public".to_string()), table_name: "USERS".to_string(), columns: Vec::new(), @@ -6922,6 +7142,7 @@ fn postgres_create_table_partitioned_concurrent_request_rejected() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Postgres), + driver_profile: None, schema: Some("public".to_string()), table_name: "events".to_string(), columns: vec![column("id")], @@ -7050,6 +7271,7 @@ fn postgres_create_table_concurrent_index() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Postgres), + driver_profile: None, schema: Some("public".to_string()), table_name: "users".to_string(), columns: vec![id], @@ -7167,6 +7389,7 @@ fn non_postgres_concurrent_flag_is_ignored() { fn gaussdb_m_options(columns: Vec) -> TableStructureSqlOptions { TableStructureSqlOptions { database_type: Some(DatabaseType::Gaussdb), + driver_profile: None, schema: None, table_name: "USERS".to_string(), columns, @@ -7401,6 +7624,7 @@ fn gaussdb_m_rebuild_index_unchanged_type_does_not_rebuild() { // No columns — just test the index itself has no change let options = TableStructureSqlOptions { database_type: Some(DatabaseType::Gaussdb), + driver_profile: None, schema: None, table_name: "USERS".to_string(), columns: Vec::new(), @@ -7449,6 +7673,7 @@ fn mysql_create_table_nullable_timestamp_without_default_gets_explicit_null() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "u7_game_order_step".to_string(), columns: vec![created_at, updated_at], @@ -7479,6 +7704,7 @@ fn mysql_create_table_nullable_timestamp_with_default_still_gets_explicit_null() let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "events".to_string(), columns: vec![updated_at], @@ -7507,6 +7733,7 @@ fn mysql_create_table_nullable_datetime_does_not_gain_null_keyword() { let result = build_create_table_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "events".to_string(), columns: vec![updated_at], @@ -7535,6 +7762,7 @@ fn mysql_add_column_nullable_timestamp_without_default_gets_explicit_null() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: "u7_game_order_step".to_string(), columns: vec![updated_at], diff --git a/crates/dbx-core/src/table_structure_sql/triggers.rs b/crates/dbx-core/src/table_structure_sql/triggers.rs index 870c8a38d7..25fe5b3c7f 100644 --- a/crates/dbx-core/src/table_structure_sql/triggers.rs +++ b/crates/dbx-core/src/table_structure_sql/triggers.rs @@ -22,7 +22,7 @@ fn build_trigger_sql_with_mode( return Vec::new(); } - let dialect = super::dialect::capabilities_for(options.database_type).dialect; + let dialect = super::dialect::capabilities_for(options.database_type, options.driver_profile.as_deref()).dialect; let database_label = database_label(options.database_type); if !matches!(dialect, StructureDialect::Mysql | StructureDialect::Oracle | StructureDialect::SqlServer) { if options.triggers.iter().any(has_trigger_edit) { diff --git a/crates/dbx-core/src/table_structure_sql/types.rs b/crates/dbx-core/src/table_structure_sql/types.rs index 1345e0625b..aae0dbb559 100644 --- a/crates/dbx-core/src/table_structure_sql/types.rs +++ b/crates/dbx-core/src/table_structure_sql/types.rs @@ -207,6 +207,11 @@ pub struct TriggerInfo { pub struct TableStructureSqlOptions { #[serde(default, skip_serializing_if = "Option::is_none")] pub database_type: Option, + /// Driver profile reported by the connection (e.g. `"gbase8s"`). GBase 8s + /// is Informix-compatible rather than MySQL-compatible like the rest of + /// the `Gbase` family, so this disambiguates which dialect to generate. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub driver_profile: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub schema: Option, pub table_name: String, @@ -281,6 +286,8 @@ pub struct SingleColumnAlterSqlOptions { #[serde(default, skip_serializing_if = "Option::is_none")] pub database_type: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + pub driver_profile: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub schema: Option, pub table_name: String, pub column: EditableStructureColumn, diff --git a/crates/dbx-core/src/table_structure_sql/validation.rs b/crates/dbx-core/src/table_structure_sql/validation.rs index 3de6e0d0ec..11008a22fd 100644 --- a/crates/dbx-core/src/table_structure_sql/validation.rs +++ b/crates/dbx-core/src/table_structure_sql/validation.rs @@ -15,7 +15,7 @@ use super::util::clean; /// a concurrent request in the first place). pub(super) fn validate_concurrent_index_scope(options: &TableStructureSqlOptions) -> Vec { let mut errors = Vec::new(); - let capabilities = capabilities_for(options.database_type); + let capabilities = capabilities_for(options.database_type, options.driver_profile.as_deref()); if !capabilities.index_concurrent || capabilities.dialect != StructureDialect::Postgres { return errors; } @@ -67,7 +67,7 @@ pub(super) fn validate_dameng_identity( columns: &[&EditableStructureColumn], warnings: &mut Vec, ) { - if capabilities_for(options.database_type).dialect != StructureDialect::Dameng { + if capabilities_for(options.database_type, options.driver_profile.as_deref()).dialect != StructureDialect::Dameng { return; } @@ -109,7 +109,7 @@ pub(super) fn validate_columns(columns: &[&EditableStructureColumn], warnings: & /// classes are almost always needed. This is a warning, not an error, because /// the default class may be deliberate in some cases. fn validate_gin_opclass_warnings(options: &TableStructureSqlOptions, warnings: &mut Vec) { - let capabilities = capabilities_for(options.database_type); + let capabilities = capabilities_for(options.database_type, options.driver_profile.as_deref()); if capabilities.dialect != StructureDialect::Postgres { return; } diff --git a/crates/dbx-core/tests/live_mysql_generated_columns.rs b/crates/dbx-core/tests/live_mysql_generated_columns.rs index 8deaac0c3d..19d03a9b29 100644 --- a/crates/dbx-core/tests/live_mysql_generated_columns.rs +++ b/crates/dbx-core/tests/live_mysql_generated_columns.rs @@ -38,6 +38,7 @@ fn editable_column(column: &ColumnInfo, index: usize) -> EditableStructureColumn fn change_options(table_name: &str, columns: Vec) -> TableStructureSqlOptions { TableStructureSqlOptions { database_type: Some(DatabaseType::Mysql), + driver_profile: None, schema: None, table_name: table_name.to_string(), columns, diff --git a/crates/dbx-core/tests/live_postgres_concurrent_index.rs b/crates/dbx-core/tests/live_postgres_concurrent_index.rs index b908489dc3..704e5fc0d5 100644 --- a/crates/dbx-core/tests/live_postgres_concurrent_index.rs +++ b/crates/dbx-core/tests/live_postgres_concurrent_index.rs @@ -52,6 +52,7 @@ async fn live_postgres_concurrent_index_builds_valid_index() { let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Postgres), + driver_profile: None, schema: Some(schema.clone()), table_name: "users".to_string(), columns: Vec::new(), @@ -184,6 +185,7 @@ async fn live_postgres_partitioned_parent_concurrent_request_rejected() { idx.id = "idx_events_id".to_string(); let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Postgres), + driver_profile: None, schema: Some(schema.clone()), table_name: "events".to_string(), columns: Vec::new(), diff --git a/crates/dbx-core/tests/live_sqlserver_completion.rs b/crates/dbx-core/tests/live_sqlserver_completion.rs index 7a6da9002c..b9d54154cc 100644 --- a/crates/dbx-core/tests/live_sqlserver_completion.rs +++ b/crates/dbx-core/tests/live_sqlserver_completion.rs @@ -999,6 +999,7 @@ async fn live_sqlserver_table_structure_default_changes_drop_existing_constraint active.original_position = Some(1); let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::SqlServer), + driver_profile: None, schema: Some(schema.clone()), table_name: table.to_string(), columns: vec![sku, active],