feat: 支持 Option<T> 字段(None↔NULL)、列命名默认原样(Keep)、迁移同名列先删后加,版本 1.1.0
This commit is contained in:
@@ -289,6 +289,77 @@ public class DdlFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析 AddColumn/AlterColumn 为可执行 SQL 列表(兼容"同名列修改数据结构"场景):
|
||||
/// - 库中无同名列 → 单条 ADD COLUMN
|
||||
/// - 有同名列且类型一致 → AddColumn 幂等跳过(空列表);AlterColumn 保留原 SQL(处理非空/长度等变化)
|
||||
/// - 有同名列但类型不同 → 先 DROP COLUMN 再 ADD COLUMN(重建该字段)
|
||||
/// existingType 来自 information_schema.data_type;None 表示列不存在。
|
||||
public func resolveColumnSqls(op: MigrationOperation, dialect: ISqlDialect,
|
||||
existingType: ?String): ArrayList<String> {
|
||||
let result = ArrayList<String>()
|
||||
let col = op.column.getOrThrow()
|
||||
match (existingType) {
|
||||
case None => result.add(toSql(op, dialect))
|
||||
case Some(dbType) =>
|
||||
let target = dialect.columnTypeSql(col.columnType, col.autoIncrement, col.maxLength)
|
||||
if (normalizeTypeName(dbType) == normalizeTypeName(target)) {
|
||||
// 类型一致:AddColumn 视为幂等跳过;AlterColumn 保留原 SQL
|
||||
let isAlter = match (op.kind) {
|
||||
case MigrationOperationKind.AlterColumn => true
|
||||
case _ => false
|
||||
}
|
||||
if (isAlter) {
|
||||
result.add(toSql(op, dialect))
|
||||
}
|
||||
} else {
|
||||
// 类型不同:先删除老字段,再新增新字段
|
||||
let drop = MigrationOperation(MigrationOperationKind.DropColumn)
|
||||
drop.tableName = op.tableName
|
||||
drop.columnName = col.name
|
||||
result.add(toSql(drop, dialect))
|
||||
result.add(toSql(op, dialect))
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// 类型名归一化:方言 SQL 类型 ↔ information_schema.data_type,统一为小写基础类型(忽略长度/精度括号)。
|
||||
public static func normalizeTypeName(t: String): String {
|
||||
let lower = asciiLower(t)
|
||||
let base = if (let Some(i) <- lower.indexOf("(")) { lower[0..i] } else { lower }
|
||||
match (base) {
|
||||
case "bigserial" | "bigint" | "int8" => "bigint"
|
||||
case "serial" | "integer" | "int" | "int4" => "integer"
|
||||
case "smallint" | "int2" => "smallint"
|
||||
case "varchar" | "character varying" => "varchar"
|
||||
case "character" | "char" | "bpchar" => "char"
|
||||
case "text" => "text"
|
||||
case "boolean" | "bool" => "boolean"
|
||||
case "double precision" | "float8" => "double precision"
|
||||
case "real" | "float4" => "real"
|
||||
case "timestamp" | "timestamp without time zone" | "timestamp with time zone" => "timestamp"
|
||||
case "decimal" | "numeric" => "decimal"
|
||||
case "bytea" => "bytea"
|
||||
case _ => base
|
||||
}
|
||||
}
|
||||
|
||||
/// ASCII 大写转小写(A-Z → a-z,其余字符不变;仅依赖切片与 indexOf,避免 std String 缺失 API)
|
||||
private static func asciiLower(s: String): String {
|
||||
let upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
let lower = "abcdefghijklmnopqrstuvwxyz"
|
||||
let sb = StringBuilder()
|
||||
for (i in 0..s.size) {
|
||||
let c = s[i..i + 1]
|
||||
if (let Some(idx) <- upper.indexOf(c)) {
|
||||
sb.append(lower[idx..idx + 1])
|
||||
} else {
|
||||
sb.append(c)
|
||||
}
|
||||
}
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
private func createTableSql(op: MigrationOperation, dialect: ISqlDialect): String {
|
||||
let sb = StringBuilder()
|
||||
sb.append("CREATE TABLE IF NOT EXISTS ${dialect.quoteName(op.tableName)} (\n")
|
||||
|
||||
Reference in New Issue
Block a user