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
+188
View File
@@ -0,0 +1,188 @@
/*
* Copyright (c) 2025 SimcuTeam. All rights reserved.
* 查询构建器:条件/排序/分页 → SQL,委托执行回调物化结果。
*
* 解耦设计:QueryBuilder 不直接依赖 DbContext/驱动。
* 构造时传入 执行回调 (sql, params) -> ArrayList<T>,由 DbContext 提供。
*/
package simcu::orm.query
import std.collection.*
import simcu::orm.model.*
import simcu::orm.sql.*
/**
* 分页结果。
*/
public class PagedResult<T> {
/// 当前页数据
public var items: ArrayList<T> = ArrayList()
/// 满足条件的总条数
public var total: Int64 = 0
/// 页码(从 1 开始)
public var page: Int64 = 1
/// 每页条数
public var pageSize: Int64 = 20
public init() {}
public init(items: ArrayList<T>, total: Int64, page: Int64, pageSize: Int64) {
this.items = items
this.total = total
this.page = page
this.pageSize = pageSize
}
/// 总页数
public func totalPages(): Int64 {
if (pageSize <= 0) {
return 0
}
(total + pageSize - 1) / pageSize
}
}
/**
* 查询构建器(对齐 EF Core IQueryable 的常用子集)。
*/
public class QueryBuilder<T> {
private let _model: EntityModel
private let _dialect: ISqlDialect
private let _executor: (String, ArrayList<Any>) -> ArrayList<T>
private let _counter: (String, ArrayList<Any>) -> Int64
private let _whereParts = ArrayList<String>()
private let _params = ArrayList<Any>()
private let _orderByParts = ArrayList<String>()
private var _skip: Int64 = 0
private var _take: Int64 = 0
public init(model: EntityModel, dialect: ISqlDialect, executor: (String, ArrayList<Any>) -> ArrayList<T>,
counter: (String, ArrayList<Any>) -> Int64) {
this._model = model
this._dialect = dialect
this._executor = executor
this._counter = counter
}
/// filter("age > ?", [18]):原始片段,参数按 ? 顺序;片段中请直接写数据库列名
public func filter(condition: String, params: ArrayList<Any>): QueryBuilder<T> {
_whereParts.add("(${condition})")
for (p in params) {
_params.add(p)
}
this
}
/// filter("age", ">", 18):属性名/列名自动映射为列名
public func filter(property: String, op: String, value: Any): QueryBuilder<T> {
let col = _model.mapColumn(property)
_whereParts.add("(${_dialect.quoteName(col)} ${op} ?)")
_params.add(value)
this
}
public func orderBy(property: String): QueryBuilder<T> {
_orderByParts.add("${_dialect.quoteName(_model.mapColumn(property))} ASC")
this
}
public func orderByDesc(property: String): QueryBuilder<T> {
_orderByParts.add("${_dialect.quoteName(_model.mapColumn(property))} DESC")
this
}
public func skip(n: Int64): QueryBuilder<T> {
_skip = n
this
}
public func take(n: Int64): QueryBuilder<T> {
_take = n
this
}
/// 执行查询,返回实体列表
public func toList(): ArrayList<T> {
_executor(buildSelectSql(), copyParams())
}
/// 返回第一条(无结果返回 None)
public func first(): ?T {
let saved = _take
_take = 1
let list = toList()
_take = saved
if (list.size > 0) {
return Some(list[0])
}
None
}
/// 满足条件的总数
public func count(): Int64 {
let sql = _dialect.buildCount(_model.tableName, whereSql())
_counter(sql, copyParams())
}
/// 分页(page 从 1 开始)
public func page(page: Int64, pageSize: Int64): PagedResult<T> {
let total = count()
let savedSkip = _skip
let savedTake = _take
_skip = (page - 1) * pageSize
_take = pageSize
let items = toList()
_skip = savedSkip
_take = savedTake
PagedResult<T>(items, total, page, pageSize)
}
private func buildSelectSql(): String {
_dialect.buildSelect(_model.tableName, selectColumns(), whereSql(), orderBySql(), _skip, _take)
}
/// 物化列(与属性顺序一致,结果集按此索引读取)
private func selectColumns(): ArrayList<String> {
let cols = ArrayList<String>()
for (p in _model.properties) {
cols.add(p.columnName)
}
cols
}
private func whereSql(): String {
var sb = StringBuilder()
var first = true
for (w in _whereParts) {
if (!first) {
sb.append(" AND ")
}
sb.append(w)
first = false
}
sb.toString()
}
private func orderBySql(): String {
var sb = StringBuilder()
var first = true
for (o in _orderByParts) {
if (!first) {
sb.append(", ")
}
sb.append(o)
first = false
}
sb.toString()
}
private func copyParams(): ArrayList<Any> {
let copy = ArrayList<Any>()
for (p in _params) {
copy.add(p)
}
copy
}
}