DbContext中只改写let 字段

This commit is contained in:
2026-09-02 17:54:56 +08:00
parent 74eb8b576e
commit ed082b05f9
5 changed files with 161 additions and 22 deletions
+3
View File
@@ -6,3 +6,6 @@ src/macros/*.dll
src/tests/*.cj.macrocall
# 临时/日志
*.log
.arts/
.cache/
.codegraph/
+14 -10
View File
@@ -42,7 +42,7 @@ public class User {
### 2. 定义 DbContext 并增删改查
用 `@DbContext` 宏(推荐):纯声明类即可,宏自动补 `<: DbContext`、把每个 `DbSet<T>` 声明变成 `public prop` 并注入 `this.set<T>()`、生成 `(driverName, connStr)` 构造与 `migrations()` override:
用 `@DbContext` 宏(推荐):纯声明类即可,宏自动补 `<: DbContext`、把每个 **`let` 前缀**的 `DbSet<T>` 字段改写成 `public prop` 并注入 `this.set<T>()`(`var` / `prop` 前缀一律不改动,交给用户自行管理)、生成 `(driverName, connStr)` 构造与 `migrations()` override:
```cangjie
import std.database.sql.*
@@ -52,8 +52,8 @@ import simcu::orm.macros.*
@DbContext
public class AppDbContext {
prop users: DbSet<User>
prop orders: DbSet<Order>
let users: DbSet<User>
let orders: DbSet<Order>
}
main() {
@@ -190,8 +190,8 @@ import simcu::orm.macros.*
@DbContext
public class MyDbContext {
prop users: DbSet<User>
prop orders: DbSet<Order>
let users: DbSet<User>
let orders: DbSet<Order>
}
main(args: Array<String>) {
@@ -324,7 +324,7 @@ orm-cj/
### 3. DbContext / DbSet
**推荐:`@DbContext` 宏**。把 `DbSet` 声明为 `prop`,宏自动补继承/构造/注入/`migrations()`:
**推荐:`@DbContext` 宏**。把 `DbSet` 声明为 **`let` 前缀**(`let xxx: DbSet<T>`),宏只处理这种形式:改写成 `public prop xxx: DbSet<T> { get() { this.set<T>() } }`(public getter 保证反射可枚举);`var` / `prop` 前缀一律不改动。同时自动补继承/构造/`migrations()`:
```cangjie
import simcu::orm.*
@@ -334,8 +334,8 @@ import std.collection.* // migrations() 签名需要 ArrayList
@DbContext
public class AppDbContext {
prop users: DbSet<User>
prop orders: DbSet<Order>
let users: DbSet<User>
let orders: DbSet<Order>
}
// 用法(宏生成 (driverName, connStr) 构造)
@@ -354,8 +354,12 @@ if (!db.databaseExists() || db.hasPendingMigrations()) { db.migrate() }
```cangjie
public class AppDbContext <: DbContext {
public let users: DbSet<User>
public let orders: DbSet<Order>
public prop users: DbSet<User> {
get() { this.set<User>() }
}
public prop orders: DbSet<Order> {
get() { this.set<Order>() }
}
public init(driverName: String, connStr: String) { super(driverName, connStr) }
public init(datasource: Datasource) { super(datasource) }
+1 -1
View File
@@ -3,7 +3,7 @@ cjc-version = "1.1.3"
name = "orm"
organization = "simcu"
description = "SimApi 数据访问层 ORM:数据模型映射 + 增删改查 + 数据库迁移;多方言架构,内置 openGauss/PostgreSQL 方言"
version = "1.4.0"
version = "1.4.1"
target-dir = ""
output-type = "static"
+24 -10
View File
@@ -5,13 +5,14 @@
* 用法(对齐 EF Core 声明式体验):
* @DbContext
* public class DataContext {
* public var users: DbSet<User>
* public var bots: DbSet<Bot>
* let users: DbSet<User>
* let bots: DbSet<Bot>
* }
*
* 展开行为:
* - 自动补 `<: DbContext`(标注的类不能自带继承)
* - 每个 DbSet<T> 字段/无 getter prop -> `public prop name: DbSet<T> { get() { this.set<T>() } }`
* - 只处理「let 前缀」的 DbSet<T> 字段 -> `public prop name: DbSet<T> { get() { this.set<T>() } }`;
* var / prop 一律不改动(交给用户自行管理)
* - 自动生成 `init(driverName: String, connStr: String)` 调用 super
* - 自动生成 `migrations()` override,返回 orm-cj 全局注册表 allMigrations()
* (CLI 生成的 src/migrations/MigrationRegistry.cj 在模块加载时自动注册;
@@ -44,24 +45,21 @@ public macro DbContext(input: Tokens): Tokens {
cd.superTypes.add(RefType(cangjieLex("DbContext")))
cd.upperBound = Token(TokenKind.UPPERBOUND)
// 2. DbSet<T> 字段/无 getter prop -> public prop { get() { this.set<T>() } }
// 2. 只处理「let 前缀」的 DbSet<T> 字段 -> `public prop { get() { this.set<T>() } }`;
// var / prop 一律不改动(交给用户自行管理)
let newDecls = ArrayList<Decl>()
for (d in cd.body.decls) {
match (d) {
case vd: VarDecl =>
let typeName = vd.declType.toTokens().toString()
if (isDbSetType(typeName)) {
if (isDbSetType(typeName) && declIsLet(vd.toTokens().toString())) {
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 {
// prop 一律不改动
newDecls.add(d)
}
case _ => newDecls.add(d)
}
}
@@ -86,6 +84,22 @@ private func isDbSetType(t: String): Bool {
t.indexOf("DbSet") == Some(0) && t.indexOf("<").getOrThrow() > 0
}
/// 判断声明关键字是否为 let(保留字 let/var 只会独立出现在声明词位置,
/// 按词边界匹配避免误伤类型名/泛型实参里的子串)。
/// prop 是 PropDecl(非 VarDecl),根本不会走到这里。
private func declIsLet(t: String): Bool {
hasWord(t, "let") && !hasWord(t, "var")
}
/// 判断字符串中 w 是否作为独立词出现(textified 输出用单个空格分隔词)
private func hasWord(s: String, w: String): Bool {
let padded = " " + s + " "
match (padded.indexOf(" " + w + " ")) {
case Some(_) => true
case None => false
}
}
/// 生成 `public prop name: DbSet<T> { get() { this.set<T>() } }`
private func buildSetProp(name: String, typeName: String): Decl {
let inner = extractInnerType(typeName)
+118
View File
@@ -0,0 +1,118 @@
/*
* Copyright (c) 2025 SimcuTeam. All rights reserved.
* @DbContext 宏回归测试:只处理「let 前缀」的 DbSet<T> 字段,var/prop 一律不改动。
*
* 通过注册一个 FakeDriver,让宏生成的 `init(driverName, connStr)` 走通
* `super(...) -> DatasourceFactory.create(connStr, driverName)` 这条真实路径,
* 从而验证:let 字段被改写为 `public prop { get() { this.set<T>() } }`(public 可被
* std.reflect 枚举),getModels() 能反射采集到模型;手写 prop DbSet 保持原样不被改写。
*/
package simcu::orm.tests
import std.collection.*
import std.database.sql.*
import std.time.*
import std.unittest.*
import std.unittest.testmacro.*
import simcu::orm.*
import simcu::orm.macros.*
// ---------- 复用 sim_orm_test.cj 的实体 ----------
// User / Order 已在同包定义,这里直接引用。
// ---------- 假驱动 ----------
class FakeDriver <: Driver {
public prop name: String {
get() { "fake" }
}
public prop version: String {
get() { "0.0.1" }
}
public prop preferredPooling: Bool {
get() { false }
}
public func open(_: String, _: Array<(String, String)>): Datasource {
FakeDatasource()
}
public func getDriverStartTime(): DateTime {
DateTime.now()
}
}
let _ = {
=>
DriverManager.register("fake", FakeDriver())
}()
// ---------- 宏应用:仅含 let 字段 ----------
@DbContext
class MacroCtx {
let users: DbSet<User>
let orders: DbSet<Order>
}
// ---------- 宏应用:let 与手写 prop 混用 ----------
// let 字段被宏改写为 public prop;手写 prop(含 getter)保持原样、不改写。
@DbContext
class MixedCtx {
let users: DbSet<User>
prop todos: DbSet<Order> {
get() { this.set<Order>() }
}
}
@Test
public class DbContextMacroTests {
@TestCase
public func testMacroGeneratesInitAndDbSets(): Unit {
let ctx = MacroCtx("fake", "postgres://localhost/db")
// 宏生成的 init 已把 let 字段初始化为 set<T>(),可直接使用
let users = ctx.users
let orders = ctx.orders
@Expect(users is DbSet<User>, true)
@Expect(orders is DbSet<Order>, true)
// DbSet 连通到 context 的变更队列
users.add(User())
@Expect(ctx.pendingCount(), 1)
orders.add(Order())
@Expect(ctx.pendingCount(), 2)
// 默认方言为 openGauss(继承 PG)
@Expect(ctx.getDialect().quoteName("users"), "\"users\"")
}
@TestCase
public func testMacroModelsCollectedFromLetFields(): Unit {
let ctx = MacroCtx("fake", "postgres://localhost/db")
// 宏把 let DbSet 字段统一提升为 public,getModels() 反射可采集到模型
let models = ctx.getModels()
@Expect(models.size, 2)
// 模型采集自 let 字段(users -> User, orders -> Order)
var gotUser = false
var gotOrder = false
for (m in models) {
if (m.tableName == "users") {
gotUser = true
}
if (m.tableName == "orders") {
gotOrder = true
}
}
@Expect(gotUser, true)
@Expect(gotOrder, true)
}
@TestCase
public func testPropDbSetIsNotRewritten(): Unit {
let ctx = MixedCtx("fake", "postgres://localhost/db")
// let 字段被改写为 public prop,反射可采集 -> 1 个模型(users)
let models = ctx.getModels()
@Expect(models.size, 1)
// 手写 prop(internal,含 getter)保持原样:可正常使用,
// 但不会被宏改写成 public prop,因而不会重复进入模型采集
let todos = ctx.todos
@Expect(todos is DbSet<Order>, true)
todos.add(Order())
@Expect(ctx.pendingCount(), 1)
}
}