feat: orm-cj 仓颉版 EF Core 风格 ORM 初始版本

- 基于数据模型(POCO + 注解)的映射与增删改查:DbContext/DbSet/ChangeTracker/QueryBuilder
- ISqlDialect 方言接口 + PostgreSQL/openGauss 实现,零外部依赖
- @DbContext 宏:纯声明 DbSet 类自动展开为完整 DbContext 子类
- 迁移:Migration/Migrator/ModelSnapshot(快照 JSON)/MiniJson,从模型生成迁移
- 迁移 CLI:add/rm/update/downgrade/list/help,应用内嵌一行 db.cli(args) 接入
- DbContext 便捷方法:databaseExists/hasPendingMigrations/migrate
- 57 个单元测试全部通过
This commit is contained in:
2026-08-19 09:14:01 +08:00
commit afa6096ed2
28 changed files with 5822 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
/*
* Copyright (c) 2025 SimcuTeam. All rights reserved.
* @DbContext 类级宏:把纯声明的 DataContext 类展开为完整的 DbContext 子类。
*
* 用法(对齐 EF Core 声明式体验):
* @DbContext
* public class DataContext {
* public var users: DbSet<User>
* public var bots: DbSet<Bot>
* }
*
* 展开行为:
* - 自动补 `<: DbContext`(标注的类不能自带继承)
* - 每个 DbSet<T> 字段/无 getter prop -> `public prop name: DbSet<T> { get() { this.set<T>() } }`
* - 自动生成 `init(driverName: String, connStr: String)` 调用 super
* - 自动生成 `migrations()` override,返回 orm-cj 全局注册表 allMigrations()
* CLI 生成的 src/migrations/MigrationRegistry.cj 在模块加载时自动注册;
* 无迁移文件的默认项目返回空列表,应用照常编译运行)
*
* 应用侧要求(DataContext.cj 所在文件):
* import simcu::orm.*
* import simcu::orm.macros.*
* import std.collection.* // migrations() 签名需要 ArrayList
* import simcu::orm.migrations.* // migrations() 签名需要 Migration
*/
macro package simcu::orm.macros
import std.ast.*
import std.collection.*
/**
* 把纯声明的 DataContext 类展开为完整 DbContext 子类。
* @param input 被标注的类声明。
* @return 注入继承/字段 prop/init/migrations 后的类声明。
*/
public macro DbContext(input: Tokens): Tokens {
let decl = parseDecl(input)
if (let cd: ClassDecl <- decl) {
if (cd.superTypes.size > 0) {
throw ASTException("@DbContext 标注的类不能自带继承,继承由宏自动补 <: DbContext")
}
// 1. 补继承 <: DbContext
cd.superTypes.add(RefType(cangjieLex("DbContext")))
cd.upperBound = Token(TokenKind.UPPERBOUND)
// 2. DbSet<T> 字段/无 getter prop -> public prop { get() { this.set<T>() } }
let newDecls = ArrayList<Decl>()
for (d in cd.body.decls) {
match (d) {
case vd: VarDecl =>
let typeName = vd.declType.toTokens().toString()
if (isDbSetType(typeName)) {
newDecls.add(buildSetProp(vd.identifier.value, typeName))
} else {
newDecls.add(d)
}
case pd: PropDecl =>
let typeName = pd.declType.toTokens().toString()
if (isDbSetType(typeName)) {
newDecls.add(buildSetProp(pd.identifier.value, typeName))
} else {
newDecls.add(d)
}
case _ => newDecls.add(d)
}
}
// 3. 生成 init + migrations
newDecls.add(parseDecl(cangjieLex(
"public init(driverName: String, connStr: String) {\n super(driverName, connStr)\n}")))
newDecls.add(parseDecl(cangjieLex(
"public override func migrations(): ArrayList<Migration> {\n allMigrations()\n}")))
cd.body.decls.clear()
for (d in newDecls) {
cd.body.decls.add(d)
}
return cd.toTokens()
}
throw ASTException("@DbContext 只能标注在 class 声明上")
}
/// 类型 tokens 形如 "DbSet < User >" / "DbSet<User>",判断是否为 DbSet 类型
private func isDbSetType(t: String): Bool {
t.indexOf("DbSet") == Some(0) && t.indexOf("<").getOrThrow() > 0
}
/// 生成 `public prop name: DbSet<T> { get() { this.set<T>() } }`
private func buildSetProp(name: String, typeName: String): Decl {
let inner = extractInnerType(typeName)
let propSrc = "public prop ${name}: ${typeName} { get() { this.set<${inner}>() } }"
parseDecl(cangjieLex(propSrc))
}
/// 提取泛型实参 tokens"DbSet < User >" -> " User ""DbSet < ArrayList < User > >" -> " ArrayList < User > "
/// 生成 `this.set< User >()` 时空格合法,无需去空格
private func extractInnerType(t: String): String {
let open = t.indexOf("<").getOrThrow()
let close = t.lastIndexOf(">").getOrThrow()
t[open + 1..close]
}