release: simcu::orm 1.2.1 — README 清理(去除 EF 对照、补充 @Index 索引注解)、迁移生成索引说明、包描述与版本更新

This commit is contained in:
2026-08-23 06:45:04 +08:00
parent 9d7b353e2f
commit 270a01fd49
10 changed files with 175 additions and 37 deletions
+104 -2
View File
@@ -13,7 +13,8 @@
* let m1 = gen.ensure("20250801000000_AddAge", "新增 age 列", "snapshot.json", models1)
* migrator.migrate([m1])
*
* 限制(v1):不支持主键/自增属性变更(抛异常提示手写迁移);不生成索引(模型无索引注解)。
* 索引:模型字段用 @Index 标注即生成 CREATE [UNIQUE] INDEX(down 自动 DROP INDEX)。
* 限制(v1):不支持主键/自增属性变更(抛异常提示手写迁移)。
*/
package simcu::orm.migrations
@@ -36,6 +37,13 @@ public class MigrationGenerator {
for (model in models) {
upOps.add(createTableOp(model))
downOps.add(dropTableOp(model.tableName))
// 索引随建表一并创建:down 先 DROP INDEX 再 DROP TABLE(setPresets 已逆序处理)
for (op in createIndexOpsFor(model)) {
upOps.add(op)
}
for (op in dropIndexOpsFor(model)) {
downOps.add(op)
}
}
setPresets(m, upOps, downOps)
m
@@ -50,11 +58,17 @@ public class MigrationGenerator {
let oldByName = indexByTable(oldModels)
let newByName = indexByTable(newModels)
// 1. 新增表 → CreateTable
// 1. 新增表 → CreateTable(含索引)
for (n in newModels) {
if (!oldByName.contains(n.tableName)) {
upOps.add(createTableOp(n))
downOps.add(dropTableOp(n.tableName))
for (op in createIndexOpsFor(n)) {
upOps.add(op)
}
for (op in dropIndexOpsFor(n)) {
downOps.add(op)
}
}
}
// 2. 已有表 → 列增删改
@@ -196,6 +210,78 @@ public class MigrationGenerator {
}
}
}
// 索引增删改
diffIndexes(upOps, downOps, old, cur)
}
/// 索引 diff:按 indexName 匹配,处理新增/删除/变更(变更 = up 先 DROP 再 CREATE,down 反之)
private static func diffIndexes(upOps: ArrayList<MigrationOperation>, downOps: ArrayList<MigrationOperation>,
old: EntityModel, cur: EntityModel): Unit {
var oldIdx = HashMap<String, PropertyModel>()
for (p in old.properties) {
if (p.isIndex) {
oldIdx[p.indexName] = p
}
}
var curIdx = HashMap<String, PropertyModel>()
for (p in cur.properties) {
if (p.isIndex) {
curIdx[p.indexName] = p
}
}
// 新增 / 变更
for (p in cur.properties) {
if (!p.isIndex) {
continue
}
if (let Some(op) <- oldIdx.get(p.indexName)) {
if (!sameIndex(op, p)) {
upOps.add(dropIndexOp(cur.tableName, p.indexName))
downOps.add(createIndexOp(old.tableName, op))
upOps.add(createIndexOp(cur.tableName, p))
downOps.add(dropIndexOp(old.tableName, p.indexName))
}
} else {
upOps.add(createIndexOp(cur.tableName, p))
downOps.add(dropIndexOp(cur.tableName, p.indexName))
}
}
// 删除
for (p in old.properties) {
if (!p.isIndex) {
continue
}
if (!curIdx.contains(p.indexName)) {
upOps.add(dropIndexOp(old.tableName, p.indexName))
downOps.add(createIndexOp(old.tableName, p))
}
}
}
private static func sameIndex(a: PropertyModel, b: PropertyModel): Bool {
a.columnName == b.columnName && a.indexUnique == b.indexUnique
}
/// 该模型所有索引列 → CREATE INDEX op(up 用)
private static func createIndexOpsFor(model: EntityModel): ArrayList<MigrationOperation> {
let ops = ArrayList<MigrationOperation>()
for (p in model.properties) {
if (p.isIndex) {
ops.add(createIndexOp(model.tableName, p))
}
}
ops
}
/// 该模型所有索引列 → DROP INDEX op(down 用,与 createIndexOpsFor 同序)
private static func dropIndexOpsFor(model: EntityModel): ArrayList<MigrationOperation> {
let ops = ArrayList<MigrationOperation>()
for (p in model.properties) {
if (p.isIndex) {
ops.add(dropIndexOp(model.tableName, p.indexName))
}
}
ops
}
private static func sameColumn(a: PropertyModel, b: PropertyModel): Bool {
@@ -275,4 +361,20 @@ public class MigrationGenerator {
op.column = Some(columnDefinition(p))
op
}
private static func createIndexOp(table: String, p: PropertyModel): MigrationOperation {
let op = MigrationOperation(MigrationOperationKind.CreateIndex)
op.tableName = table
op.indexName = p.indexName
op.unique = p.indexUnique
op.columnNames.add(p.columnName)
op
}
private static func dropIndexOp(table: String, indexName: String): MigrationOperation {
let op = MigrationOperation(MigrationOperationKind.DropIndex)
op.tableName = table
op.indexName = indexName
op
}
}
+1 -1
View File
@@ -381,7 +381,7 @@ public class DdlFactory {
if (op.unique) {
sb.append("UNIQUE ")
}
sb.append("INDEX ${dialect.quoteName(op.indexName)} ON ${dialect.quoteName(op.tableName)} (")
sb.append("INDEX IF NOT EXISTS ${dialect.quoteName(op.indexName)} ON ${dialect.quoteName(op.tableName)} (")
var first = true
for (c in op.columnNames) {
if (!first) {
+14 -4
View File
@@ -10,10 +10,11 @@
* // 模型变更后:快照存在 → 载入旧模型 diff,并覆盖保存新快照
* gen.ensure("20250801000000_AddAge", "新增 age 列", "snapshot.json", models1)
*
* 快照 JSON 结构(版本 2,较 v1 新增 enum 标记):
* {"version":2,"models":[{"table":"users","columns":[
* 快照 JSON 结构(版本 3,较 v2 新增索引标记 idx/ixName/ixUnique):
* {"version":3,"models":[{"table":"users","columns":[
* {"name":"id","column":"id","type":"Int64","key":true,"auto":true,
* "client":false,"required":false,"maxLen":0,"enum":false}]}]}
* "client":false,"required":false,"maxLen":0,"enum":false,
* "idx":false,"ixName":"","ixUnique":false}]}]}
*/
package simcu::orm.migrations
@@ -30,7 +31,7 @@ public class ModelSnapshot {
/// 快照内的轻量模型列表
public var models: ArrayList<EntityModel> = ArrayList()
/// 快照格式版本
public static let formatVersion: Int64 = 2
public static let formatVersion: Int64 = 3
public init() {}
@@ -51,6 +52,9 @@ public class ModelSnapshot {
lp.maxLength = p.maxLength
lp.typeNameOverride = p.typeName()
lp.isEnum = p.isEnum
lp.isIndex = p.isIndex
lp.indexName = p.indexName
lp.indexUnique = p.indexUnique
lm.properties.add(lp)
}
if (let Some(kp) <- m.keyProperty) {
@@ -90,6 +94,9 @@ public class ModelSnapshot {
co["required"] = JBool(p.isRequired)
co["maxLen"] = JNumber("${p.maxLength}")
co["enum"] = JBool(p.isEnum)
co["idx"] = JBool(p.isIndex)
co["ixName"] = JString(p.indexName)
co["ixUnique"] = JBool(p.indexUnique)
cols.add(JObject(co))
}
mo["columns"] = JArray(cols)
@@ -126,6 +133,9 @@ public class ModelSnapshot {
lp.isRequired = getBool(co, "required")
lp.maxLength = getLong(co, "maxLen")
lp.isEnum = getBool(co, "enum")
lp.isIndex = getBool(co, "idx")
lp.indexName = getString(co, "ixName")
lp.indexUnique = getBool(co, "ixUnique")
if (lp.isKey) {
lm.keyProperty = Some(lp)
}