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