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:
@@ -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]
|
||||
}
|
||||
Reference in New Issue
Block a user