# SimOrm for Cangjie(simcu::orm) > 灵感来自于 EF Core。 > 仓颉版 ORM:**基于数据模型(POCO + 注解)的映射、增删改查、数据库迁移**。 面向多数据库的方言架构:内置 PostgreSQL(`PostgreSqlDialect`)与 openGauss(`OpenGaussDialect`,继承 PG)方言(对接 [opengauss-driver](../opengauss-driver)),可通过实现 `SqlDialect` 接口接入 sqlite / mysql 等新数据库。包本身零外部依赖。 --- ## 引入 ```toml [dependencies] "simcu::orm" = { path = "../orm-cj" } ``` > 数据库驱动由使用者自行引入(如 `opengauss` path 依赖 openGauss 驱动);构建前需设置 `CANGJIE_STDX_PATH` 指向本地 stdx 的 `static/stdx` 目录。 --- ## 快速开始 ### 1. 定义实体(数据模型) ```cangjie package your_app.models import simcu::orm.* @Table["users"] // 指定表名(不写则用类简单名) public class User { public var id: Int64 = 0 // 整型 id → 主键 + 数据库自增 public var name: String = "" // 列名 name @Column["full_name"] public var alias: String = "" // 注解覆盖列名 @Ignore public var temp: String = "" // 不映射 public var age: Int32 = 0 } ``` ### 2. 定义 DbContext 并增删改查 用 `@DbContext` 宏(推荐):纯声明类即可,宏自动补 `<: DbContext`、把每个 **`let` 前缀**的 `DbSet` 字段改写成 `public prop` 并注入 `this.set()`(`var` / `prop` 前缀一律不改动,交给用户自行管理)、生成 `(driverName, connStr)` 构造与 `migrations()` override: ```cangjie import std.database.sql.* import opengauss.driver.* import simcu::orm.* import simcu::orm.macros.* @DbContext public class AppDbContext { let users: DbSet let orders: DbSet } main() { // 1. 构造 DbContext("pgsql" 驱动 + 连接串),迁移 CLI 与 CRUD 共用同一个实例 let db = AppDbContext("pgsql", "Host=..;Database=app;Username=..;Password=..") // 2. 增 let user = User() user.name = "alice" db.users.add(user) // 入队 INSERT db.saveChanges() // 事务内执行,回读自增主键到 user.id // 3. 改 user.age = 30 db.users.update(user) // 入队 UPDATE(全部非主键列) db.saveChanges() // 4. 查 let list = db.users.query() .filter("age", ">", Int64(18)) // 属性名/列名自动映射列名 .orderBy("name") .skip(0).take(10) .toList() // ArrayList let one = db.users.find(Int64(1)) // ?User,按主键 let total = db.users.query().count() // 5. 删 db.users.remove(user) db.saveChanges() } ``` > 不用宏时手动继承 `DbContext` 等价写法:补 `public init(...) { super(...) }` 构造,每个 DbSet 声明为 `public prop users: DbSet { get() { this.set() } }`,并 override `migrations()`。宏就是把这几个样板步骤自动做完(详见「模块说明 3」)。 ### 3. 迁移 ```cangjie import simcu::orm.migrations.* // 定义迁移(migrationId 按字典序应用;CLI 生成 "yyyyMMddHHmmss" 时间戳格式,类名 MigrationId<时间戳>) public class MigrationId20250701000000 <: Migration { public init() { super("20250701000000", "创建用户表") } public override func up(builder: MigrationBuilder): Unit { builder.createTable("users") { tb => tb.column("id", ColumnTypes.BigIntCol).primary().autoInc().notNull() tb.column("name", ColumnTypes.TextCol).withMaxLength(100).notNull() tb.column("active", ColumnTypes.BoolCol).withDefault(true) } builder.createIndex("ix_users_name", "users", idxCols) } public override func down(builder: MigrationBuilder): Unit { builder.dropTable("users") } } main() { let idxCols = ArrayList() // 索引列(API 为 ArrayList) idxCols.add("name") let migrator = Migrator(datasource) let pending = migrator.pending(migrations) // 未执行的迁移(预览) migrator.migrate(migrations) // 事务内按序执行并记录历史 } ``` 迁移历史记录在 `simcu_orm_migrations` 表(id / name / applied_at),已应用的迁移自动跳过。 --- ### 从数据模型生成迁移(MigrationGenerator) 不用手写 `Migration` 子类,直接从实体模型生成: ```cangjie import simcu::orm.* import simcu::orm.migrations.* main() { let gen = MigrationGenerator() // 1. 初始迁移:模型集合 → 全部 CREATE TABLE(down 自动生成 DROP TABLE) let models = ArrayList() models.add(ModelCache.get()) models.add(ModelCache.get()) let m0 = gen.initial("20250701000000", "初始建表", models) Migrator(datasource).migrate([m0]) // 2. 模型变更后:旧模型快照(上次的 models) vs 新模型 → 增量迁移(表/列增删改) let models2 = ArrayList() models2.add(ModelCache.get()) // 假设 User 新增了 age 字段 models2.add(ModelCache.get()) let m1 = gen.diff("20250801000000_AddAge", "新增 age 列", models, models2) Migrator(datasource).migrate([m1]) // 旧模型列表 models 由调用方保存,充当快照 } ``` ### 快照持久化(ModelSnapshot) 上面 `diff` 需要调用方手存旧模型列表。`ModelSnapshot` 把模型序列化成 JSON 持久化到文件,`diff` 不依赖手存: ```cangjie import simcu::orm.* import simcu::orm.migrations.* main() { let gen = MigrationGenerator() // ensure: 快照文件不存在 → 生成初始迁移并保存快照; // 已存在 → 载入旧快照 diff 出新迁移并覆盖保存 let m = gen.ensure("20250801000000_AddAge", "新增 age 列", "snapshot.json", models()) Migrator(datasource).migrate([m]) } ``` - 快照 JSON 结构:`{"version":4,"models":[{"table":"users","columns":[{...}],"indexes":[{...}]}]}`,含表名、列名、类型、主键/自增/必填/最大长度,以及实体级索引(名称/列/唯一/过滤条件); - `ModelSnapshot` 也提供 `capture(models)` / `toJson()` / `fromJson(text)` / `save(path)` / `load(path)`(文件不存在返回 `None`),可自行接入版本化/比较逻辑; - 零依赖:`MiniJson` 内置迷你 JSON 解析/序列化,不引入第三方包。 生成规则与限制(v1): - **初始**:`initial(id, desc, models)` → 每个模型一张 `CreateTable`,`down` 为逆序 `DropTable`; - **增量**:`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→BinaryCol`、`Rune→IntCol`、`Duration→BigIntCol`;`Option` 按内层类型 X 映射且列可空(`None` ↔ `NULL`); - **不支持**(抛异常提示手写迁移):主键列名变更、主键/自增属性变更;`@Ignore`/`@Column`/`@Required`/`@MaxLength`/`@AutoIncrement`/`@Index` 均会反映到 DDL(`@Index` 生成 `CREATE INDEX`);类级 `@TableIndex` 生成对应的 `CREATE [UNIQUE] INDEX ... [WHERE filter]`(复合/唯一/部分索引),字段级与实体级索引统一走 `IndexSpec` diff(快照 v4 持久化)。 ### 迁移 CLI `orm-cj` 附带迁移 CLI(`simcu::orm.cli.MigrationCli`),把迁移落成 **.cj 文件** + **快照 JSON**,应用内嵌运行,无需安装外部工具。 **应用接线(一行接入)**:`main()` 最上方先构造 `DbContext` 子类实例,`app orm <命令>` 时执行 CLI 并退出,否则走正常应用逻辑: ```cangjie import simcu::orm.* import simcu::orm.macros.* // import myapp.migrations.* // 生成首个迁移后手动取消注释(见下方「生成约定」) @DbContext public class MyDbContext { let users: DbSet let orders: DbSet } main(args: Array) { let db = MyDbContext("pgsql", "Host=..;Database=..;Username=..;Password=..") if (db.cli(args)) { // `app orm add/rm/update/downgrade/list/help` return } // ... 正常应用启动逻辑(db 可继续用于 CRUD) } ``` > `db.cli(args)` 是 DbContext 的实例方法:命中 `orm <命令>` 时执行 CLI 并返回 `true`,否则返回 `false`(正常启动)。模型从实例的 `DbSet` 属性反射收集,连接/驱动/方言来自构造参数,无需任何回调或宏(`@DbContext` 宏只是省去手写继承样板,CLI 不依赖它)。 编译后运行(`cjpm run -- ` 是把参数传给应用的方式): | 命令 | 作用 | |------|------| | `cjpm run -- orm add [描述]` | 从模型生成迁移 .cj 文件 + 更新注册文件 + 写入快照(描述可空,仅写入注释与历史记录);模型无变化则跳过 | | `cjpm run -- orm rm <迁移id或类名>` | 移除已生成的迁移(删除迁移文件 + 注册条目;注册表清空时重置快照) | | `cjpm run -- orm update` | 连接数据库,按 `migrationId` 字典序应用所有未执行的迁移 | | `cjpm run -- orm downgrade [目标]` | 回退迁移:无目标=回退最近一个;有目标=回退到该迁移之后(含该迁移的 down) | | `cjpm run -- orm list` | 列出已注册迁移(需真实连接读取历史表) | | `cjpm run -- orm seed` | 列出数据填充;`seed ` 运行指定;`seed --all` 运行全部(见「数据填充」) | | `cjpm run -- orm database truncate` | 清空所有业务表数据(保留表结构与迁移历史) | | `cjpm run -- orm database drop` | 删除所有表(含迁移历史),完全清空数据库,之后 `update` 可重建 | | `cjpm run -- orm help` | 显示帮助 | **开发/部署模式**:在项目目录(含 `cjpm.toml`)通过 `cjpm run` 运行时全部命令可用;打包部署后(工作目录无 `cjpm.toml`)自动进入只读模式,仅保留 `list` / `update`(应用迁移)/ `seed`(数据填充,只写业务数据)。 **生成约定**: - **首次使用(无迁移)**:不需要建 `src/migrations/` 目录,也不要写 `import <应用包名>.migrations.*`(该子包不存在会导致编译失败);首次 `add` 时 CLI 自动创建目录并生成迁移文件 + 注册文件; - **生成后手动加 import**:执行 `add` 生成首个迁移后,在 `main()` 所在文件手动加一行 `import <应用包名>.migrations.*`(如 `import myapp.migrations.*`)——注册文件在子包内惰性加载,只有 import 触发后 `update/downgrade/list` 才能看到迁移;之后新增迁移无需再动这行; - 迁移 / 注册文件生成在 `src/migrations/`,该目录是**子包**,文件头 `package <应用包名>.migrations`(如应用包 `myapp` → `package myapp.migrations`); - 迁移文件命名为 **id 的蛇形形式**(如 `20250701000000.cj`),仅含 migrationId,不包含描述;CLI 生成的 migrationId 为 `yyyyMMddHHmmss` 时间戳,**迁移类名自动为 `MigrationId<时间戳>`**(如 `MigrationId20250701000000`);迁移描述可空,写入文件头部 `// 描述: ...` 注释与迁移类 `super("...", "...")` 中; - `add` 只生成不执行;执行迁移用 `update`(回退用 `downgrade`),或在应用启动时直接 `db.migrate()`; - 快照持久化在 `src/migrations/snapshot.json`;`add` 用「旧快照 vs 当前模型」做 diff,所以**改模型后重新编译应用再 `add`** 即生成增量迁移; - 生成源码文本已包含 `import std.collection.*` 等标准库引用,应用无需额外配置。 ### 数据填充(Seed) `orm seed` 提供数据填充:用 `@Seed` 类级宏标注普通类,`run(ctx)` 内用**应用自己的 DbContext** 直接操作 ORM。**无需注册表、不记录运行历史,可随时/多次运行,幂等由填充代码自行保证**(如按 id find 后不存在再插入)。 ```cangjie // src/seeds/InitGames.cj import simcu::orm.macros.* import simcu::orm.seeds.* import myapp.models.* @Seed["初始化游戏服务器配置"] // seedId 取类名 InitGames,参数仅作描述 public class InitGames { public func run(ctx: MyDbContext): Unit { if (ctx.games.find("game-1").isNone()) { // 自行保证幂等 let g = Game() g.id = "game-1" g.apiKey = "dev-key-1" g.name = "测试服" ctx.games.add(g) ctx.saveChanges() } } } ``` `@Seed` 宏在编译期自动展开:生成包装类(继承 `simcu::orm.seeds.Seed`,把 `ctx` 从 `Any` 转型为 run 参数类型后调用)和顶层 `let registerSeed(...)`,模块加载即自动注册,**无需手写注册文件**。应用侧只需在 `main()` 所在文件加一行 `import <应用包名>.seeds.*` 触发子包加载(与迁移注册文件同模式)。 | 命令 | 作用 | |------|------| | `cjpm run -- orm seed` | 列出全部已注册填充(seedId + 描述) | | `cjpm run -- orm seed ` | 运行指定填充(支持唯一前缀匹配) | | `cjpm run -- orm seed --all` | 依次运行全部填充(单个失败继续执行其余,最终非零退出) | > 运行前自动检查迁移是否全部已应用(避免目标表不存在);打包环境同样可用。 --- ## 项目结构 ``` orm-cj/ ├── cjpm.toml # 包配置(name = orm, organization = simcu) ├── src/ │ ├── orm.cj # 聚合导出:import simcu::orm.* 即全部可见 │ ├── annotations/ # @Table / @Column / @Key / @AutoIncrement / @Ignore / @Required / @MaxLength / @Index / @TableIndex │ ├── macros/ # @DbContext 类级宏 + @Seed 数据填充宏(自动注册,无需手写注册表) │ ├── seeds/ # Seed 基类 + 进程级填充注册表(registerSeed / allSeeds) │ ├── model/ # EntityModel(反射映射)、ModelCache(模型缓存)、 │ │ # ColumnNamingPolicy(列命名策略)、ValueReader、ParamBinder、GuidUtil │ ├── tracking/ # ChangeTracker(操作队列)、EntityState、EntityEntry │ ├── sql/ # SqlDialect 接口(独立文件)+ PostgreSqlDialect(PG 实现)+ │ │ # OpenGaussDialect(继承 PG)、ColumnTypes(列类型) │ ├── query/ # QueryBuilder(条件/排序/分页)、PagedResult │ ├── db/ # DbContext(连接 + 提交 + 物化 + 数据库存在性/迁移状态检查)、DbSet │ ├── cli/ # MigrationCli(add/rm/update/downgrade/list/seed/database 命令)、 │ │ # MigrationFileGenerator(迁移/注册文件源码生成) │ ├── migrations/ # Migration 基类、MigrationBuilder、ColumnDefinition、 │ │ # DdlFactory(操作 → DDL SQL,差异语法委托方言)、 │ │ # MigrationGenerator(模型 → 迁移)、Migrator(历史表 + 执行)、 │ │ # ModelSnapshot(模型快照 JSON 持久化)+ MiniJson(零依赖 JSON 引擎) │ └── tests/ # 单元测试(cjpm test,纯逻辑,不连库) ``` --- ## 模块说明 ### 1. 注解 | 注解 | 位置 | 说明 | |------|------|------| | `@Table["users"]` | 类 | 指定表名 | | `@Column["full_name"]` | 字段 | 指定列名 | | `@Key` | 字段 | 标记主键(字段名 `id` / `_id` 自动为主键) | | `@AutoIncrement` | 字段 | 整型主键自增(整型 `id` 默认自增) | | `@Ignore` | 字段 | 不映射该字段 | | `@Required` | 字段 | 非空列(仅影响建表 DDL) | | `@MaxLength[100]` | 字段 | 字符串列最大长度(仅影响建表 DDL) | | `@Index` | 字段 | 对该列建索引(索引名默认 `ix_<表名>_<列名>`,迁移 add 自动生成 CREATE INDEX) | | `@TableIndex["col1,col2", true, "cond"]` | 类 | 类级复合/唯一/条件索引(对齐 .NET 类级 `[Index(...)]`);`columns` 为逗号分隔的列/字段名(字段名自动映射为列名),`unique` 是否唯一,`filter` 非空时生成 `... WHERE cond` 部分索引;索引名可省略(`name` 置于末位,留空则按 `ix_<表名>_<首列>` 自动生成),支持 `[columns]`、`[columns, unique]`、`[columns, unique, filter]`、`[columns, unique, filter, name]` 四种用法 | ### 2. 模型映射约定 - **表名**:`@Table` 优先,否则类简单名; - **列名**:`@Column` 优先,否则按 `ColumnNamingPolicy`(默认 **`Keep`**:字段名原样即列名,不做任何转换;可切换 `SnakeCase`:`userName` → `user_name`;`StripUnderscore`:`_id` → `id`); - **主键**:`@Key` 标注,或字段名 `id`;v1 仅支持**单主键**; - **自增**:`@AutoIncrement`,或整型(Int8-64)`id` 主键默认自增;INSERT 跳过该列并通过 `RETURNING` 回读; - **客户端生成主键**:`String` 主键默认客户端生成(`GuidUtil`:时间戳 + 自增序号 hex),INSERT 时为空自动生成; - **实体要求**:字段必须是 `public var` 标量类型,且提供无参 `public init()`。 支持的字段标量类型:`String` / `Bool` / `Int8~Int64` / `UInt8~UInt64` / `Float32` / `Float64` / `Rune` / `DateTime` / `Duration` / `Decimal` / `Array`,以及上述类型的 **`Option` 包装**(`None` ↔ `NULL`,DDL 列可空)。 ### 3. DbContext / DbSet **推荐:`@DbContext` 宏**。把 `DbSet` 声明为 **`let` 前缀**(`let xxx: DbSet`),宏只处理这种形式:改写成 `public prop xxx: DbSet { get() { this.set() } }`(public getter 保证反射可枚举);`var` / `prop` 前缀一律不改动。同时自动补继承/构造/`migrations()`: ```cangjie import simcu::orm.* import simcu::orm.macros.* import simcu::orm.migrations.* // migrations() 签名需要 Migration import std.collection.* // migrations() 签名需要 ArrayList @DbContext public class AppDbContext { let users: DbSet let orders: DbSet } // 用法(宏生成 (driverName, connStr) 构造) let db = AppDbContext("pgsql", "Host=..;Database=app;Username=..;Password=..") db.users.add(user) db.saveChanges() db.migrate() // 应用迁移 // 状态检查(应用启动时可选) db.databaseExists() // 数据库是否存在(连系统库探测,不建库) db.hasPendingMigrations() // 是否存在未应用的迁移(历史表不存在视为有) if (!db.databaseExists() || db.hasPendingMigrations()) { db.migrate() } ``` **不用宏的等价手动写法**:继承 `DbContext`,`public prop xxx: DbSet { get() { this.set() } }`,补构造并 override `migrations()`: ```cangjie public class AppDbContext <: DbContext { public prop users: DbSet { get() { this.set() } } public prop orders: DbSet { get() { this.set() } } public init(driverName: String, connStr: String) { super(driverName, connStr) } public init(datasource: Datasource) { super(datasource) } public override func migrations(): ArrayList { /* 迁移列表 */ } } ``` | API | 说明 | |-----|------| | `AppDbContext("pgsql", connStr)` | 构造:驱动名 + 连接串(驱动构建收敛在 `DatasourceFactory`;驱动名归一化,`pgsql/pg/postgresql`→`postgres`,具体驱动由应用链接的驱动包注册) | | `AppDbContext(connStr)` | 构造:只给连接串,自动探测驱动(默认 openGauss 方言) | | `AppDbContext(datasource)` | 构造:直接给驱动 Datasource(默认 `OpenGaussDialect`,继承 PG) | | `AppDbContext(datasource, dialect)` | 构造:自定义方言,如 `PostgreSqlDialect()` | | `db.set()` | 获取实体的 DbSet(同类型复用同一实例;宏展开的 prop 内部即调用它) | | `db.pendingCount()` | 待提交变更条数 | | `db.saveChanges()` | 事务内按入队顺序执行全部变更,返回影响条数 | | `db.getDialect()` | 当前方言(子类/应用可读取) | | `db.migrate(migrations)` | 应用指定迁移(委托 `Migrator`),返回本次应用数量 | | `db.migrate()` | 便捷版:应用子类 `migrations()` 提供的全部迁移 | | `db.databaseExists()` | 数据库是否存在(连系统库参数化查询,需带连接串构造;sqlite 查文件) | | `db.hasPendingMigrations()` | 是否存在未应用的迁移(数据库/历史表不存在视为有待应用迁移) | | `set().add(e)` | 入队新增 | | `set().update(e)` | 入队修改(UPDATE 全部非主键列) | | `set().remove(e)` | 入队删除 | | `set().find(key)` | 按主键查(`?T`) | | `set().toList()` | 全表查询 | | `set().count()` | 全表计数 | | `set().query()` | 构建查询 | > **语义说明(v1,操作队列模式)**:`add/update/remove` 只入队,`saveChanges` 时开启事务按入队顺序逐条执行后清空队列。同一实体重复入队会重复执行(如先 `add` 再 `update` = INSERT + UPDATE),请勿对同一实体的同一种操作重复调用。 ### 4. 查询构建器 — QueryBuilder ```cangjie let qb = db.users.query() // 宏方式直接 db.users;等价 db.set().query() qb.filter("age", ">", Int64(18)) // (属性名, 操作符, 值):自动映射列名 let ps = ArrayList() // (SQL 片段, 参数):按 ? 顺序,写原始列名 ps.add(Int64(18)) ps.add(true) qb.filter("age > ? AND active = ?", ps) qb.orderBy("name").orderByDesc("age") // 排序 qb.skip(10).take(5) // 分页(LIMIT/OFFSET) qb.toList() // ArrayList qb.first() // ?User qb.count() // Int64,满足条件的总数 qb.page(2, 10) // PagedResult(items/total/page/pageSize/totalPages) ``` > 注意:`where` 是仓颉关键字,条件方法命名为 **`filter`**(两参原始片段 / 三参属性映射两种重载)。 ### 5. 迁移 - `Migration` 基类:`super("20250701000000", "描述")`,实现 `up` / `down`; - `MigrationBuilder`:`createTable` / `dropTable` / `addColumn` / `dropColumn` / `alterColumn` / `renameColumn` / `createIndex` / `dropIndex` / `rawSql`; - `ColumnDefinition` 链式:`.primary()` `.autoInc()` `.notNull()` `.withUnique()` `.withMaxLength(n)` `.withDefault(value)`; - `ColumnTypes`(定义于 `simcu::orm.sql`):`BigIntCol`(openGauss 自增 → BIGSERIAL)/ `IntCol`(自增 → SERIAL)/ `SmallIntCol` / `TinyIntCol` / `TextCol`(VARCHAR,默认 255)/ `BoolCol` / `FloatCol`(DOUBLE PRECISION)/ `RealCol` / `DateTimeCol` / `DecimalCol`(DECIMAL(18,6))/ `BinaryCol`(BYTEA);实际 DDL 映射由方言 `columnTypeSql` 决定; - `Migrator`:`pending(migrations)` 预览未执行项;`migrate(migrations)` 事务内按 `migrationId` 字典序应用未执行项并写入历史表 `simcu_orm_migrations`。 ### 6. 数据填充(@Seed 宏) - `@Seed["描述"]` 标注普通类:**seedId 取类名**,属性参数仅作描述;类内 `run(ctx)` 用应用自己的 DbContext 直接增删改; - 宏自动生成 `Seed` 子类包装(`run(ctx: Any)` 内转型调用)+ 顶层 `let registerSeed(...)`,模块加载即注册,**无需手写注册表**; - 语义:**不记录运行历史**,可随时/多次运行,幂等由 `run` 内自行保证(如按 id find 后不存在再插入); - 应用侧需 `import <应用包名>.seeds.*` 触发子包加载(与迁移注册文件同模式);CLI 见「数据填充(Seed)」。`@Seed` 要求类提供单参数 `run(ctx)`,描述不能含双引号/反斜杠/换行。 --- ## 运行测试 ```bash cjpm test # 纯逻辑单元测试(模型映射/SQL 生成/DDL 生成/QueryBuilder),无需数据库 ``` --- ## 已知限制(v1) - **cjc 1.1.3 编译器缺陷(重要)**:类上带 `@Table[...]` 注解时,若成员变量带 ≥2 个注解且用 `= ""` 做空字符串初始化(如 `@Required @MaxLength[100] public var x: String = ""`),编译报 `expected expression after '=', found ''`。规避:默认值改用 `= String()` 或直接省略初始化器。`orm-cj` 包内代码与 e2e 示例均按此规避写法; - **仅单主键**;不支持复合主键; - **父类字段暂不支持**(不扫描继承字段); - 不支持导航属性 / 延迟加载 / 级联删除(这些能力属于 v2+); - 不支持 LINQ 表达式树,查询以 `filter` 条件方法 + SQL 片段组合; - `QueryBuilder` 的 SQL 片段形式(`filter(condition, params)`)需自行保证列名合法(会做标识符引号包裹校验外的处理)——推荐优先使用三参属性形式; - 自增主键回读依赖驱动 `RETURNING` 支持(openGauss/PostgreSQL 原生支持;换方言时由 `SqlDialect.buildInsert` 决定回读策略)。 --- ## 依赖 | 依赖 | 用途 | |------|------| | 数据库驱动(使用者引入,如 `opengauss` → `../opengauss-driver`) | 实现 `std.database.sql` 的 `Datasource`/`Connection`/`Statement` | | `stdx`(CANGJIE_STDX_PATH) | 标准扩展库(std.database.sql 接口) | > orm-cj 包本身零依赖:连接层走 `std.database.sql` 标准接口,SQL/DDL 生成走 `SqlDialect` 方言接口。新数据库接入 = 实现 `SqlDialect`(CRUD + 列类型映射 + 差异 DDL 语句)+ 提供对应 `std.database.sql` 驱动。 --- ## 许可证 MIT