fix(doris): use Doris column rename syntax

This commit is contained in:
t8y2 committed 2026-07-09 15:44:35 +08:00
1 parent b52dcb8623
commit c545d40fc5
7 files changed
+129 -13

No files matched your search

@@ -71,6 +71,7 @@ pub fn build_single_column_alter_sql(options: SingleColumnAlterSqlOptions) -> Ta
match dialect {
StructureDialect::Mysql => statements.extend(build_mysql_existing_column_sql(&table, &options.column, "")),
StructureDialect::Doris => statements.extend(build_doris_existing_column_sql(&table, &options.column, "")),
StructureDialect::Postgres => statements.extend(build_postgres_existing_column_sql(&table, &options.column)),
StructureDialect::Oracle => {
statements.extend(build_oracle_like_existing_column_sql(dialect, &table, &options.column))
@@ -247,6 +248,41 @@ pub(super) fn build_mysql_existing_column_sql(
vec![format!("ALTER TABLE {table} {operation}{position_clause};")]
}
pub(super) fn build_doris_existing_column_sql(
table: &str,
column: &EditableStructureColumn,
position_clause: &str,
) -> Vec<String> {
let Some(original) = &column.original else {
return Vec::new();
};
let mut statements = Vec::new();
let mut current_column = column.clone();
if column.name != original.name {
// Doris follows its own lightweight schema-change grammar: no MySQL CHANGE and no TO keyword.
statements.push(format!(
"ALTER TABLE {table} RENAME COLUMN {} {};",
quote_ident(StructureDialect::Doris, &original.name),
quote_ident(StructureDialect::Doris, &column.name)
));
current_column.name = column.name.clone();
}
let type_changed = column.data_type.trim() != original.data_type.trim();
let nullable_changed = column.is_nullable != original.is_nullable;
let default_changed = normalize_default(Some(&column.default_value)) != original_default(column);
let comment_changed = clean(&column.comment) != original_comment(column);
if type_changed || nullable_changed || default_changed || comment_changed || !position_clause.is_empty() {
statements.push(format!(
"ALTER TABLE {table} MODIFY COLUMN {}{position_clause};",
column_definition(StructureDialect::Doris, &current_column)
));
}
statements
}
pub(super) fn build_postgres_existing_column_sql(table: &str, column: &EditableStructureColumn) -> Vec<String> {
let Some(original) = &column.original else {
return Vec::new();
@@ -25,7 +25,7 @@ pub(super) fn column_definition(dialect: StructureDialect, column: &EditableStru
parts.push("ON UPDATE CURRENT_TIMESTAMP".to_string());
}
}
if dialect == StructureDialect::Mysql && !clean(&column.comment).is_empty() {
if matches!(dialect, StructureDialect::Mysql | StructureDialect::Doris) && !clean(&column.comment).is_empty() {
parts.push(format!("COMMENT {}", quote_string(&clean(&column.comment))));
}
parts.join(" ")
@@ -1,8 +1,8 @@
use super::column_alter::{
build_clickhouse_existing_column_sql, build_h2_existing_column_sql, build_informix_existing_column_sql,
build_mysql_existing_column_sql, build_oracle_like_existing_column_sql, build_postgres_existing_column_sql,
build_questdb_existing_column_sql, build_sqlite_existing_column_sql, build_sqlserver_existing_column_sql,
has_column_extra_change, has_existing_column_attribute_change,
build_clickhouse_existing_column_sql, build_doris_existing_column_sql, build_h2_existing_column_sql,
build_informix_existing_column_sql, build_mysql_existing_column_sql, build_oracle_like_existing_column_sql,
build_postgres_existing_column_sql, build_questdb_existing_column_sql, build_sqlite_existing_column_sql,
build_sqlserver_existing_column_sql, has_column_extra_change, has_existing_column_attribute_change,
};
use super::column_format::column_definition;
use super::comments::build_sqlserver_column_comment_sql;
@@ -142,6 +142,7 @@ pub(super) fn build_column_sql(options: &TableStructureSqlOptions, warnings: &mu
column,
if has_position_change { &position_clause } else { "" },
)),
StructureDialect::Doris => statements.extend(build_doris_existing_column_sql(&table, column, "")),
StructureDialect::Postgres => statements.extend(build_postgres_existing_column_sql(&table, column)),
StructureDialect::Oracle => {
statements.extend(build_oracle_like_existing_column_sql(dialect, &table, column))
@@ -3,6 +3,7 @@ use crate::models::connection::DatabaseType;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum StructureDialect {
Mysql,
Doris,
Postgres,
Sqlite,
#[cfg(feature = "duckdb-bundled")]
@@ -65,7 +66,6 @@ pub(super) fn capabilities_for(database_type: Option<DatabaseType>) -> TableStru
match database_type {
Some(
DatabaseType::Mysql
| DatabaseType::Doris
| DatabaseType::StarRocks
| DatabaseType::Goldendb
| DatabaseType::Sundb
@@ -87,6 +87,15 @@ pub(super) fn capabilities_for(database_type: Option<DatabaseType>) -> TableStru
foreign_key: true,
..base
},
Some(DatabaseType::Doris) => TableStructureCapabilities {
dialect: StructureDialect::Doris,
add_column: true,
drop_column: true,
rename_column: true,
alter_existing_column: true,
comment: true,
..base
},
Some(DatabaseType::Gbase) => TableStructureCapabilities {
dialect: StructureDialect::Mysql,
add_column: true,
@@ -268,6 +277,7 @@ pub(super) fn database_label(database_type: Option<DatabaseType>) -> String {
pub(super) fn dialect_label(dialect: StructureDialect) -> String {
match dialect {
StructureDialect::Mysql => "mysql",
StructureDialect::Doris => "doris",
StructureDialect::Postgres => "postgres",
StructureDialect::Sqlite => "sqlite",
#[cfg(feature = "duckdb-bundled")]
@@ -287,6 +297,7 @@ pub(super) fn dialect_label(dialect: StructureDialect) -> String {
pub(super) fn database_type_for_dialect(dialect: StructureDialect) -> Option<DatabaseType> {
match dialect {
StructureDialect::Mysql => Some(DatabaseType::Mysql),
StructureDialect::Doris => Some(DatabaseType::Doris),
StructureDialect::Postgres => Some(DatabaseType::Postgres),
StructureDialect::Sqlite => Some(DatabaseType::Sqlite),
#[cfg(feature = "duckdb-bundled")]
@@ -138,6 +138,69 @@ fn builds_mysql_unsigned_integer_column_with_length_before_attribute() {
assert_eq!(result.statements, vec!["ALTER TABLE `users` ADD COLUMN `score` int(11) unsigned;"]);
}
#[test]
fn doris_table_editor_renames_column_without_mysql_change_syntax() {
let mut renamed = column("dtp_flag_jt");
renamed.data_type = "int".to_string();
renamed.comment = "Group DTP".to_string();
renamed.original = Some(ColumnInfo {
name: "dtp_flag".to_string(),
data_type: "int".to_string(),
is_nullable: true,
column_default: None,
is_primary_key: false,
extra: None,
comment: Some("Group DTP".to_string()),
});
let result = build_table_structure_change_sql(TableStructureSqlOptions {
database_type: Some(DatabaseType::Doris),
schema: Some("qybiprod".to_string()),
table_name: "dim_prod_sp_vkorg".to_string(),
columns: vec![renamed],
indexes: Vec::new(),
foreign_keys: Vec::new(),
triggers: Vec::new(),
table_comment: None,
original_table_comment: None,
});
assert_eq!(result.warnings, Vec::<String>::new());
assert_eq!(result.statements, vec!["ALTER TABLE `dim_prod_sp_vkorg` RENAME COLUMN `dtp_flag` `dtp_flag_jt`;"]);
}
#[test]
fn doris_single_column_alter_renames_then_modifies_column_definition() {
let mut renamed = column("dtp_flag_jt");
renamed.data_type = "int".to_string();
renamed.comment = "Group DTP".to_string();
renamed.original = Some(ColumnInfo {
name: "dtp_flag".to_string(),
data_type: "int".to_string(),
is_nullable: true,
column_default: None,
is_primary_key: false,
extra: None,
comment: Some("Division DTP".to_string()),
});
let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions {
database_type: Some(DatabaseType::Doris),
schema: Some("qybiprod".to_string()),
table_name: "dim_prod_sp_vkorg".to_string(),
column: renamed,
});
assert_eq!(result.warnings, Vec::<String>::new());
assert_eq!(
result.statements,
vec![
"ALTER TABLE `dim_prod_sp_vkorg` RENAME COLUMN `dtp_flag` `dtp_flag_jt`;",
"ALTER TABLE `dim_prod_sp_vkorg` MODIFY COLUMN `dtp_flag_jt` int COMMENT 'Group DTP';",
]
);
}
#[test]
fn dameng_integer_column_omits_mysql_display_width() {
let mut age = column("age");
@@ -18,7 +18,10 @@ pub(super) fn qualified_table(dialect: StructureDialect, schema: Option<&str>, t
pub(super) fn quote_ident(dialect: StructureDialect, name: &str) -> String {
match dialect {
StructureDialect::Mysql | StructureDialect::ManticoreSearch | StructureDialect::Questdb => {
StructureDialect::Mysql
| StructureDialect::Doris
| StructureDialect::ManticoreSearch
| StructureDialect::Questdb => {
format!("`{}`", name.replace('`', "``"))
}
StructureDialect::SqlServer => format!("[{}]", name.replace(']', "]]")),
@@ -117,7 +120,9 @@ pub(super) fn is_protected_manticore_id_column(dialect: StructureDialect, column
pub(super) fn is_temporal_type_for_default(dialect: StructureDialect, base_type: &str) -> bool {
let normalized = base_type.split_whitespace().collect::<Vec<_>>().join(" ").to_ascii_lowercase();
match dialect {
StructureDialect::Mysql => matches!(normalized.as_str(), "date" | "datetime" | "timestamp" | "time" | "year"),
StructureDialect::Mysql | StructureDialect::Doris => {
matches!(normalized.as_str(), "date" | "datetime" | "timestamp" | "time" | "year")
}
StructureDialect::Postgres => {
matches!(
normalized.as_str(),
@@ -183,7 +188,7 @@ pub(super) fn is_temporal_expression(value: &str) -> bool {
pub(super) fn is_string_type_for_default(dialect: StructureDialect, base_type: &str) -> bool {
let normalized = base_type.split_whitespace().collect::<Vec<_>>().join(" ").to_ascii_lowercase();
match dialect {
StructureDialect::Mysql => matches!(
StructureDialect::Mysql | StructureDialect::Doris => matches!(
normalized.as_str(),
"char"
| "varchar"