fix(doris): use Doris column rename syntax

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

View File

@ -54,13 +54,13 @@ async function copy() {
</div>
<!-- centered: 居中占满 -->
<div v-else class="flex-1 flex flex-col items-center justify-center gap-2 px-6 text-center">
<div v-else class="flex-1 min-h-0 flex flex-col items-center justify-center gap-2 px-6 py-4 text-center">
<TriangleAlert class="h-8 w-8 text-destructive/50" aria-hidden="true" />
<div class="space-y-1 select-text text-destructive" @mousedown.stop @click.stop>
<div class="min-h-0 max-h-48 max-w-lg overflow-auto space-y-1 select-text text-destructive" @mousedown.stop @click.stop>
<div class="text-sm font-medium">{{ displayTitle }}</div>
<div class="text-xs max-w-lg break-all cursor-text text-destructive/80 select-text">{{ message }}</div>
<div class="text-xs break-all cursor-text text-destructive/80 select-text">{{ message }}</div>
</div>
<div class="flex flex-wrap items-center justify-center gap-2 text-foreground">
<div class="shrink-0 flex flex-wrap items-center justify-center gap-2 text-foreground">
<Button variant="outline" size="sm" class="h-7 gap-1.5 px-2 text-xs" @click.stop="copy">
<Copy class="h-3.5 w-3.5" />
{{ t("grid.copy") }}

View File

@ -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();

View File

@ -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(" ")

View File

@ -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))

View File

@ -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")]

View File

@ -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");

View File

@ -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"