release: simcu::orm 1.2.1 — README 清理(去除 EF 对照、补充 @Index 索引注解)、迁移生成索引说明、包描述与版本更新
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
# SimOrm for Cangjie(simcu::orm)
|
||||
|
||||
> 仓颉版 EF Core 风格 ORM:**基于数据模型(POCO + 注解)的映射、增删改查、数据库迁移**,移植自 C# 项目 [SimApi](https://github.com/SimcuTeam/simapi-net) 的 `EF Core + SimApi` 数据访问思路(`E:\simcu\simapi-net`)。
|
||||
> 灵感来自于 EF Core。
|
||||
|
||||
> 仓颉版 ORM:**基于数据模型(POCO + 注解)的映射、增删改查、数据库迁移**。
|
||||
|
||||
面向多数据库的方言架构:内置 PostgreSQL(`PostgreSqlDialect`)与 openGauss(`OpenGaussDialect`,继承 PG)方言(对接 [opengauss-driver](../opengauss-driver)),可通过实现 `ISqlDialect` 接口接入 sqlite / mysql 等新数据库。包本身零外部依赖。
|
||||
|
||||
@@ -40,7 +42,7 @@ public class User {
|
||||
|
||||
### 2. 定义 DbContext 并增删改查
|
||||
|
||||
用 `@DbContext` 宏(推荐,EF Core 声明式体验):纯声明类即可,宏自动补 `<: DbContext`、把每个 `DbSet<T>` 声明变成 `public prop` 并注入 `this.set<T>()`、生成 `(driverName, connStr)` 构造与 `migrations()` override:
|
||||
用 `@DbContext` 宏(推荐):纯声明类即可,宏自动补 `<: DbContext`、把每个 `DbSet<T>` 声明变成 `public prop` 并注入 `this.set<T>()`、生成 `(driverName, connStr)` 构造与 `migrations()` override:
|
||||
|
||||
```cangjie
|
||||
import std.database.sql.*
|
||||
@@ -122,7 +124,7 @@ main() {
|
||||
|
||||
### 从数据模型生成迁移(MigrationGenerator)
|
||||
|
||||
不用手写 `Migration` 子类,直接从实体模型生成(对齐 EF Core `migrations add`):
|
||||
不用手写 `Migration` 子类,直接从实体模型生成:
|
||||
|
||||
```cangjie
|
||||
import simcu::orm.*
|
||||
@@ -158,7 +160,7 @@ import simcu::orm.migrations.*
|
||||
main() {
|
||||
let gen = MigrationGenerator()
|
||||
// ensure: 快照文件不存在 → 生成初始迁移并保存快照;
|
||||
// 已存在 → 载入旧快照 diff 出新迁移并覆盖保存(对齐 EF Core migrations add)
|
||||
// 已存在 → 载入旧快照 diff 出新迁移并覆盖保存
|
||||
let m = gen.ensure("20250801000000_AddAge", "新增 age 列", "snapshot.json", models())
|
||||
Migrator(datasource).migrate([m])
|
||||
}
|
||||
@@ -173,9 +175,9 @@ main() {
|
||||
- **增量**:`diff(id, desc, oldModels, newModels)` → 新增表 `CreateTable`、删除表 `DropTable`、列增删改(`AddColumn`/`DropColumn`/`AlterColumn`),`down` 自动按 up 逆序反转;
|
||||
- **同名列修改数据结构**:`Migrator` 执行 `AddColumn`/`AlterColumn` 前先查 `information_schema`,若库中已有同名列且类型与目标不一致,则**先 `DROP COLUMN` 再 `ADD COLUMN`**(重建该字段);类型一致时 `AddColumn` 幂等跳过、`AlterColumn` 正常执行(处理非空/长度等变化);
|
||||
- 字段类型 → 列类型映射:`String→TextCol`、`Bool→BoolCol`、`Int8/16/32/64→Tiny/Small/Int/BigIntCol`、`Float32→RealCol`、`Float64→FloatCol`、`DateTime→DateTimeCol`、`Decimal→DecimalCol`、`Array<Byte>→BinaryCol`、`Rune→IntCol`、`Duration→BigIntCol`;`Option<X>` 按内层类型 X 映射且列可空(`None` ↔ `NULL`);
|
||||
- **不支持**(抛异常提示手写迁移):主键列名变更、主键/自增属性变更;模型无索引注解,不生成索引;`@Ignore`/`@Column`/`@Required`/`@MaxLength`/`@AutoIncrement` 均会反映到 DDL。
|
||||
- **不支持**(抛异常提示手写迁移):主键列名变更、主键/自增属性变更;`@Ignore`/`@Column`/`@Required`/`@MaxLength`/`@AutoIncrement`/`@Index` 均会反映到 DDL(`@Index` 生成 `CREATE INDEX`)。
|
||||
|
||||
### 迁移 CLI(对齐 dotnet ef)
|
||||
### 迁移 CLI
|
||||
|
||||
`orm-cj` 附带迁移 CLI(`simcu::orm.cli.MigrationCli`),把迁移落成 **.cj 文件** + **快照 JSON**,应用内嵌运行,无需安装外部工具。
|
||||
|
||||
@@ -294,19 +296,20 @@ orm-cj/
|
||||
|
||||
## 模块说明
|
||||
|
||||
### 1. 注解(对齐 .NET EF Core 数据注解)
|
||||
### 1. 注解
|
||||
|
||||
| 注解 | 对齐 .NET | 位置 | 说明 |
|
||||
|------|-----------|------|------|
|
||||
| `@Table["users"]` | `[Table("users")]` | 类 | 指定表名 |
|
||||
| `@Column["full_name"]` | `[Column("full_name")]` | 字段 | 指定列名 |
|
||||
| `@Key` | `[Key]` | 字段 | 标记主键(字段名 `id` / `_id` 自动为主键) |
|
||||
| `@AutoIncrement` | `[DatabaseGenerated(Identity)]` | 字段 | 整型主键自增(整型 `id` 默认自增) |
|
||||
| `@Ignore` | `[NotMapped]` | 字段 | 不映射该字段 |
|
||||
| `@Required` | `[Required]` | 字段 | 非空列(仅影响建表 DDL) |
|
||||
| `@MaxLength[100]` | `[MaxLength(100)]` | 字段 | 字符串列最大长度(仅影响建表 DDL) |
|
||||
| 注解 | 位置 | 说明 |
|
||||
|------|------|------|
|
||||
| `@Table["users"]` | 类 | 指定表名 |
|
||||
| `@Column["full_name"]` | 字段 | 指定列名 |
|
||||
| `@Key` | 字段 | 标记主键(字段名 `id` / `_id` 自动为主键) |
|
||||
| `@AutoIncrement` | 字段 | 整型主键自增(整型 `id` 默认自增) |
|
||||
| `@Ignore` | 字段 | 不映射该字段 |
|
||||
| `@Required` | 字段 | 非空列(仅影响建表 DDL) |
|
||||
| `@MaxLength[100]` | 字段 | 字符串列最大长度(仅影响建表 DDL) |
|
||||
| `@Index` | 字段 | 对该列建索引(索引名默认 `ix_<表名>_<列名>`,迁移 add 自动生成 CREATE INDEX) |
|
||||
|
||||
### 2. 模型映射约定(对齐 EF Core 数据模型)
|
||||
### 2. 模型映射约定
|
||||
|
||||
- **表名**:`@Table` 优先,否则类简单名;
|
||||
- **列名**:`@Column` 优先,否则按 `ColumnNamingPolicy`(默认 **`Keep`**:字段名原样即列名,不做任何转换;可切换 `SnakeCase`:`userName` → `user_name`;`StripUnderscore`:`_id` → `id`);
|
||||
@@ -317,7 +320,7 @@ orm-cj/
|
||||
|
||||
支持的字段标量类型:`String` / `Bool` / `Int8~Int64` / `UInt8~UInt64` / `Float32` / `Float64` / `Rune` / `DateTime` / `Duration` / `Decimal` / `Array<Byte>`,以及上述类型的 **`Option<T>` 包装**(`None` ↔ `NULL`,DDL 列可空)。
|
||||
|
||||
### 3. DbContext / DbSet(对齐 EF Core)
|
||||
### 3. DbContext / DbSet
|
||||
|
||||
**推荐:`@DbContext` 宏**。把 `DbSet` 声明为 `prop`,宏自动补继承/构造/注入/`migrations()`:
|
||||
|
||||
@@ -337,7 +340,7 @@ public class AppDbContext {
|
||||
let db = AppDbContext("pgsql", "Host=..;Database=app;Username=..;Password=..")
|
||||
db.users.add(user)
|
||||
db.saveChanges()
|
||||
db.migrate() // 应用迁移(对齐 EF Database.Migrate)
|
||||
db.migrate() // 应用迁移
|
||||
|
||||
// 状态检查(应用启动时可选)
|
||||
db.databaseExists() // 数据库是否存在(连系统库探测,不建库)
|
||||
@@ -380,9 +383,9 @@ public class AppDbContext <: DbContext {
|
||||
| `set<T>().count()` | 全表计数 |
|
||||
| `set<T>().query()` | 构建查询 |
|
||||
|
||||
> **语义说明(v1,操作队列模式)**:`add/update/remove` 只入队,`saveChanges` 时开启事务按入队顺序逐条执行后清空队列。与 EF Core 的 identity map 不同,同一实体重复入队会重复执行(如先 `add` 再 `update` = INSERT + UPDATE),请勿对同一实体的同一种操作重复调用。
|
||||
> **语义说明(v1,操作队列模式)**:`add/update/remove` 只入队,`saveChanges` 时开启事务按入队顺序逐条执行后清空队列。同一实体重复入队会重复执行(如先 `add` 再 `update` = INSERT + UPDATE),请勿对同一实体的同一种操作重复调用。
|
||||
|
||||
### 4. 查询构建器 — QueryBuilder<T>(对齐 EF Core IQueryable 常用子集)
|
||||
### 4. 查询构建器 — QueryBuilder<T>
|
||||
|
||||
```cangjie
|
||||
let qb = db.users.query() // 宏方式直接 db.users;等价 db.set<User>().query()
|
||||
@@ -402,7 +405,7 @@ qb.page(2, 10) // PagedResult<User>(items/total/
|
||||
|
||||
> 注意:`where` 是仓颉关键字,条件方法命名为 **`filter`**(两参原始片段 / 三参属性映射两种重载)。
|
||||
|
||||
### 5. 迁移(对齐 EF Core Migrations)
|
||||
### 5. 迁移
|
||||
|
||||
- `Migration` 基类:`super("20250701000000_InitialCreate", "描述")`,实现 `up` / `down`;
|
||||
- `MigrationBuilder`:`createTable` / `dropTable` / `addColumn` / `dropColumn` / `alterColumn` / `renameColumn` / `createIndex` / `dropIndex` / `rawSql`;
|
||||
@@ -431,8 +434,8 @@ cjpm test # 纯逻辑单元测试(模型映射/SQL 生成/DDL 生成/QueryB
|
||||
|
||||
- **cjc 1.1.3 编译器缺陷(重要)**:类上带 `@Table[...]` 注解时,若成员变量带 ≥2 个注解且用 `= ""` 做空字符串初始化(如 `@Required @MaxLength[100] public var x: String = ""`),编译报 `expected expression after '=', found '<EOF>'`。规避:默认值改用 `= String()` 或直接省略初始化器。`orm-cj` 包内代码与 e2e 示例均按此规避写法;
|
||||
- **仅单主键**;不支持复合主键;
|
||||
- **Option<T> 字段已支持**:`?String` 等可空字段 `None ↔ NULL`(详见「模型映射约定」);**父类字段暂不支持**(不扫描继承字段);
|
||||
- 不支持导航属性 / 延迟加载 / 级联删除(对齐 EF Core 这些能力属于 v2+);
|
||||
- **父类字段暂不支持**(不扫描继承字段);
|
||||
- 不支持导航属性 / 延迟加载 / 级联删除(这些能力属于 v2+);
|
||||
- 不支持 LINQ 表达式树,查询以 `filter` 条件方法 + SQL 片段组合;
|
||||
- `QueryBuilder` 的 SQL 片段形式(`filter(condition, params)`)需自行保证列名合法(会做标识符引号包裹校验外的处理)——推荐优先使用三参属性形式;
|
||||
- 自增主键回读依赖驱动 `RETURNING` 支持(openGauss/PostgreSQL 原生支持;换方言时由 `ISqlDialect.buildInsert` 决定回读策略)。
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
cjc-version = "1.1.3"
|
||||
name = "orm"
|
||||
organization = "simcu"
|
||||
description = "SimApi 数据访问层 ORM(对齐 .NET EF Core:数据模型映射 + 增删改查 + 数据库迁移;多方言架构,内置 openGauss/PostgreSQL 方言)"
|
||||
version = "1.2.0"
|
||||
description = "SimApi 数据访问层 ORM:数据模型映射 + 增删改查 + 数据库迁移;多方言架构,内置 openGauss/PostgreSQL 方言"
|
||||
version = "1.2.1"
|
||||
target-dir = ""
|
||||
output-type = "static"
|
||||
|
||||
|
||||
@@ -91,3 +91,13 @@ public class MaxLength {
|
||||
this.maxLength = maxLength
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记该字段所在列建索引(对齐 .NET IndexAttribute),
|
||||
* 迁移 CLI(add) 会按模型自动生成 CREATE INDEX;索引名默认为 ix_<表名>_<列名>。
|
||||
* 用法:@Index public var _sellerAccount: String = ""
|
||||
*/
|
||||
@Annotation[target: [MemberVariable]]
|
||||
public class Index {
|
||||
public const init() {}
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ public class MigrationFileGenerator {
|
||||
case MigrationOperationKind.RenameColumn =>
|
||||
sb.append("${indent}builder.renameColumn(\"${escape(op.tableName)}\", \"${escape(op.columnName)}\", \"${escape(op.newColumnName)}\")\n")
|
||||
case MigrationOperationKind.CreateIndex =>
|
||||
sb.append("${indent}builder.createIndex(\"${escape(op.indexName)}\", \"${escape(op.tableName)}\", [${colList(op.columnNames)}]")
|
||||
sb.append("${indent}builder.createIndex(\"${escape(op.indexName)}\", \"${escape(op.tableName)}\", ArrayList<String>([${colList(op.columnNames)}])")
|
||||
if (op.unique) {
|
||||
sb.append(", true")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -61,6 +61,12 @@ public class PropertyModel {
|
||||
public var isEnum: Bool = false
|
||||
/// 枚举完整类型名(如 simcu::xxx.AssetKind;快照重建的轻量模型为空串)
|
||||
public var enumTypeName: String = ""
|
||||
/// 是否建索引(@Index 标注)
|
||||
public var isIndex: Bool = false
|
||||
/// 索引名(空则按 ix_<表名>_<列名> 自动生成)
|
||||
public var indexName: String = ""
|
||||
/// 是否唯一索引(当前 @Index 为 false,预留)
|
||||
public var indexUnique: Bool = false
|
||||
|
||||
/// 字段类型简单名(如 Int64、String、Option<AssetKind>):优先 typeNameOverride,否则走反射
|
||||
public func typeName(): String {
|
||||
@@ -251,6 +257,13 @@ public class ModelCache {
|
||||
if (let Some(m) <- v.findAnnotation<MaxLength>()) {
|
||||
propM.maxLength = m.maxLength
|
||||
}
|
||||
// 索引(@Index 标注):索引名默认 ix_<表名>_<列名>
|
||||
if (v.findAnnotation<Index>().isSome()) {
|
||||
propM.isIndex = true
|
||||
if (propM.indexName == "") {
|
||||
propM.indexName = "ix_${model.tableName}_${propM.columnName}"
|
||||
}
|
||||
}
|
||||
model.properties.add(propM)
|
||||
}
|
||||
// 主键校验(v1 仅支持单主键)
|
||||
|
||||
@@ -116,7 +116,7 @@ class MigrationFileGeneratorTests {
|
||||
public func testHandWrittenMigrationSource(): Unit {
|
||||
let m = HandWrittenMigration()
|
||||
let src = MigrationFileGenerator.migrationSource("app", MigrationFileGenerator.classNameOf(m.migrationId), m)
|
||||
@Expect(src.contains("builder.createIndex(\"ix_tags_label\", \"tags\", [\"label\"], true)"))
|
||||
@Expect(src.contains("builder.createIndex(\"ix_tags_label\", \"tags\", ArrayList<String>([\"label\"]), true)"))
|
||||
@Expect(src.contains("tb.column(\"label\", ColumnTypes.TextCol).notNull().withUnique().withMaxLength(50)"))
|
||||
// rawSql 双引号转义
|
||||
@Expect(src.contains("builder.rawSql(\"SELECT 1 -- comment \\\"quoted\\\"\")"))
|
||||
|
||||
@@ -444,11 +444,11 @@ class DdlFactoryTests {
|
||||
idxOp.tableName = "users"
|
||||
idxOp.columnNames.add("name")
|
||||
@Expect(f.toSql(idxOp, d),
|
||||
"CREATE INDEX \"ix_users_name\" ON \"users\" (\"name\")")
|
||||
"CREATE INDEX IF NOT EXISTS \"ix_users_name\" ON \"users\" (\"name\")")
|
||||
// CREATE UNIQUE INDEX
|
||||
idxOp.unique = true
|
||||
@Expect(f.toSql(idxOp, d),
|
||||
"CREATE UNIQUE INDEX \"ix_users_name\" ON \"users\" (\"name\")")
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS \"ix_users_name\" ON \"users\" (\"name\")")
|
||||
// DROP INDEX
|
||||
let dropIdx = MigrationOperation(MigrationOperationKind.DropIndex)
|
||||
dropIdx.indexName = "ix_users_name"
|
||||
|
||||
Reference in New Issue
Block a user