feat: 支持 Option<T> 字段(None↔NULL)、列命名默认原样(Keep)、迁移同名列先删后加,版本 1.1.0

This commit is contained in:
2026-08-21 03:41:53 +08:00
parent 132aae865b
commit 394c4a1fb5
14 changed files with 335 additions and 87 deletions
+71
View File
@@ -289,6 +289,77 @@ public class DdlFactory {
}
}
/// 解析 AddColumn/AlterColumn 为可执行 SQL 列表(兼容"同名列修改数据结构"场景):
/// - 库中无同名列 → 单条 ADD COLUMN
/// - 有同名列且类型一致 → AddColumn 幂等跳过(空列表);AlterColumn 保留原 SQL(处理非空/长度等变化)
/// - 有同名列但类型不同 → 先 DROP COLUMN 再 ADD COLUMN(重建该字段)
/// existingType 来自 information_schema.data_typeNone 表示列不存在。
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")