package aceboot::orm.common
import std.collection.*
/**
* 关系元数据(@Entity 宏据 @ManyToOne/@OneToOne/@OneToMany/@ManyToMany 生成;零反射)。
* 描述「主实体的一个关系字段 → 目标实体」的导航信息,供 Repository 装填关系对象/集合。
* - fieldName:主实体上的关系字段名(如 "author"/"posts")。
* - kind:关系种类("many-to-one" | "one-to-one" | "one-to-many" | "many-to-many")。
* - targetTable:目标实体表名(默认按类型名小写;运行时优先用 targetType 经实体表注册表解析,故可留空/默认)。
* - targetType:目标实体类型名(如 "User"),运行时据此查实际表名,避免硬编码表名。
* - fkColumn:外键列名,语义随 kind:
* many-to-one/one-to-one(owner):**主表**上指向 target.id 的外键列(如 "authorId")。
* one-to-many:**目标表**上指向主表 id 的外键列(反向,如 "authorId")。
* many-to-many:不用(走 join 表)。
* - targetIdColumn:目标表主键列名(默认 "id")。
* - joinTable/joinOwnerColumn/joinTargetColumn:many-to-many 的连接表名与两侧外键列。
*/
public class RelationSpec {
public let fieldName: String
public let kind: String
public let targetTable: String
public let targetType: String
public let fkColumn: String
public let targetIdColumn: String
public let joinTable: String
public let joinOwnerColumn: String
public let joinTargetColumn: String
// 是否预加载(eager):true 时 findById/findAll 自动装填该关系(默认 false,须显式 findOne/loadCollection)。
public let eager: Bool
// 是否级联持久化(cascade):true 时保存主实体沿本关系递归保存目标对象/集合(默认 false)。
// 本波仅落地元数据基座,级联写语义放到后续「级联保存」波。
public let cascade: Bool
public init(fieldName: String, kind: String, targetTable: String, fkColumn: String,
targetIdColumn!: String = "id", targetType!: String = "", joinTable!: String = "",
joinOwnerColumn!: String = "", joinTargetColumn!: String = "", eager!: Bool = false,
cascade!: Bool = false) {
this.fieldName = fieldName
this.kind = kind
this.targetTable = targetTable
this.fkColumn = fkColumn
this.targetIdColumn = targetIdColumn
this.targetType = targetType
this.joinTable = joinTable
this.joinOwnerColumn = joinOwnerColumn
this.joinTargetColumn = joinTargetColumn
this.eager = eager
this.cascade = cascade
}
/** 是否为「一对多/多对多」集合关系(二次查询装填,而非单对象 JOIN)。 */
public func isCollection(): Bool {
kind == "one-to-many" || kind == "many-to-many"
}
}
// 实体类型名 → 表名(@Entity 宏在程序启动期自注册)。关系只声明目标**类型**,
// 运行时据此解析实际表名,避免在关系标注里硬编码目标表名。
let _entityTables = HashMap<String, String>()
/** @Entity 宏生成调用:登记实体类型名 → 表名。 */
public func registerEntityTable(typeName: String, table: String): Unit {
_entityTables[typeName] = table
}
/** 据实体类型名取表名(无则 None)。 */
public func lookupEntityTable(typeName: String): ?String {
_entityTables.get(typeName)
}
/**
* 非泛型的关系映射器视图(@Entity 宏让每个 XMapper 同时实现之)。
* findOne 装填关系对象时不知道目标实体的具体泛型类型,故用本接口擦除类型:
* - relTable/relIdColumn:目标表名与主键列名(拼 JOIN 与前缀剥离用)。
* - relColumns:目标表全部列元数据(决定 SELECT 哪些列、加什么前缀)。
* - relFromRow:由(已剥前缀的)行构造目标实体,返回 Any(由主映射器 setRelation 下转回填)。
*/
public interface RelMapper {
func relTable(): String
func relIdColumn(): String
func relColumns(): Array<ColumnSpec>
func relFromRow(r: Row): Any
/**
* 类型擦除持久化(级联/嵌套写基座,零反射):把一个擦除为 Any 的目标实体保存进库并返回其主键。
* 仅 @Entity 宏生成的映射器实现——宏内 `(e as 具体实体)` 下转后走 `Repository<具体实体>(ds, 映射器).save`,
* 故主键可经引用语义回填到传入对象。调用方经 lookupRelationMapper(targetTable) 取得目标映射器再调用之。
*/
func relSave(ds: DataSource, e: Any): Int64
/**
* 类型擦除嵌套装填(点号路径 findOne 基座,零反射):在一个擦除为 Any 的目标实体上递归装填其
* (可再嵌套的)关系。ds 由父 Repository 透传;默认空操作(前向兼容),@Entity 宏对每个实体生成覆盖——
* 宏内 `(e as 具体实体)` 下转后走 `Repository<具体实体>(ds, 映射器).loadRelations(...)`,故递归装填经引用语义
* 就地回填到传入对象(即父实体已持有的同一 class 引用)。调用方经 lookupRelationMapper(targetTable) 取得。
*/
func relLoadInto(_: DataSource, _: Any, _: Array<String>): Unit {}
}
// 表名 → 关系映射器工厂(@Entity 宏在程序启动期自注册,findOne 据 targetTable 查得目标映射器)。
let _relMappers = HashMap<String, () -> RelMapper>()
/**
* @Entity 宏生成调用:登记一个表的关系映射器工厂(零反射,启动期自注册)。
* findOne 据关系的 targetTable 查得目标实体的 RelMapper,用其列元数据/行构造装填关系对象。
*/
public func registerRelationMapper(table: String, factory: () -> RelMapper): Unit {
_relMappers[table] = factory
}
/** 据表名取关系映射器实例(无则 None)。 */
public func lookupRelationMapper(table: String): ?RelMapper {
match (_relMappers.get(table)) {
case Some(f) => Some(f())
case None => None
}
}