diff --git a/apps/desktop/src/components/structure/TableStructureEditor.vue b/apps/desktop/src/components/structure/TableStructureEditor.vue index 7c0d702d08..9b96580722 100644 --- a/apps/desktop/src/components/structure/TableStructureEditor.vue +++ b/apps/desktop/src/components/structure/TableStructureEditor.vue @@ -33,6 +33,7 @@ import { invalidateObjectMetadataCache, loadObjectMetadataFacet, type ObjectMeta import { invalidateTableMetadataCache } from "@/lib/metadata/tableMetadataCache"; import { type BuildTableStructureChangeSqlOptions, type EditableStructureColumn, type EditableStructureForeignKey, type EditableStructureIndex, type EditableStructureTrigger } from "@/lib/table/tableStructureEditorSql"; import { buildMysqlAutoIncrementCounterStatement, canEditMysqlAutoIncrementCounter, refreshMysqlAutoIncrementCounterDraft } from "@/lib/table/mysqlAutoIncrementCounter"; +import { MYSQL_STORAGE_ENGINES_SQL, mysqlTableEngineSql, mysqlTableEngineSqlOption, parseMysqlTableEngineMetadata, refreshMysqlTableEngineDraft, supportsMysqlTableEngine } from "@/lib/table/mysqlTableEngine"; import { PRESET_FIELDS_TEMPLATE_ID, createTableColumnTemplateDrafts } from "@/lib/table/tableColumnTemplates"; import { getMysqlDataTypeHelp } from "@/lib/table/mysqlDataTypeHelp"; import { getPostgresDataTypeHelp, gaussdbMTypeDisplayName } from "@/lib/table/postgresDataTypeHelp"; @@ -917,6 +918,11 @@ const mysqlAutoIncrementValue = ref(); const originalMysqlAutoIncrementValue = ref(); const mysqlAutoIncrementLoading = ref(false); const mysqlAutoIncrementLoadError = ref(""); +const mysqlTableEngine = ref(""); +const originalMysqlTableEngine = ref(""); +const mysqlTableEngineOptions = ref([]); +const mysqlTableEngineLoading = ref(false); +const mysqlTableEngineLoadError = ref(""); const tableOwner = ref(""); const originalTableOwner = ref(""); const tableOwnerLoading = ref(false); @@ -927,6 +933,7 @@ const tableOwnerRolesLoadError = ref(""); const supportsTableOwner = computed(() => !isCreateMode.value && databaseType.value === "postgres"); const canEditMysqlAutoIncrement = computed(() => canEditMysqlAutoIncrementCounter(connection.value, isCreateMode.value, columns.value)); const canBuildMysqlAutoIncrement = computed(() => canEditMysqlAutoIncrement.value && !mysqlAutoIncrementLoading.value && !mysqlAutoIncrementLoadError.value && originalMysqlAutoIncrementValue.value !== undefined); +const supportsMysqlEngine = computed(() => supportsMysqlTableEngine(connection.value)); const tableOwnerOptions = computed(() => { const owner = tableOwner.value; if (!owner || tableOwnerRoles.value.includes(owner)) return tableOwnerRoles.value; @@ -950,6 +957,7 @@ let structureLoadRequestId = 0; let tableOwnerLoadRequestId = 0; let tableOwnerRolesLoadRequestId = 0; let mysqlAutoIncrementLoadRequestId = 0; +let mysqlTableEngineLoadRequestId = 0; let dataTypeOptionsRequestId = 0; let sqlPreviewDebounceTimer: ReturnType | undefined; let deferredSqlPreviewRefresh = false; @@ -1137,6 +1145,8 @@ function createCurrentDraft(initialized = true): TableStructureEditorDraft { originalTableComment: originalTableComment.value, mysqlAutoIncrementValue: mysqlAutoIncrementValue.value, originalMysqlAutoIncrementValue: originalMysqlAutoIncrementValue.value, + mysqlTableEngine: mysqlTableEngine.value, + originalMysqlTableEngine: originalMysqlTableEngine.value, tableOwner: tableOwner.value, originalTableOwner: originalTableOwner.value, columns: cloneDraftValue(columns.value), @@ -1169,6 +1179,8 @@ function restoreDraft(draft: TableStructureEditorDraft) { originalTableComment.value = draft.originalTableComment || ""; mysqlAutoIncrementValue.value = draft.mysqlAutoIncrementValue; originalMysqlAutoIncrementValue.value = draft.originalMysqlAutoIncrementValue; + mysqlTableEngine.value = draft.mysqlTableEngine || ""; + originalMysqlTableEngine.value = draft.originalMysqlTableEngine || ""; tableOwner.value = draft.tableOwner || ""; originalTableOwner.value = draft.originalTableOwner || ""; columns.value = cloneDraftValue(draft.columns || []); @@ -1252,7 +1264,7 @@ function markDraftHydratedAndSync() { function hasPendingStructureChanges(): boolean { if (isCreateMode.value) { - return !!newTableName.value.trim() || !!tableComment.value.trim() || columns.value.length > 0 || indexes.value.length > 0 || foreignKeys.value.length > 0 || triggers.value.length > 0; + return !!newTableName.value.trim() || !!tableComment.value.trim() || mysqlTableEngine.value !== originalMysqlTableEngine.value || columns.value.length > 0 || indexes.value.length > 0 || foreignKeys.value.length > 0 || triggers.value.length > 0; } const scope = captureStructureRefreshScope(); return ( @@ -1261,6 +1273,7 @@ function hasPendingStructureChanges(): boolean { scope.foreignKeys || scope.triggers || scope.tableComment || + mysqlTableEngine.value.toLowerCase() !== originalMysqlTableEngine.value.toLowerCase() || (canBuildMysqlAutoIncrement.value && mysqlAutoIncrementValue.value !== originalMysqlAutoIncrementValue.value) || (supportsTableOwner.value && tableOwner.value.trim() !== originalTableOwner.value.trim()) ); @@ -1405,6 +1418,7 @@ function structureChangeOptions(): BuildTableStructureChangeSqlOptions { triggers: triggers.value, tableComment: tableComment.value, originalTableComment: isCreateMode.value ? undefined : originalTableComment.value, + mysqlEngine: mysqlTableEngineSqlOption({ value: mysqlTableEngine.value, originalValue: originalMysqlTableEngine.value }, isCreateMode.value, supportsMysqlEngine.value && !mysqlTableEngineLoading.value && !mysqlTableEngineLoadError.value), partitioned: isPartitionedParent.value, isGaussdbMMode: connection.value?.driver_profile?.toLowerCase() === "gaussdb-m", }; @@ -1491,6 +1505,7 @@ const canApply = computed( !saving.value && !postSaveRefreshing.value && !secondaryMetadataLoading.value && + !mysqlTableEngineLoading.value && !sqlPreviewLoading.value && !sqlPreviewPending.value && pendingStatements.value.length > 0 && @@ -1542,6 +1557,12 @@ function resetState() { mysqlAutoIncrementLoadRequestId += 1; mysqlAutoIncrementLoading.value = false; mysqlAutoIncrementLoadError.value = ""; + mysqlTableEngine.value = ""; + originalMysqlTableEngine.value = ""; + mysqlTableEngineOptions.value = []; + mysqlTableEngineLoadRequestId += 1; + mysqlTableEngineLoading.value = false; + mysqlTableEngineLoadError.value = ""; tableOwner.value = ""; originalTableOwner.value = ""; tableOwnerLoadRequestId += 1; @@ -1578,9 +1599,9 @@ async function reloadStructureFromDatabase() { loadedMetadataFacets.clear(); if (refreshDdl) { ddlFetched.value = false; - await Promise.all([fetchDdl(true), loadTableOwner(true), loadTableOwnerRoles()]); + await Promise.all([fetchDdl(true), loadTableOwner(true), loadTableOwnerRoles(), loadMysqlTableEngine(true)]); } else { - await Promise.all([loadStructure(false, visibleTableStructureRefreshScope(activeTab.value), true, { blockSecondaryMetadata: true, forceDdl: true, forceMetadata: true }), loadTableOwner(true), loadTableOwnerRoles()]); + await Promise.all([loadStructure(false, visibleTableStructureRefreshScope(activeTab.value), true, { blockSecondaryMetadata: true, forceDdl: true, forceMetadata: true }), loadTableOwner(true), loadTableOwnerRoles(), loadMysqlTableEngine(true)]); } } @@ -1641,6 +1662,43 @@ async function loadMysqlAutoIncrementCounter(preserveDraft = false) { } } +async function loadMysqlTableEngine(preserveDraft = false) { + const requestId = ++mysqlTableEngineLoadRequestId; + const connectionId = props.connectionId; + const database = props.database; + if (!supportsMysqlEngine.value || !connectionId || !database) { + mysqlTableEngine.value = ""; + originalMysqlTableEngine.value = ""; + mysqlTableEngineOptions.value = []; + mysqlTableEngineLoading.value = false; + mysqlTableEngineLoadError.value = ""; + return; + } + + mysqlTableEngineLoading.value = true; + mysqlTableEngineLoadError.value = ""; + try { + await store.ensureConnected(connectionId); + const [enginesResult, tableResult] = await Promise.all([ + api.executeQuery(connectionId, database, MYSQL_STORAGE_ENGINES_SQL, undefined, undefined, { maxRows: 100 }), + isCreateMode.value || !props.tableName ? Promise.resolve(undefined) : api.executeQuery(connectionId, database, mysqlTableEngineSql(database, props.tableName), undefined, undefined, { maxRows: 1 }), + ]); + if (requestId !== mysqlTableEngineLoadRequestId) return; + const metadata = parseMysqlTableEngineMetadata(enginesResult, tableResult); + const draft = refreshMysqlTableEngineDraft(metadata, { value: mysqlTableEngine.value, originalValue: originalMysqlTableEngine.value }, isCreateMode.value, preserveDraft); + const options = [...metadata.engines]; + if (draft.value && !options.some((option) => option.toLowerCase() === draft.value.toLowerCase())) options.unshift(draft.value); + mysqlTableEngineOptions.value = options; + mysqlTableEngine.value = draft.value; + originalMysqlTableEngine.value = draft.originalValue; + } catch (error: any) { + if (requestId !== mysqlTableEngineLoadRequestId) return; + mysqlTableEngineLoadError.value = error?.message || String(error); + } finally { + if (requestId === mysqlTableEngineLoadRequestId) mysqlTableEngineLoading.value = false; + } +} + async function loadTableOwner(force = false, preserveDraft = false) { const connectionId = props.connectionId; const database = props.database; @@ -1852,7 +1910,7 @@ async function loadStructure( async function refreshStructureAfterSave(scope: TableStructureRefreshScope, characterLengthUnitsAfterSave: ReadonlyMap) { try { - await Promise.all([loadStructure(true, scope, false, { blockSecondaryMetadata: true, characterLengthUnitsAfterSave }), loadTableOwner(true)]); + await Promise.all([loadStructure(true, scope, false, { blockSecondaryMetadata: true, characterLengthUnitsAfterSave }), loadTableOwner(true), loadMysqlTableEngine(false)]); } catch (e) { console.warn("[DBX][structure-editor:post-save-refresh-failed]", e); } finally { @@ -3354,6 +3412,7 @@ onMounted(() => { observeStructureHorizontalScroller(); void loadTableOwner(false, props.draft?.tableOwner !== undefined); void loadTableOwnerRoles(); + void loadMysqlTableEngine(props.draft?.mysqlTableEngine !== undefined); if (props.draft?.initialized) { void hydrateRestoredDraftFromDatabase().then(() => { applyInitialStructureTarget(); @@ -3375,6 +3434,9 @@ onActivated(() => { void loadDynamicDataTypeOptions(); if (supportsTableOwner.value && !loadedMetadataFacets.has("owner")) void loadTableOwner(false, props.draft?.tableOwner !== undefined); if (supportsTableOwner.value && !tableOwnerRolesLoading.value && tableOwnerRoles.value.length === 0 && !tableOwnerRolesLoadError.value) void loadTableOwnerRoles(); + if (supportsMysqlEngine.value && !mysqlTableEngineLoading.value && mysqlTableEngineOptions.value.length === 0 && !mysqlTableEngineLoadError.value) { + void loadMysqlTableEngine(props.draft?.mysqlTableEngine !== undefined); + } if (props.draft?.initialized && !draftHydrated) { restoreDraft(props.draft); applyInitialStructureTarget(); @@ -3503,6 +3565,10 @@ watch( originalMysqlAutoIncrementValue, mysqlAutoIncrementLoading, mysqlAutoIncrementLoadError, + mysqlTableEngine, + originalMysqlTableEngine, + mysqlTableEngineLoading, + mysqlTableEngineLoadError, tableOwner, columns, indexes, @@ -3625,6 +3691,29 @@ watch([activeTab, ddlLoading], ([tab, loading]) => { +
+ + + + + + + + {{ t("structureEditor.mysqlTableEngineLoadFailed", { message: mysqlTableEngineLoadError }) }} + +
+
{ + it("builds mode-independent metadata SQL for quoted and Unicode names", () => { + const sql = mysqlTableEngineSql("db'x", "订单\\archive"); + expect(sql).toContain("TABLE_SCHEMA = CONVERT(X'64622778' USING utf8mb4)"); + expect(sql).toContain("TABLE_NAME = CONVERT(X'e8aea2e58d955c61726368697665' USING utf8mb4)"); + expect(sql).not.toContain("db'x"); + }); + + it("keeps supported engines, the server default, and the current table engine", () => { + const engines = result( + ["Engine", "Support", "Comment"], + [ + ["InnoDB", "DEFAULT", ""], + ["MyISAM", "YES", ""], + ["FEDERATED", "NO", ""], + ], + ); + const table = result(["engine"], [["FEDERATED"]]); + + expect(parseMysqlTableEngineMetadata(engines, table)).toEqual({ + currentEngine: "FEDERATED", + defaultEngine: "InnoDB", + engines: ["FEDERATED", "InnoDB", "MyISAM"], + }); + }); + + it("uses the default for a new table and preserves a dirty edit across refresh", () => { + const initial = { currentEngine: "MyISAM", defaultEngine: "InnoDB", engines: ["InnoDB", "MyISAM"] }; + expect(mysqlTableEngineDraft(initial, true)).toEqual({ value: "InnoDB", originalValue: "InnoDB" }); + expect(mysqlTableEngineDraft(initial, false)).toEqual({ value: "MyISAM", originalValue: "MyISAM" }); + + expect(refreshMysqlTableEngineDraft({ ...initial, currentEngine: "InnoDB" }, { value: "ARCHIVE", originalValue: "MyISAM" }, false, true)).toEqual({ + value: "ARCHIVE", + originalValue: "InnoDB", + }); + }); + + it("sends the selected engine for creates and only changed engines for edits", () => { + expect(mysqlTableEngineSqlOption({ value: "InnoDB", originalValue: "InnoDB" }, true, true)).toBe("InnoDB"); + expect(mysqlTableEngineSqlOption({ value: "innodb", originalValue: "InnoDB" }, false, true)).toBeUndefined(); + expect(mysqlTableEngineSqlOption({ value: "MyISAM", originalValue: "InnoDB" }, false, true)).toBe("MyISAM"); + expect(mysqlTableEngineSqlOption({ value: "MyISAM", originalValue: "InnoDB" }, false, false)).toBeUndefined(); + }); + + it("is limited to native MySQL connections", () => { + expect(supportsMysqlTableEngine({ db_type: "mysql", driver_profile: "mysql" })).toBe(true); + expect(supportsMysqlTableEngine({ db_type: "mysql", driver_profile: undefined })).toBe(true); + expect(supportsMysqlTableEngine({ db_type: "jdbc", driver_profile: "mysql" })).toBe(false); + expect(supportsMysqlTableEngine({ db_type: "mysql", driver_profile: "goldendb" })).toBe(false); + }); +}); diff --git a/apps/desktop/src/lib/table/mysqlTableEngine.ts b/apps/desktop/src/lib/table/mysqlTableEngine.ts new file mode 100644 index 0000000000..721b99a4a3 --- /dev/null +++ b/apps/desktop/src/lib/table/mysqlTableEngine.ts @@ -0,0 +1,86 @@ +import type { ConnectionConfig, QueryResult } from "@/types/database"; + +export interface MysqlTableEngineMetadata { + currentEngine?: string; + defaultEngine?: string; + engines: string[]; +} + +export interface MysqlTableEngineDraft { + value: string; + originalValue: string; +} + +export const MYSQL_STORAGE_ENGINES_SQL = "SHOW ENGINES"; + +function mysqlUtf8Literal(value: string): string { + const bytes = new TextEncoder().encode(value); + const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join(""); + return `CONVERT(X'${hex}' USING utf8mb4)`; +} + +export function mysqlTableEngineSql(database: string, table: string): string { + return ["SELECT ENGINE AS engine", "FROM information_schema.TABLES", `WHERE TABLE_SCHEMA = ${mysqlUtf8Literal(database)}`, ` AND TABLE_NAME = ${mysqlUtf8Literal(table)}`, "LIMIT 1"].join("\n"); +} + +function columnIndex(result: QueryResult, name: string): number { + return result.columns.findIndex((column) => column.trim().toLowerCase() === name.toLowerCase()); +} + +function cellText(result: QueryResult, rowIndex: number, index: number): string { + if (index < 0) return ""; + const value = result.rows[rowIndex]?.[index]; + return value === null || value === undefined ? "" : String(value).trim(); +} + +export function parseMysqlTableEngineMetadata(enginesResult: QueryResult, tableResult?: QueryResult): MysqlTableEngineMetadata { + const engineIndex = columnIndex(enginesResult, "Engine"); + const supportIndex = columnIndex(enginesResult, "Support"); + const engines: string[] = []; + let defaultEngine: string | undefined; + const seen = new Set(); + + for (let rowIndex = 0; rowIndex < enginesResult.rows.length; rowIndex += 1) { + const engine = cellText(enginesResult, rowIndex, engineIndex); + const support = cellText(enginesResult, rowIndex, supportIndex).toUpperCase(); + if (!engine || (support !== "YES" && support !== "DEFAULT")) continue; + const key = engine.toLowerCase(); + if (!seen.has(key)) { + engines.push(engine); + seen.add(key); + } + if (support === "DEFAULT") defaultEngine = engine; + } + + let currentEngine: string | undefined; + if (tableResult) { + currentEngine = cellText(tableResult, 0, columnIndex(tableResult, "engine")) || undefined; + if (currentEngine && !seen.has(currentEngine.toLowerCase())) engines.unshift(currentEngine); + } + + return { currentEngine, defaultEngine, engines }; +} + +export function mysqlTableEngineDraft(metadata: MysqlTableEngineMetadata, isCreateMode: boolean): MysqlTableEngineDraft { + const current = (isCreateMode ? metadata.defaultEngine || metadata.engines[0] : metadata.currentEngine) || ""; + return { value: current, originalValue: current }; +} + +export function refreshMysqlTableEngineDraft(metadata: MysqlTableEngineMetadata, current: MysqlTableEngineDraft, isCreateMode: boolean, preserveDraft: boolean): MysqlTableEngineDraft { + const server = mysqlTableEngineDraft(metadata, isCreateMode); + if (!preserveDraft || current.value === current.originalValue) return server; + return { value: current.value, originalValue: server.originalValue }; +} + +export function mysqlTableEngineSqlOption(draft: MysqlTableEngineDraft, isCreateMode: boolean, supported: boolean): string | undefined { + const value = draft.value.trim(); + if (!supported || !value) return undefined; + if (!isCreateMode && value.toLowerCase() === draft.originalValue.trim().toLowerCase()) return undefined; + return value; +} + +export function supportsMysqlTableEngine(connection: Pick | undefined): boolean { + if (connection?.db_type !== "mysql") return false; + const profile = connection.driver_profile?.trim().toLowerCase(); + return !profile || profile === "mysql"; +} diff --git a/apps/desktop/src/lib/table/tableStructureEditorSql.ts b/apps/desktop/src/lib/table/tableStructureEditorSql.ts index 8be04231b3..319d546843 100644 --- a/apps/desktop/src/lib/table/tableStructureEditorSql.ts +++ b/apps/desktop/src/lib/table/tableStructureEditorSql.ts @@ -83,6 +83,7 @@ export interface BuildTableStructureChangeSqlOptions { triggers?: EditableStructureTrigger[]; tableComment?: string; originalTableComment?: string; + mysqlEngine?: string; /** The target table is a PostgreSQL partitioned parent (`relkind = 'p'`); * the backend rejects `CREATE INDEX CONCURRENTLY` on such tables (fail * closed) instead of downgrading to a blocking `CREATE INDEX`. */ diff --git a/apps/desktop/src/types/database.ts b/apps/desktop/src/types/database.ts index 90efa2b6d2..0747fe74f1 100644 --- a/apps/desktop/src/types/database.ts +++ b/apps/desktop/src/types/database.ts @@ -1022,6 +1022,8 @@ export interface TableStructureEditorDraft { originalTableComment: string; mysqlAutoIncrementValue?: string; originalMysqlAutoIncrementValue?: string; + mysqlTableEngine?: string; + originalMysqlTableEngine?: string; tableOwner?: string; originalTableOwner?: string; columns: import("@/lib/table/tableStructureEditorSql").EditableStructureColumn[]; diff --git a/crates/dbx-core/src/table_structure_sql.rs b/crates/dbx-core/src/table_structure_sql.rs index 878a1132bf..3fb381af4d 100644 --- a/crates/dbx-core/src/table_structure_sql.rs +++ b/crates/dbx-core/src/table_structure_sql.rs @@ -6,6 +6,7 @@ mod create_table; mod dialect; mod foreign_keys; mod indexes; +mod mysql_engine; mod owner; mod sqlite_rebuild; mod triggers; @@ -28,10 +29,15 @@ use columns::{build_column_sql, validate_primary_key_change_scope}; use comments::build_table_comment_sql; use foreign_keys::build_foreign_key_sql; use indexes::build_index_sql; +use mysql_engine::{build_mysql_engine_change_sql, validate_mysql_engine}; use triggers::build_trigger_sql; use validation::{validate_concurrent_index_scope, validate_draft}; pub fn build_table_structure_change_sql(mut options: TableStructureSqlOptions) -> TableStructureSqlResult { + let mysql_engine_errors = validate_mysql_engine(&options); + if !mysql_engine_errors.is_empty() { + return TableStructureSqlResult { statements: Vec::new(), warnings: mysql_engine_errors }; + } // GaussDB M-mode uses MySQL-compatible SQL dialect with backtick quoting. // Map to StructureDialect::Mysql so DDL is generated correctly. if options.is_gaussdb_m_mode { @@ -54,6 +60,7 @@ pub fn build_table_structure_change_sql(mut options: TableStructureSqlOptions) - statements.extend(build_foreign_key_sql(&options, &mut warnings)); statements.extend(build_trigger_sql(&options, &mut warnings)); statements.extend(build_table_comment_sql(&options, &mut warnings)); + statements.extend(build_mysql_engine_change_sql(&options)); TableStructureSqlResult { statements, warnings } } 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 d86c64495a..bbab8745ed 100644 --- a/crates/dbx-core/src/table_structure_sql/create_table.rs +++ b/crates/dbx-core/src/table_structure_sql/create_table.rs @@ -6,6 +6,7 @@ use super::comments::{build_sqlserver_column_comment_sql, build_sqlserver_table_ use super::dialect::{capabilities_for, database_label, StructureDialect}; use super::foreign_keys::build_foreign_key_sql; use super::indexes::build_create_index_statements; +use super::mysql_engine::{append_mysql_table_option, validate_mysql_engine}; use super::triggers::build_trigger_sql; use super::types::{TableStructureSqlOptions, TableStructureSqlResult}; use super::util::{clean, format_default_for_sql, normalize_default, qualified_table, quote_ident, quote_string}; @@ -24,6 +25,7 @@ pub fn build_create_table_sql(mut options: TableStructureSqlOptions) -> TableStr } options.table_name = clean(&options.table_name); let mut warnings = Vec::new(); + warnings.extend(validate_mysql_engine(&options)); // Fail closed: a concurrent-index request on a partitioned parent (or on an // existing index in a hand-built draft) is refused up front instead of // degrading into blocking index DDL. @@ -103,12 +105,18 @@ pub fn build_create_table_sql(mut options: TableStructureSqlOptions) -> TableStr statements.push(format!("CREATE TABLE {table} (\n {}\n);", column_definitions.join(",\n "))); + if let Some(engine) = options.mysql_engine.as_deref().map(str::trim).filter(|engine| !engine.is_empty()) { + if let Some(statement) = statements.last_mut() { + append_mysql_table_option(statement, &format!("ENGINE = {engine}")); + } + } + if capabilities.comment { let table_comment = clean(options.table_comment.as_deref().unwrap_or("")); if !table_comment.is_empty() { if matches!(dialect, StructureDialect::Mysql | StructureDialect::GaussdbM) { if let Some(last) = statements.last_mut() { - *last = last.replace(");", &format!(") COMMENT = {};", quote_string(&table_comment))); + append_mysql_table_option(last, &format!("COMMENT = {}", quote_string(&table_comment))); } } else if matches!( dialect, diff --git a/crates/dbx-core/src/table_structure_sql/mysql_engine.rs b/crates/dbx-core/src/table_structure_sql/mysql_engine.rs new file mode 100644 index 0000000000..1d0472bea7 --- /dev/null +++ b/crates/dbx-core/src/table_structure_sql/mysql_engine.rs @@ -0,0 +1,38 @@ +use super::dialect::StructureDialect; +use super::types::TableStructureSqlOptions; +use super::util::qualified_table; +use crate::models::connection::DatabaseType; + +pub(super) fn validate_mysql_engine(options: &TableStructureSqlOptions) -> Vec { + let Some(engine) = options.mysql_engine.as_deref() else { + return Vec::new(); + }; + let engine = engine.trim(); + if options.database_type != Some(DatabaseType::Mysql) || options.is_gaussdb_m_mode { + return vec!["Changing the table engine is supported only for native MySQL connections.".to_string()]; + } + if engine.is_empty() { + return vec!["MySQL table engine is required.".to_string()]; + } + if engine.len() > 64 || !engine.chars().all(|character| character.is_ascii_alphanumeric() || character == '_') { + return vec!["MySQL table engine contains invalid characters.".to_string()]; + } + Vec::new() +} + +pub(super) fn build_mysql_engine_change_sql(options: &TableStructureSqlOptions) -> Vec { + let Some(engine) = options.mysql_engine.as_deref().map(str::trim).filter(|engine| !engine.is_empty()) else { + return Vec::new(); + }; + let table = qualified_table(StructureDialect::Mysql, options.schema.as_deref(), &options.table_name); + vec![format!("ALTER TABLE {table} ENGINE = {engine};")] +} + +pub(super) fn append_mysql_table_option(statement: &mut String, option: &str) { + if statement.ends_with(';') { + statement.pop(); + } + statement.push(' '); + statement.push_str(option); + statement.push(';'); +} 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 32e5b1f397..598e18dade 100644 --- a/crates/dbx-core/src/table_structure_sql/sqlite_rebuild.rs +++ b/crates/dbx-core/src/table_structure_sql/sqlite_rebuild.rs @@ -1516,6 +1516,7 @@ mod tests { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, } diff --git a/crates/dbx-core/src/table_structure_sql/tests.rs b/crates/dbx-core/src/table_structure_sql/tests.rs index 915c826e79..3bb0adb665 100644 --- a/crates/dbx-core/src/table_structure_sql/tests.rs +++ b/crates/dbx-core/src/table_structure_sql/tests.rs @@ -59,11 +59,56 @@ fn structure_change_options( triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, } } +#[test] +fn mysql_table_engine_change_generates_alter_table() { + let mut options = structure_change_options(DatabaseType::Mysql, Some("dbx_test"), "remote_orders", Vec::new()); + options.mysql_engine = Some("FEDERATED".to_string()); + + let result = build_table_structure_change_sql(options); + + assert!(result.warnings.is_empty()); + assert_eq!(result.statements, vec!["ALTER TABLE `remote_orders` ENGINE = FEDERATED;"]); +} + +#[test] +fn mysql_create_table_includes_engine_before_comment() { + let mut options = structure_change_options(DatabaseType::Mysql, Some("dbx_test"), "archive", vec![column("id")]); + options.mysql_engine = Some("MyISAM".to_string()); + options.table_comment = Some("remote archive".to_string()); + + let result = build_create_table_sql(options); + + assert!(result.warnings.is_empty()); + assert_eq!( + result.statements[0], + "CREATE TABLE `archive` (\n `id` varchar(255)\n) ENGINE = MyISAM COMMENT = 'remote archive';" + ); +} + +#[test] +fn mysql_table_engine_rejects_non_mysql_and_unsafe_values() { + let mut postgres = structure_change_options(DatabaseType::Postgres, Some("public"), "users", Vec::new()); + postgres.mysql_engine = Some("InnoDB".to_string()); + let postgres_result = build_table_structure_change_sql(postgres); + assert!(postgres_result.statements.is_empty()); + assert_eq!( + postgres_result.warnings, + vec!["Changing the table engine is supported only for native MySQL connections."] + ); + + let mut mysql = structure_change_options(DatabaseType::Mysql, None, "users", Vec::new()); + mysql.mysql_engine = Some("InnoDB; DROP TABLE users".to_string()); + let mysql_result = build_table_structure_change_sql(mysql); + assert!(mysql_result.statements.is_empty()); + assert_eq!(mysql_result.warnings, vec!["MySQL table engine contains invalid characters."]); +} + fn index(name: &str, columns: &[&str]) -> EditableStructureIndex { EditableStructureIndex { id: name.to_string(), @@ -120,6 +165,7 @@ fn index_change_options( triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, } @@ -197,6 +243,7 @@ fn builds_mysql_column_and_index_changes() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -412,6 +459,7 @@ fn builds_xugu_type_change_with_native_syntax() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -621,6 +669,7 @@ fn builds_mysql_unsigned_integer_column_with_length_before_attribute() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -655,6 +704,7 @@ fn doris_table_editor_renames_column_without_mysql_change_syntax() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -713,6 +763,7 @@ fn dameng_integer_column_omits_mysql_display_width() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -754,6 +805,7 @@ fn builds_highgo_foreign_key_changes_with_postgres_syntax() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -824,6 +876,7 @@ fn builds_informix_column_and_index_changes() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -879,6 +932,7 @@ fn oracle_does_not_generate_drop_sql_for_all_columns() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -939,6 +993,7 @@ fn oracle_create_table_preserves_character_length_units() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1027,6 +1082,7 @@ fn iris_drop_index_includes_table_name() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1067,6 +1123,7 @@ fn iris_ignores_comment_changes_but_keeps_supported_column_alters() { triggers: Vec::new(), table_comment: Some("new table description".to_string()), original_table_comment: Some("old table description".to_string()), + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1147,6 +1204,7 @@ fn oracle_compatible_databases_keep_comment_on_sql() { triggers: Vec::new(), table_comment: Some("new table description".to_string()), original_table_comment: Some("old table description".to_string()), + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1180,6 +1238,7 @@ fn mysql_create_index_with_comment() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1212,6 +1271,7 @@ fn manticoresearch_builds_create_table_sql_only() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1254,6 +1314,7 @@ fn manticoresearch_builds_add_and_drop_column_sql() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1319,6 +1380,7 @@ fn gbase8a_uses_limited_mysql_ddl() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1388,6 +1450,7 @@ fn gbase8a_allows_mysql_style_column_reorder() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1422,6 +1485,7 @@ fn manticoresearch_does_not_drop_id_column() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1489,6 +1553,7 @@ fn manticoresearch_warns_when_existing_column_properties_change() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1523,6 +1588,7 @@ fn manticoresearch_ignores_mysql_column_options() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1560,6 +1626,7 @@ fn manticoresearch_builds_text_column_properties() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1589,6 +1656,7 @@ fn manticoresearch_builds_json_secondary_index_property() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1614,6 +1682,7 @@ fn mysql_create_unique_index_with_comment_and_btree() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1641,6 +1710,7 @@ fn mysql_create_functional_index_preserves_key_part_syntax() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1668,6 +1738,7 @@ fn mysql_add_timestamp_column_drops_invalid_precision() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1695,6 +1766,7 @@ fn mysql_add_timestamp_column_preserves_valid_precision() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1729,6 +1801,7 @@ fn builds_postgres_create_table_with_comments_and_index() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1760,6 +1833,7 @@ fn quotes_expression_like_new_index_columns_without_provenance() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1831,6 +1905,7 @@ fn create_table_trims_table_name_whitespace_for_all_statements() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1867,6 +1942,7 @@ fn warns_for_sqlite_unsafe_column_changes() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1907,6 +1983,7 @@ fn qualifies_attached_sqlite_table_and_index_changes() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -1975,6 +2052,7 @@ fn builds_rqlite_changes_with_sqlite_dialect() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -2004,6 +2082,7 @@ fn builds_kingbase_add_column_without_column_keyword() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -2068,6 +2147,7 @@ fn builds_mysql_column_reorder_statements() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -2123,6 +2203,7 @@ fn mysql_add_column_before_existing_column_does_not_reorder_shifted_column() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -2185,6 +2266,7 @@ fn mysql_existing_column_reorder_does_not_reorder_columns_shifted_by_prior_move( triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -2259,6 +2341,7 @@ fn mysql_moving_first_column_to_end_uses_single_reorder_statement() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -2283,6 +2366,7 @@ fn builds_sql_server_quoted_column_and_index_statements() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -2313,6 +2397,7 @@ fn sqlserver_strips_mysql_display_width_from_fixed_integer_types() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -2337,6 +2422,7 @@ fn sqlserver_strips_scale_from_float() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -2361,6 +2447,7 @@ fn sqlserver_preserves_float_mantissa_bits() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -2410,6 +2497,7 @@ fn sqlserver_default_changes_drop_old_constraints_with_isolated_batches() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -2592,6 +2680,7 @@ fn sqlserver_unchanged_foreign_key_does_not_warn_when_saving_other_changes() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -2621,6 +2710,7 @@ fn sqlserver_add_column_with_identity() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -2650,6 +2740,7 @@ fn dameng_add_column_with_identity() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -2678,6 +2769,7 @@ fn dameng_rejects_identity_on_incompatible_type() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -2710,6 +2802,7 @@ fn sqlserver_rejects_identity_on_incompatible_type() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -2745,6 +2838,7 @@ fn sqlserver_changed_foreign_key_still_warns_as_unsupported() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -2785,6 +2879,7 @@ fn sqlserver_unchanged_identity_extra_does_not_mark_existing_column_changed() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -2825,6 +2920,7 @@ fn dameng_unchanged_identity_extra_does_not_mark_existing_column_changed() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -3078,6 +3174,7 @@ fn dameng_rejects_adding_second_identity_column() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -3130,6 +3227,7 @@ fn sqlserver_existing_column_identity_change_warns_without_unchanged_foreign_key triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -3161,6 +3259,7 @@ fn builds_duckdb_create_table_statements() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -3206,6 +3305,7 @@ fn builds_clickhouse_nullable_comment_and_reorder_statements() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -3252,6 +3352,7 @@ fn builds_h2_schema_qualified_existing_column_statements() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4060,6 +4161,7 @@ fn mysql_create_table_with_auto_increment() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4087,6 +4189,7 @@ fn mysql_create_table_keeps_column_charset_collation_and_comment() { triggers: Vec::new(), table_comment: Some("User accounts".to_string()), original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4118,6 +4221,7 @@ fn mysql_compatible_databases_do_not_emit_mysql_column_charset_clauses() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4146,6 +4250,7 @@ fn mysql_create_table_with_on_update_current_timestamp() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4174,6 +4279,7 @@ fn postgres_create_table_with_identity() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4204,6 +4310,7 @@ fn dameng_create_table_with_identity() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4230,6 +4337,7 @@ fn dameng_create_table_preserves_character_length_units() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4264,6 +4372,7 @@ fn dameng_alter_column_preserves_character_length_unit() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4299,6 +4408,7 @@ fn dameng_rejects_multiple_identity_columns() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4327,6 +4437,7 @@ fn dameng_rejects_zero_identity_increment() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4356,6 +4467,7 @@ fn sqlserver_create_table_with_identity() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4380,6 +4492,7 @@ fn mysql_quotes_datetime_literal_default() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4404,6 +4517,7 @@ fn mysql_does_not_quote_current_timestamp() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4429,6 +4543,7 @@ fn mysql_does_not_quote_temporal_function_with_parens() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4453,6 +4568,7 @@ fn mysql_date_literal_default_is_quoted() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4477,6 +4593,7 @@ fn mysql_time_literal_default_is_quoted() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4501,6 +4618,7 @@ fn non_temporal_types_are_not_quoted() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4626,6 +4744,7 @@ fn builds_mysql_foreign_key_changes() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4655,6 +4774,7 @@ fn builds_mysql_composite_foreign_key() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4687,6 +4807,7 @@ fn builds_oracle_foreign_key_with_supported_actions() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4723,6 +4844,7 @@ fn builds_oracle_foreign_key_replacement() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4757,6 +4879,7 @@ fn builds_mysql_trigger_changes() { triggers: vec![existing], table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4802,6 +4925,7 @@ fn unchanged_postgres_trigger_does_not_block_column_rename() { triggers: vec![existing], table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4830,6 +4954,7 @@ fn changed_postgres_trigger_remains_unsupported() { triggers: vec![existing], table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4863,6 +4988,7 @@ fn rejects_editing_existing_oracle_trigger_without_complete_source() { triggers: vec![existing], table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4886,6 +5012,7 @@ fn builds_oracle_statement_trigger_without_row_clause() { triggers: vec![trigger("ORDERS_AUDIT", "BEFORE STATEMENT", "UPDATE OF STATUS", "BEGIN\n NULL;\nEND;\n/")], table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4920,6 +5047,7 @@ fn drops_existing_oracle_trigger_without_reconstructing_it() { triggers: vec![existing], table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4940,6 +5068,7 @@ fn rejects_unsupported_oracle_compound_trigger_shape() { triggers: vec![trigger("ORDERS_CT", "COMPOUND", "UPDATE", "BEGIN\n NULL;\nEND;")], table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4964,6 +5093,7 @@ fn mysql_varchar_default_is_quoted() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -4989,6 +5119,7 @@ fn mysql_char_default_is_quoted() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -5013,6 +5144,7 @@ fn mysql_text_default_is_quoted() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -5037,6 +5169,7 @@ fn mysql_enum_default_is_quoted() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -5061,6 +5194,7 @@ fn mysql_int_default_is_not_quoted() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -5191,6 +5325,7 @@ fn mysql_character_column_add_with_charset_collation() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -5223,6 +5358,7 @@ fn mysql_numeric_column_omits_charset_collation_in_column_definition() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -5265,6 +5401,7 @@ fn mysql_numeric_column_ignores_charset_collation_in_change_detection() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -5302,6 +5439,7 @@ fn mysql_character_column_detects_charset_collation_change() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -5344,6 +5482,7 @@ fn mysql_character_column_preserves_charset_collation_on_other_change() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -5491,6 +5630,7 @@ fn oscar_create_table_with_primary_key_and_comments() { triggers: Vec::new(), table_comment: Some("user table".to_string()), original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -5683,6 +5823,7 @@ fn oscar_drop_index_with_schema_qualifier() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -5703,6 +5844,7 @@ fn oscar_table_comment_uses_comment_on_table() { triggers: Vec::new(), table_comment: Some("new comment".to_string()), original_table_comment: Some("old comment".to_string()), + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -5785,6 +5927,7 @@ fn postgres_partitioned_parent_concurrent_request_rejected() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: true, is_gaussdb_m_mode: false, }); @@ -5815,6 +5958,7 @@ fn postgres_partitioned_parent_plain_index_unchanged() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: true, is_gaussdb_m_mode: false, }); @@ -5857,6 +6001,7 @@ fn postgres_create_table_partitioned_concurrent_request_rejected() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: true, is_gaussdb_m_mode: false, }); @@ -5983,6 +6128,7 @@ fn postgres_create_table_concurrent_index() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -6098,6 +6244,7 @@ fn gaussdb_m_options(columns: Vec) -> TableStructureSql triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: true, } @@ -6328,6 +6475,7 @@ fn gaussdb_m_rebuild_index_unchanged_type_does_not_rebuild() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: true, }; diff --git a/crates/dbx-core/src/table_structure_sql/types.rs b/crates/dbx-core/src/table_structure_sql/types.rs index e83097b0ca..75bdef6d8d 100644 --- a/crates/dbx-core/src/table_structure_sql/types.rs +++ b/crates/dbx-core/src/table_structure_sql/types.rs @@ -210,6 +210,8 @@ pub struct TableStructureSqlOptions { pub table_comment: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub original_table_comment: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mysql_engine: Option, /// Whether the target table is a partitioned parent table (PostgreSQL /// `relkind = 'p'`). PostgreSQL rejects `CREATE INDEX CONCURRENTLY` on /// partitioned parents, so the builder refuses such a request up front diff --git a/crates/dbx-core/tests/live_mysql_generated_columns.rs b/crates/dbx-core/tests/live_mysql_generated_columns.rs index 1897afb61f..642f9d7af8 100644 --- a/crates/dbx-core/tests/live_mysql_generated_columns.rs +++ b/crates/dbx-core/tests/live_mysql_generated_columns.rs @@ -46,6 +46,7 @@ fn change_options(table_name: &str, columns: Vec) -> Ta triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, } diff --git a/crates/dbx-core/tests/live_postgres_concurrent_index.rs b/crates/dbx-core/tests/live_postgres_concurrent_index.rs index 6ef1157dde..438d548738 100644 --- a/crates/dbx-core/tests/live_postgres_concurrent_index.rs +++ b/crates/dbx-core/tests/live_postgres_concurrent_index.rs @@ -59,6 +59,7 @@ async fn live_postgres_concurrent_index_builds_valid_index() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, }); @@ -189,6 +190,7 @@ async fn live_postgres_partitioned_parent_concurrent_request_rejected() { triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: true, is_gaussdb_m_mode: false, }); diff --git a/crates/dbx-core/tests/live_sqlserver_completion.rs b/crates/dbx-core/tests/live_sqlserver_completion.rs index b5f03431c0..bde38cf7a2 100644 --- a/crates/dbx-core/tests/live_sqlserver_completion.rs +++ b/crates/dbx-core/tests/live_sqlserver_completion.rs @@ -1006,6 +1006,7 @@ async fn live_sqlserver_table_structure_default_changes_drop_existing_constraint triggers: Vec::new(), table_comment: None, original_table_comment: None, + mysql_engine: None, partitioned: false, is_gaussdb_m_mode: false, });