fix(gbase): support 8a and 8s profiles

This commit is contained in:
t8y2 committed 2026-06-14 10:16:24 +08:00
1 parent 213162aad3
commit cbc7f9b0db
41 files changed
+596 -50

No files matched your search

@@ -1,4 +1,5 @@
use super::column_format::{clickhouse_column_type, column_data_type, column_definition};
use super::columns::build_drop_column_sql;
use super::comments::build_sqlserver_column_comment_sql;
use super::dialect::{capabilities_for, database_label, StructureDialect};
use super::types::{EditableStructureColumn, SingleColumnAlterSqlOptions, TableStructureSqlResult};
@@ -29,7 +30,7 @@ pub fn build_single_column_alter_sql(options: SingleColumnAlterSqlOptions) -> Ta
warnings.push(format!("Primary key column \"{}\" cannot be dropped from this editor.", original.name));
return TableStructureSqlResult { statements, warnings };
}
statements.push(format!("ALTER TABLE {table} DROP COLUMN {};", quote_ident(dialect, &original.name)));
statements.push(build_drop_column_sql(dialect, &table, &original.name));
return TableStructureSqlResult { statements, warnings };
}
@@ -74,6 +75,7 @@ pub fn build_single_column_alter_sql(options: SingleColumnAlterSqlOptions) -> Ta
StructureDialect::ClickHouse => {
statements.extend(build_clickhouse_existing_column_sql(&table, &options.column, ""))
}
StructureDialect::Informix => statements.extend(build_informix_existing_column_sql(&table, &options.column)),
StructureDialect::SqlServer => statements.extend(build_sqlserver_existing_column_sql(
&table,
&options.column,
@@ -191,6 +193,42 @@ pub(super) fn build_postgres_existing_column_sql(table: &str, column: &EditableS
statements
}
pub(super) fn build_informix_existing_column_sql(table: &str, column: &EditableStructureColumn) -> Vec<String> {
let Some(original) = &column.original else {
return Vec::new();
};
let dialect = StructureDialect::Informix;
let mut statements = Vec::new();
let mut current_name = original.name.clone();
if column.name != original.name {
statements.push(format!(
"RENAME COLUMN {table}.{} TO {};",
quote_ident(dialect, &original.name),
quote_ident(dialect, &column.name)
));
current_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);
if type_changed || nullable_changed || default_changed {
let mut parts = vec![quote_ident(dialect, &current_name), column_data_type(dialect, column)];
if column.is_nullable {
parts.push("NULL".to_string());
} else {
parts.push("NOT NULL".to_string());
}
let default_value = normalize_default(Some(&column.default_value));
if !default_value.is_empty() {
parts.push(format!("DEFAULT {}", format_default_for_sql(dialect, &column.data_type, &default_value)));
} else if default_changed {
parts.push("DEFAULT NULL".to_string());
}
statements.push(format!("ALTER TABLE {table} MODIFY ({});", parts.join(" ")));
}
statements
}
pub(super) fn build_oracle_like_existing_column_sql(
dialect: StructureDialect,
table: &str,
@@ -1,7 +1,8 @@
use super::column_alter::{
build_clickhouse_existing_column_sql, build_h2_existing_column_sql, build_mysql_existing_column_sql,
build_oracle_like_existing_column_sql, build_postgres_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_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_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;
@@ -33,7 +34,7 @@ pub(super) fn build_column_sql(options: &TableStructureSqlOptions, warnings: &mu
warnings.push(format!("Primary key column \"{}\" cannot be dropped from this editor.", original.name));
continue;
}
statements.push(format!("ALTER TABLE {table} DROP COLUMN {};", quote_ident(dialect, &original.name)));
statements.push(build_drop_column_sql(dialect, &table, &original.name));
continue;
}
@@ -106,6 +107,7 @@ pub(super) fn build_column_sql(options: &TableStructureSqlOptions, warnings: &mu
column,
if has_position_change { &position_clause } else { "" },
)),
StructureDialect::Informix => statements.extend(build_informix_existing_column_sql(&table, column)),
StructureDialect::SqlServer => statements.extend(build_sqlserver_existing_column_sql(
&table,
column,
@@ -185,11 +187,13 @@ pub(super) fn build_add_column_sql(
schema: Option<&str>,
table_name: &str,
) -> Vec<String> {
let add_keyword = if dialect == StructureDialect::SqlServer { "ADD" } else { "ADD COLUMN" };
let definition = column_definition(dialect, column);
let mut statements = if dialect == StructureDialect::Oracle {
vec![format!("ALTER TABLE {table} ADD ({definition});")]
} else if dialect == StructureDialect::Informix {
vec![format!("ALTER TABLE {table} ADD ({definition});")]
} else {
let add_keyword = if dialect == StructureDialect::SqlServer { "ADD" } else { "ADD COLUMN" };
vec![format!("ALTER TABLE {table} {add_keyword} {definition}{position_clause};")]
};
if matches!(dialect, StructureDialect::Postgres | StructureDialect::Oracle) && !clean(&column.comment).is_empty() {
@@ -212,6 +216,13 @@ pub(super) fn build_add_column_sql(
statements
}
pub(super) fn build_drop_column_sql(dialect: StructureDialect, table: &str, column_name: &str) -> String {
if dialect == StructureDialect::Informix {
return format!("ALTER TABLE {table} DROP ({});", quote_ident(dialect, column_name));
}
format!("ALTER TABLE {table} DROP COLUMN {};", quote_ident(dialect, column_name))
}
pub(super) fn column_position_clause(
dialect: StructureDialect,
columns: &[&EditableStructureColumn],
@@ -11,6 +11,7 @@ pub(super) enum StructureDialect {
Oracle,
H2,
ClickHouse,
Informix,
Unsupported,
}
@@ -64,8 +65,7 @@ pub(super) fn capabilities_for(database_type: Option<DatabaseType>) -> TableStru
| DatabaseType::StarRocks
| DatabaseType::Goldendb
| DatabaseType::Sundb
| DatabaseType::Databend
| DatabaseType::Gbase,
| DatabaseType::Databend,
) => TableStructureCapabilities {
dialect: StructureDialect::Mysql,
add_column: true,
@@ -82,6 +82,13 @@ pub(super) fn capabilities_for(database_type: Option<DatabaseType>) -> TableStru
alter_primary_key: true,
..base
},
Some(DatabaseType::Gbase) => TableStructureCapabilities {
dialect: StructureDialect::Mysql,
add_column: true,
drop_column: true,
rename_column: true,
..base
},
Some(
DatabaseType::Postgres
| DatabaseType::Gaussdb
@@ -197,6 +204,17 @@ pub(super) fn capabilities_for(database_type: Option<DatabaseType>) -> TableStru
comment: true,
..base
},
Some(DatabaseType::Informix) => 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
},
_ => base,
}
}
@@ -227,6 +245,7 @@ pub(super) fn dialect_label(dialect: StructureDialect) -> String {
StructureDialect::Oracle => "oracle",
StructureDialect::H2 => "h2",
StructureDialect::ClickHouse => "clickhouse",
StructureDialect::Informix => "informix",
StructureDialect::Unsupported => "this database",
}
.to_string()
@@ -243,6 +262,7 @@ pub(super) fn database_type_for_dialect(dialect: StructureDialect) -> Option<Dat
StructureDialect::Oracle => Some(DatabaseType::Oracle),
StructureDialect::H2 => Some(DatabaseType::H2),
StructureDialect::ClickHouse => Some(DatabaseType::ClickHouse),
StructureDialect::Informix => Some(DatabaseType::Informix),
StructureDialect::Unsupported => None,
}
}
@@ -112,7 +112,7 @@ pub(super) fn build_drop_index_sql(
if matches!(dialect, StructureDialect::Mysql | StructureDialect::SqlServer) {
return format!("DROP INDEX {} ON {table};", quote_ident(dialect, index_name));
}
if matches!(dialect, StructureDialect::Postgres | StructureDialect::Oracle)
if matches!(dialect, StructureDialect::Postgres | StructureDialect::Oracle | StructureDialect::Informix)
&& schema.is_some_and(|schema| !schema.trim().is_empty())
{
return format!("DROP INDEX {}.{};", quote_ident(dialect, schema.unwrap()), quote_ident(dialect, index_name));
@@ -117,6 +117,75 @@ fn builds_mysql_column_and_index_changes() {
);
}
#[test]
fn builds_informix_column_and_index_changes() {
let mut renamed = column("display_name");
renamed.data_type = "varchar(120)".to_string();
renamed.is_nullable = false;
renamed.default_value = "'guest'".to_string();
renamed.original = Some(ColumnInfo {
name: "name".to_string(),
data_type: "varchar(80)".to_string(),
is_nullable: true,
column_default: None,
is_primary_key: false,
extra: None,
comment: Some(String::new()),
});
let mut email = column("email");
email.is_nullable = false;
let mut old_col = column("old_col");
old_col.marked_for_drop = true;
old_col.original = Some(ColumnInfo {
name: "old_col".to_string(),
data_type: "varchar(20)".to_string(),
is_nullable: true,
column_default: None,
is_primary_key: false,
extra: None,
comment: None,
});
let mut old_index = index("idx_old", &["name"]);
old_index.marked_for_drop = true;
old_index.original = Some(IndexInfo {
name: "idx_old".to_string(),
columns: vec!["name".to_string()],
is_unique: false,
is_primary: false,
filter: None,
index_type: None,
included_columns: None,
comment: None,
});
let mut email_index = index("uniq_users_email", &["email"]);
email_index.is_unique = true;
let result = build_table_structure_change_sql(TableStructureSqlOptions {
database_type: Some(DatabaseType::Informix),
schema: Some("gbasedbt".to_string()),
table_name: "users".to_string(),
columns: vec![renamed, email, old_col],
indexes: vec![old_index, email_index],
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![
"RENAME COLUMN gbasedbt.users.name TO display_name;",
"ALTER TABLE gbasedbt.users MODIFY (display_name varchar(120) NOT NULL DEFAULT 'guest');",
"ALTER TABLE gbasedbt.users ADD (email varchar(255) NOT NULL);",
"ALTER TABLE gbasedbt.users DROP (old_col);",
"DROP INDEX gbasedbt.idx_old;",
"CREATE UNIQUE INDEX uniq_users_email ON gbasedbt.users (email);",
]
);
}
#[test]
fn mysql_create_index_with_comment() {
let mut col = column("name");
@@ -146,6 +215,69 @@ fn mysql_create_index_with_comment() {
);
}
#[test]
fn gbase8a_uses_limited_mysql_ddl() {
let mut renamed = column("display_email");
renamed.data_type = "varchar(255)".to_string();
renamed.original = Some(ColumnInfo {
name: "email".to_string(),
data_type: "varchar(255)".to_string(),
is_nullable: true,
column_default: None,
is_primary_key: false,
extra: None,
comment: None,
});
let new_col = column("nickname");
let mut old_col = column("old_col");
old_col.marked_for_drop = true;
old_col.original = Some(ColumnInfo {
name: "old_col".to_string(),
data_type: "varchar(20)".to_string(),
is_nullable: true,
column_default: None,
is_primary_key: false,
extra: None,
comment: None,
});
let mut index = index("idx_users_email", &["display_email"]);
index.original = Some(IndexInfo {
name: "idx_users_email".to_string(),
columns: vec!["email".to_string()],
is_unique: false,
is_primary: false,
filter: None,
index_type: None,
included_columns: None,
comment: None,
});
let result = build_table_structure_change_sql(TableStructureSqlOptions {
database_type: Some(DatabaseType::Gbase),
schema: None,
table_name: "users".to_string(),
columns: vec![renamed, new_col, old_col],
indexes: vec![index],
foreign_keys: Vec::new(),
triggers: Vec::new(),
table_comment: None,
original_table_comment: None,
});
assert_eq!(
result.statements,
vec![
"ALTER TABLE `users` CHANGE COLUMN `email` `display_email` varchar(255);",
"ALTER TABLE `users` ADD COLUMN `nickname` varchar(255);",
"ALTER TABLE `users` DROP COLUMN `old_col`;",
]
);
assert_eq!(
result.warnings,
vec!["Editing existing indexes is not supported for gbase from this editor.".to_string()]
);
}
#[test]
fn mysql_create_unique_index_with_comment_and_btree() {
let mut idx = index("uniq_users_email", &["email"]);
@@ -4,7 +4,11 @@ use super::types::EditableStructureColumn;
pub(super) fn qualified_table(dialect: StructureDialect, schema: Option<&str>, table_name: &str) -> String {
if matches!(
dialect,
StructureDialect::Postgres | StructureDialect::Oracle | StructureDialect::SqlServer | StructureDialect::H2
StructureDialect::Postgres
| StructureDialect::Oracle
| StructureDialect::SqlServer
| StructureDialect::H2
| StructureDialect::Informix
) && schema.is_some_and(|schema| !schema.trim().is_empty())
{
return format!("{}.{}", quote_ident(dialect, schema.unwrap()), quote_ident(dialect, table_name));
@@ -16,10 +20,20 @@ pub(super) fn quote_ident(dialect: StructureDialect, name: &str) -> String {
match dialect {
StructureDialect::Mysql => format!("`{}`", name.replace('`', "``")),
StructureDialect::SqlServer => format!("[{}]", name.replace(']', "]]")),
StructureDialect::Informix if is_simple_informix_identifier(name) => name.to_string(),
_ => format!("\"{}\"", name.replace('"', "\"\"")),
}
}
fn is_simple_informix_identifier(name: &str) -> bool {
let mut chars = name.chars();
let Some(first) = chars.next() else {
return false;
};
(first.is_ascii_alphabetic() || first == '_')
&& chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_' || ch == '$')
}
pub(super) fn quote_string(value: &str) -> String {
format!("'{}'", value.replace('\'', "''"))
}
@@ -78,6 +92,7 @@ pub(super) fn is_temporal_type_for_default(dialect: StructureDialect, base_type:
StructureDialect::Sqlite => {
matches!(normalized.as_str(), "date" | "datetime" | "timestamp" | "time")
}
StructureDialect::Informix => matches!(normalized.as_str(), "date" | "datetime"),
_ => false,
}
}