fix(table-structure): quote default values for string column types
This commit is contained in:
parent
268ee99328
commit
48af146b95
|
|
@ -65,7 +65,7 @@ fn builds_mysql_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.default_value = "guest".to_string();
|
||||
renamed.comment = "Shown name".to_string();
|
||||
renamed.original = Some(ColumnInfo {
|
||||
name: "name".to_string(),
|
||||
|
|
@ -122,7 +122,7 @@ 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.default_value = "guest".to_string();
|
||||
renamed.original = Some(ColumnInfo {
|
||||
name: "name".to_string(),
|
||||
data_type: "varchar(80)".to_string(),
|
||||
|
|
@ -887,7 +887,7 @@ fn builds_h2_schema_qualified_existing_column_statements() {
|
|||
name.id = "name".to_string();
|
||||
name.data_type = "VARCHAR(120)".to_string();
|
||||
name.is_nullable = false;
|
||||
name.default_value = "'guest'".to_string();
|
||||
name.default_value = "guest".to_string();
|
||||
name.comment = "Display name".to_string();
|
||||
name.original = Some(ColumnInfo {
|
||||
name: "NAME".to_string(),
|
||||
|
|
@ -1496,3 +1496,140 @@ fn builds_mysql_trigger_changes() {
|
|||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mysql_varchar_default_is_quoted() {
|
||||
let mut col = column("name");
|
||||
col.data_type = "varchar(255)".to_string();
|
||||
col.default_value = "hello".to_string();
|
||||
|
||||
let result = build_create_table_sql(TableStructureSqlOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
schema: None,
|
||||
table_name: "users".to_string(),
|
||||
columns: vec![col],
|
||||
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!(result.statements[0].contains("DEFAULT 'hello'"));
|
||||
assert!(!result.statements[0].contains("DEFAULT hello "));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mysql_char_default_is_quoted() {
|
||||
let mut col = column("code");
|
||||
col.data_type = "char(10)".to_string();
|
||||
col.default_value = "abc".to_string();
|
||||
|
||||
let result = build_create_table_sql(TableStructureSqlOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
schema: None,
|
||||
table_name: "items".to_string(),
|
||||
columns: vec![col],
|
||||
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!(result.statements[0].contains("DEFAULT 'abc'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mysql_text_default_is_quoted() {
|
||||
let mut col = column("description");
|
||||
col.data_type = "text".to_string();
|
||||
col.default_value = "default value".to_string();
|
||||
|
||||
let result = build_create_table_sql(TableStructureSqlOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
schema: None,
|
||||
table_name: "products".to_string(),
|
||||
columns: vec![col],
|
||||
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!(result.statements[0].contains("DEFAULT 'default value'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mysql_enum_default_is_quoted() {
|
||||
let mut col = column("status");
|
||||
col.data_type = "enum('active','inactive')".to_string();
|
||||
col.default_value = "active".to_string();
|
||||
|
||||
let result = build_create_table_sql(TableStructureSqlOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
schema: None,
|
||||
table_name: "users".to_string(),
|
||||
columns: vec![col],
|
||||
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!(result.statements[0].contains("DEFAULT 'active'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mysql_int_default_is_not_quoted() {
|
||||
let mut col = column("score");
|
||||
col.data_type = "int".to_string();
|
||||
col.default_value = "100".to_string();
|
||||
|
||||
let result = build_create_table_sql(TableStructureSqlOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
schema: None,
|
||||
table_name: "games".to_string(),
|
||||
columns: vec![col],
|
||||
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!(result.statements[0].contains("DEFAULT 100"));
|
||||
assert!(!result.statements[0].contains("DEFAULT '100'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn postgres_varchar_default_is_quoted() {
|
||||
let mut col = column("label");
|
||||
col.data_type = "varchar(100)".to_string();
|
||||
col.default_value = "test label".to_string();
|
||||
col.original = Some(ColumnInfo {
|
||||
name: "label".to_string(),
|
||||
data_type: "varchar(100)".to_string(),
|
||||
is_nullable: true,
|
||||
column_default: None,
|
||||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: Some(String::new()),
|
||||
});
|
||||
|
||||
let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions {
|
||||
database_type: Some(DatabaseType::Postgres),
|
||||
schema: None,
|
||||
table_name: "items".to_string(),
|
||||
column: col,
|
||||
});
|
||||
|
||||
assert!(result.statements.iter().any(|s| s.contains("SET DEFAULT 'test label'")));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,16 +114,109 @@ pub(super) fn is_temporal_expression(value: &str) -> bool {
|
|||
trimmed.chars().all(|c| c.is_ascii_alphabetic() || c == '_')
|
||||
}
|
||||
|
||||
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!(
|
||||
normalized.as_str(),
|
||||
"char"
|
||||
| "varchar"
|
||||
| "tinytext"
|
||||
| "text"
|
||||
| "mediumtext"
|
||||
| "longtext"
|
||||
| "binary"
|
||||
| "varbinary"
|
||||
| "tinyblob"
|
||||
| "blob"
|
||||
| "mediumblob"
|
||||
| "longblob"
|
||||
| "enum"
|
||||
| "set"
|
||||
| "json"
|
||||
| "nvarchar"
|
||||
| "nchar"
|
||||
| "long"
|
||||
),
|
||||
StructureDialect::Postgres => matches!(
|
||||
normalized.as_str(),
|
||||
"char"
|
||||
| "character"
|
||||
| "varchar"
|
||||
| "character varying"
|
||||
| "text"
|
||||
| "bpchar"
|
||||
| "name"
|
||||
| "json"
|
||||
| "jsonb"
|
||||
| "xml"
|
||||
| "bytea"
|
||||
| "uuid"
|
||||
),
|
||||
StructureDialect::SqlServer => matches!(
|
||||
normalized.as_str(),
|
||||
"char" | "varchar" | "nchar" | "nvarchar" | "text" | "ntext" | "xml" | "uniqueidentifier" | "sysname"
|
||||
),
|
||||
StructureDialect::Oracle => matches!(
|
||||
normalized.as_str(),
|
||||
"char" | "nchar" | "varchar2" | "nvarchar2" | "clob" | "nclob" | "long" | "raw" | "long raw" | "bfile"
|
||||
),
|
||||
StructureDialect::H2 => matches!(
|
||||
normalized.as_str(),
|
||||
"char"
|
||||
| "character"
|
||||
| "varchar"
|
||||
| "character varying"
|
||||
| "text"
|
||||
| "clob"
|
||||
| "binary"
|
||||
| "varbinary"
|
||||
| "blob"
|
||||
| "uuid"
|
||||
| "json"
|
||||
),
|
||||
StructureDialect::ClickHouse => matches!(normalized.as_str(), "string" | "fixedstring" | "uuid" | "json"),
|
||||
StructureDialect::Sqlite => {
|
||||
matches!(normalized.as_str(), "text" | "varchar" | "char" | "character" | "clob" | "nvarchar" | "nchar")
|
||||
}
|
||||
StructureDialect::Informix => matches!(
|
||||
normalized.as_str(),
|
||||
"char"
|
||||
| "character"
|
||||
| "varchar"
|
||||
| "character varying"
|
||||
| "nvarchar"
|
||||
| "nchar"
|
||||
| "text"
|
||||
| "clob"
|
||||
| "lvarchar"
|
||||
| "byte"
|
||||
| "blob"
|
||||
),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn format_default_for_sql(dialect: StructureDialect, data_type: &str, default_value: &str) -> String {
|
||||
if default_value.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
let base_type = data_type.split('(').next().unwrap_or(data_type).trim();
|
||||
if is_temporal_type_for_default(dialect, base_type) && !is_temporal_expression(default_value) {
|
||||
quote_string(default_value)
|
||||
} else {
|
||||
default_value.to_string()
|
||||
if is_temporal_type_for_default(dialect, base_type) {
|
||||
if is_temporal_expression(default_value) {
|
||||
return default_value.to_string();
|
||||
}
|
||||
return quote_string(default_value);
|
||||
}
|
||||
if is_string_type_for_default(dialect, base_type) {
|
||||
// Only skip quoting for function-call expressions like `gen_random_uuid()`.
|
||||
// Simple identifiers like `CURRENT_TIMESTAMP` are not valid defaults for string columns.
|
||||
if default_value.contains('(') || default_value.contains(')') {
|
||||
return default_value.to_string();
|
||||
}
|
||||
return quote_string(default_value);
|
||||
}
|
||||
default_value.to_string()
|
||||
}
|
||||
|
||||
pub(super) fn normalize_default(value: Option<&String>) -> String {
|
||||
|
|
|
|||
Loading…
Reference in New Issue