fix(mysql): show inherited charset and collation in structure editor

This commit is contained in:
Melody Ruch authored and GitHub committed 2026-09-04 09:48:41 +08:00
1 parent 48410b70b4
commit 06dcd0013c
16 files changed
+562 -91

No files matched your search

@@ -1,7 +1,41 @@
use super::dialect::{is_oracle_like, StructureDialect};
use super::types::EditableStructureColumn;
use super::types::{EditableStructureColumn, TableStructureSqlOptions};
use super::util::{clean, format_default_for_sql, normalize_default, quote_ident, quote_string};
/// Drops the `CHARACTER SET` / `COLLATE` inputs of MySQL columns that merely
/// inherit the table's default collation, so the generated DDL does not spell
/// out clauses the server would apply anyway.
///
/// MySQL reports the *effective* collation of every character column and never
/// records whether it was written out explicitly, so "equals the table default"
/// is the only signal available. Omitting the clauses is equivalent to keeping
/// them: a column definition without `CHARACTER SET` takes the table default,
/// which is exactly the value being dropped here. This runs on the DDL inputs
/// only — introspection (`get_columns`) still reports the real values so the
/// structure editor can show the column's current charset and collation.
///
/// The original snapshot is normalized alongside the draft so that an untouched
/// column does not register as a charset change and trigger a needless `MODIFY`.
pub(super) fn strip_inherited_mysql_column_charsets(options: &mut TableStructureSqlOptions) {
let Some(table_collation) = options.table_collation.as_deref().map(str::trim).filter(|value| !value.is_empty())
else {
return;
};
let inherits = |collation: &str| collation.trim().eq_ignore_ascii_case(table_collation);
for column in &mut options.columns {
if inherits(&column.collation) {
column.character_set = String::new();
column.collation = String::new();
}
if let Some(original) = column.original.as_mut() {
if original.collation.as_deref().is_some_and(inherits) {
original.character_set = None;
original.collation = None;
}
}
}
}
pub(super) fn column_definition(dialect: StructureDialect, column: &EditableStructureColumn) -> String {
let data_type = column_data_type(dialect, column);
let mut parts = vec![quote_ident(dialect, &column.name), data_type];
@@ -1,6 +1,6 @@
use super::column_format::{
column_data_type, column_extra_clause, has_dameng_identity, is_dameng_identity_compatible_type,
is_mysql_character_data_type, is_mysql_timestamp_type,
is_mysql_character_data_type, is_mysql_timestamp_type, strip_inherited_mysql_column_charsets,
};
use super::comments::{build_sqlserver_column_comment_sql, build_sqlserver_table_comment_sql};
use super::dialect::{capabilities_for, database_label, StructureDialect};
@@ -26,6 +26,7 @@ pub fn build_create_table_sql(mut options: TableStructureSqlOptions) -> TableStr
dialect = capabilities.dialect;
}
options.table_name = clean(&options.table_name);
strip_inherited_mysql_column_charsets(&mut options);
let mut warnings = Vec::new();
warnings.extend(validate_mysql_engine(&options));
// Fail closed: a concurrent-index request on a partitioned parent (or on an
@@ -1519,6 +1519,7 @@ mod tests {
mysql_engine: None,
partitioned: false,
is_gaussdb_m_mode: false,
table_collation: None,
}
}
File diff suppressed because it is too large. Load diff
@@ -216,6 +216,14 @@ pub struct TableStructureSqlOptions {
pub original_table_comment: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mysql_engine: Option<String>,
/// MySQL only: the table's current default collation
/// (`information_schema.TABLES.TABLE_COLLATION`). A column whose collation
/// merely matches it inherits the table default, so its `CHARACTER SET` /
/// `COLLATE` clauses are redundant and are dropped from the generated DDL.
/// Introspection keeps reporting the column's real values, which is what
/// the structure editor renders in its charset/collation pickers.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub table_collation: Option<String>,
/// 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