-
Notifications
You must be signed in to change notification settings - Fork 0
源码导读
-
面向接口编程与抽象设计
2.1 选型考虑
- 接口定义:面向接口编程,使用依赖注入。
- 依赖注入容器:
dig
或wire
。
2.2 实施建议
- 遵循SOLID原则,特别是接口隔离原则。l
- 使用依赖注入容器简化管理。
-
Go反射与unsafe编程
3.1 选型考虑
- 反射支持:选择支持Go反射特性的ORM框架。
- 安全性:慎用
unsafe
包,确保安全性。
3.2 实施建议
- 封装反射逻辑,提高可读性。
- 对使用
unsafe
的代码进行仔细审查。
-
Go并发编程
4.1 选型考虑
- Go协程与通道:选择适合Go并发编程的ORM框架。
- 内存分配:理解Go的内存分配模型。
4.2 实施建议
- 确保ORM框架是并发安全的。
- 了解Go调度器工作原理。
-
SQL编程
5.1 选型考虑
- SQL生成与执行:选择支持良好的SQL生成与执行功能的ORM框架。
- 事务支持:确保ORM框架提供良好的事务支持。
5.2 实施建议
- 理解并优化生成的SQL语句。
- 选择稳定性能良好的数据库驱动。
-
结论
综合考虑面向接口设计、反射与unsafe使用、并发编程和SQL操作等。确保所选框架在满足业务需求的同时,提供高效、安全、可测试的解决方案。了解框架文档、社区活跃度,以获得及时的支持和帮助。
// Querier sql 查询语句抽象
type Querier[T any] interface {
Get(ctx context.Context) (*T, error)
GetMulti(ctx context.Context) ([]*T, error)
}
// Executor insert update 语句执行抽象
type Executor interface {
Exec(ctx context.Context) Result
}
// Query sql 中间结构体
// SQL: sql 语句
// Args: sql 语句中的占位符参数
type Query struct {
SQL string
Args []any
}
// QueryBuilder 构造 sql 语句的抽象
type QueryBuilder interface {
Build() (*Query, error)
}
// Session 抽象的数据库操作接口
// 包含 事务 与 非事务
type Session interface {
queryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
execContext(ctx context.Context, query string, args ...any) (sql.Result, error)
}
// Registry 元数据注册中心的抽象
type Registry interface {
// Get 查找元数据
Get(val any) (*Model, error)
// Register 注册一个模型
Register(val any, opts ...Option) (*Model, error)
}
存储 元数据(用户结构体 与 操作数据对象)
type registry struct {
models sync.Map
}
Registry 的具体实现的底层数据结构采用 go 提供的 sync.Map 保证了并发安全
核心方法
func (r *registry) Get(val any) (*Model, error)
func (r *registry) Register(val any, opts ...Option)
// 接受一级结构体指针
// parseModel 支持从标签中提取自定义设置
// 标签形式 orm:"key1=value1,key2=value2"
func (r *registry) parseModel(val any) (*Model, error)
func (r *registry) parseTag(tag reflect.StructTag) (map[string]string, error)
用户结构体名 -> Model -> database.sql
type Model struct {
// TableName 结构体对应的表名
TableName string
Fields []*Field
FieldMap map[string]*Field
ColumnMap map[string]*Field
}
// Field 字段
type Field struct {
ColName string
GoName string
Type reflect.Type
Index int
// Offset 相对于对象起始地址的字段偏移量
Offset uintptr
}
扩展 Model 属性
type Option func(m *Model) error
WithColumnName
func WithColumnName(field string, columnName string) Option {
return func(model *Model) error {
fd, ok := model.FieldMap[field]
if !ok {
return errs.NewErrUnknownField(field)
}
// 注意,这里我们根本没有检测 ColName 会不会是空字符串
// 因为正常情况下,用户都不会写错
// 即便写错了,也很容易在测试中发现
fd.ColName = columnName
return nil
}
}
支持的 tag
const (
tagKeyColumn = "column"
)
// TableName 用户实现这个接口来返回自定义的表名
type TableName interface {
TableName() string
}
TODO: picture
调用方创建模型注册中心 -> 调用方向数据中心注册元数据
调用方向数据中获取元数据 -> 数据中心查找响应的元数据 (如果没找到就注册该条元数据, 懒加载实现)
func (r *registry) Register(val any, opts ...Option) (*Model, error)
-> func (r *registry) parseModel(val any) (*Model, error)
-> func (r *registry) parseTag(tag reflect.StructTag) (map[string]string, error)
func (r *registry) Get(val any) (*Model, error) => 是否已有元数据
-> func (r *registry) Register(val any, opts ...Option) (*Model, error)
-> func (r *registry) parseModel(val any) (*Model, error)
-> func (r *registry) parseTag(tag reflect.StructTag) (map[string]string, error)
获取调用方结构体反射类型 (check接受一级结构体指针)
-> 逐一解析结构体字段
-> 解析其 tag 填充 Field 结构体
// 接受一级结构体指针
// parseModel 支持从标签中提取自定义设置
// 标签形式 orm:"key1=value1,key2=value2"
func (r *registry) parseModel(val any) (*Model, error) {
typ := reflect.TypeOf(val)
if typ.Kind() != reflect.Ptr ||
typ.Elem().Kind() != reflect.Struct {
return nil, errs.ErrPointerOnly
}
typ = typ.Elem()
// 获得字段的数量
numField := typ.NumField()
fds := make(map[string]*Field, numField)
fields := make([]*Field, 0, numField)
colMap := make(map[string]*Field, numField)
for i := 0; i < numField; i++ {
fdType := typ.Field(i)
tags, err := r.parseTag(fdType.Tag)
if err != nil {
return nil, err
}
colName := tags[tagKeyColumn]
if colName == "" {
colName = underscoreName(fdType.Name)
}
f := &Field{
ColName: colName,
Type: fdType.Type,
GoName: fdType.Name,
Offset: fdType.Offset,
Index: i,
}
fds[fdType.Name] = f
fields = append(fields, f)
colMap[colName] = f
}
var tableName string
if tn, ok := val.(TableName); ok {
tableName = tn.TableName()
}
if tableName == "" {
tableName = underscoreName(typ.Name())
}
return &Model{
TableName: tableName,
Fields: fields,
FieldMap: fds,
ColumnMap: colMap,
}, nil
}
type DB struct {
dialect Dialect
r model.Registry
db *sql.DB
valCreator valuer.Creator
}
拥有元数据注册中心, 维护与数据库的连接, 并提供数据库查询结果集向对象的映射实现
// Open 创建一个 DB 实例。
// 默认情况下,该 DB 将使用 MySQL 作为方言
// 如果你使用了其它数据库,可以使用 DBWithDialect 指定
func Open(driver string, dsn string, opts ...DBOption) (*DB, error) {
db, err := sql.Open(driver, dsn)
if err != nil {
return nil, err
}
return OpenDB(db, opts...)
}
// 语法糖
func OpenDB(db *sql.DB, opts ...DBOption) (*DB, error)
func MustNewDB(driver string, dsn string, opts ...DBOption) *DB
func (db *DB) queryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error) {
return db.db.QueryContext(ctx, query, args...)
}
func (db *DB) execContext(ctx context.Context, query string, args ...any) (sql.Result, error) {
return db.db.ExecContext(ctx, query, args...)
}
// Tx 继承 Session 操作数据库
type Tx struct {
tx *sql.Tx
db *DB
}
事务类图:
classDiagram
class Dialect
class Registry
class DB
class Creator
class Middleware
class DBOption {
+DBOption(*DB)
}
class DB {
- dialect: Dialect
- r: model.Registry
- db: *sql.DB
- valCreator: valuer.Creator
- mdls: []Middleware
+ Open(driver: string, dsn: string, opts: ...DBOption): (*DB, error)
+ OpenDB(db: *sql.DB, opts: ...DBOption): (*DB, error)
+ DBWithDialect(dialect: Dialect): DBOption
+ DBWithRegistry(r: model.Registry): DBOption
+ DBUseReflectValuer(): DBOption
+ DBUseMiddlewares(mdls: ...Middleware): DBOption
+ MustNewDB(driver: string, dsn: string, opts: ...DBOption): *DB
+ Close(): error
+ Wait(): error
+ queryContext(ctx: context.Context, query: string, args: any): (*sql.Rows, error)
+ execContext(ctx: context.Context, query: string, args: any): (sql.Result, error)
+ BeginTx(ctx: context.Context, opts: *sql.TxOptions): (*Tx, error)
+ DoTx(ctx: context.Context, fn: func(ctx: context.Context, tx: *Tx) error, opts: *sql.TxOptions): error
}
class Tx {
- tx: *sql.Tx
- db: *DB
+ Commit(): error
+ Rollback(): error
+ RollbackIfNotCommit(): error
}
DB "1" *-- "1" Dialect
DB "1" *-- "0..*" DBOption
DB "1" *-- "1" sql_DB
DB "1" *-- "1" Creator
DB "1" *-- "1" Registry
DB "1" *-- "0..*" Middleware
Tx "1" *-- "1" DB
type DBOption func(*DB)
DBWithRegistry 更换数据中心实现
func DBWithRegistry(r model.Registry) DBOption {
return func(db *DB) {
db.r = r
}
}
DBUseReflectValuer 更改数据返回结果实现
func DBUseReflectValuer() DBOption {
return func(db *DB) {
db.valCreator = valuer.NewReflectValue
}
}
优雅启动, 确保数据正确连接
func (db *DB) Wait() error {
err := db.db.Ping()
for errors.Is(err, driver.ErrBadConn) {
log.Println("数据库启动中")
err = db.db.Ping()
}
return nil
}
在返回数据库查询查询结果时, 填充数据
// Value 是对结构体实例的内部抽象
type Value interface {
// Field 返回字段对应的值
Field(name string) (any, error)
// SetColumns 设置新值
SetColumns(rows *sql.Rows) error
}
type Creator func(val any, meta *model.Model) Value
类型
// reflectValue 基于反射的 Value
type reflectValue struct {
val reflect.Value
meta *model.Model
}
构造方法
// NewReflectValue 返回一个封装好的,基于反射实现的 Value
// 输入 val 必须是一个指向结构体实例的指针,而不能是任何其它类型
func NewReflectValue(val interface{}, meta *model.Model) Value {
return reflectValue{
val: reflect.ValueOf(val).Elem(),
meta: meta,
}
}
填充数据库查询结果
func (r reflectValue) SetColumns(rows *sql.Rows) error
反射获取查询返回结果
用反射填充 valuer 对象值
类型
type unsafeValue struct {
// 基准地址
addr unsafe.Pointer
meta *model.Model
}
构造方法
func NewUnsafeValue(val interface{}, meta *model.Model) Value {
return unsafeValue{
addr: unsafe.Pointer(reflect.ValueOf(val).Pointer()),
meta: meta,
}
}
填充数据库查询结果
func (u unsafeValue) SetColumns(rows *sql.Rows) error
根据 unsafe 基准地址 + offset 在对应位置, 利用反射在当前位置开辟一个新对象
把其地址指向查询返回结果
func (u unsafeValue) Field(name string) (any, error)
// 执行命令: go test -bench=BenchmarkQuerier_Get -benchmem -benchtime=10000x
// goos: windows
// goarch: amd64
// pkg: LORM/v8
// cpu: Intel(R) Core(TM) i5-10210U CPU @ 1.60GHz
// BenchmarkQuerier_Get/unsafe-8 10000 418712 ns/op 3324 B/op 112 allocs/op
// BenchmarkQuerier_Get/reflect-8 10000 1462437 ns/op 3503 B/op 120 allocs/op
// PASS
// ok LORM/v8 19.787s
// 可以看出 unsafe 的性能远远快于直接使用 reflect
Expression 代表语句,或者语句的部分
// Expression 代表语句,或者语句的部分
type Expression interface {
expr()
}
Assignable 标记接口
// Assignable 标记接口,
// 实现该接口意味着可以用于赋值语句,
// 用于在 UPDATE 和 UPSERT 中
type Assignable interface {
assign()
}
Selectable select 语句中 colume, rawexpr, aggregate 的抽象
// Selectable select 语句中 colume, rawexpr, aggregate 的抽象
type Selectable interface {
selectable()
}
- Expression
// Predicate 代表一个查询条件
// Predicate 可以通过和 Predicate 组合构成复杂的查询条件
type Predicate struct {
left Expression
op op
right Expression
}
- Expression
- Selectable
// Aggregate 代表聚合函数,例如 AVG, MAX, MIN 等
type Aggregate struct {
fn string
arg string
alias string
}
- Expression
- Selectable
- Assignable
type Column struct {
name string
alias string
}
- Expression
- Selectable
- Assignable
// RawExpr 原生 sql 语句
type RawExpr struct {
raw string
args []any
}
- Expression
type binaryExpr struct {
left Expression
op op
right Expression
}
type MathExpr binaryExpr
func (m MathExpr) Add(val interface{}) MathExpr
func (m MathExpr) Multi(val interface{}) MathExp
type value struct {
val any
}
type builder struct {
// 构造 SQL
sb strings.Builder
// 存放 SQL 参数
args []any
// 存放当前对象的元数据信息
model *model.Model
// 方言抽象
dialect Dialect
quoter byte
}
兼容不同数据库的方言
type Dialect interface {
// quoter 返回一个引号,引用列名,表名的引号
quoter() byte
// buildUpsert 构造插入冲突部分
buildUpsert(b *builder, odk *Upsert) error
}
mysql, sqlite3 方言实现
var (
MySQL Dialect = &mysqlDialect{}
SQLite3 Dialect = &sqlite3Dialect{}
)
// buildColumn 构造列
func (b *builder) buildColumn(fd string) error
// 构造方言的quote
func (b *builder) quote(name string)
// 构造原生表达式
func (b *builder) raw(r RawExpr)
// 构造 sql 语句参数
func (b *builder) addArgs(args ...any)
// 构造条件表达式
func (b *builder) buildPredicates(ps []Predicate) error
// 构造表达式
func (b *builder) buildExpression(e Expression) error
// 构造二分表达式
func (b *builder) buildBinaryExpr(e binaryExpr) error
流程:
err := b.buildSubExpr(e.left) => 构造做部分
左右部分有可能又是一个 Predicate, 所以递归构造, 到最后为 raw, value
// 构造二分表达式的右半部分
func (b *builder) buildSubExpr(subExpr Expression) error
// 构造 Aggregate 表达式
func (b *builder) buildAggregate(a Aggregate, useAlias bool) error
// 构造 As 别名
func (b *builder) buildAs(alias string)
构造 select 语句发送给数据库处理
用户需传入要查询的结构体(泛型的使用)
采用构造复杂结构的 Build 模式, 链式调用
// Selector 用于构造 SELECT 语句
type Selector[T any] struct {
builder
table string
db *DB
where []Predicate
having []Predicate
columns []Selectable
groupBy []Column
limit int
offset int
}
// NewSelector 构造 Selector
func NewSelector[T any](db *DB) *Selector[T] {
return &Selector[T]{
builder: builder{
dialect: db.dialect,
quoter: db.dialect.quoter(),
},
db: db,
}
}
依次构造: SELECT 列名... FROM 表名 WHERE 条件 GROUP BY 列名 HAVING 条件 LIMIT limit OFFSET offest
列名, 表名有不同数据库的方言
// Build 构造 sql 查询语句, 底层调用 database.sql 查询数据库
func (s *Selector[T]) Build() (*Query, error)
其余方法及 Build() 流程
// Select 选择要查询的列
func (s *Selector[T]) Select(cols ...Selectable) *Selector[T]
// From 指定表名,如果是空字符串,那么将会使用默认表名
func (s *Selector[T]) From(tbl string) *Selector[T]
func (s *Selector[T]) buildColumns() error
func (s *Selector[T]) buildColumn(c Column, useAlias bool) error
// Where 用于构造 WHERE 查询条件。如果 ps 长度为 0,那么不会构造 WHERE 部分
func (s *Selector[T]) Where(ps ...Predicate) *Selector[T]
// GroupBy 设置 group by 子句
func (s *Selector[T]) GroupBy(cols ...Column) *Selector[T]
func (s *Selector[T]) Having(ps ...Predicate) *Selector[T]
func (s *Selector[T]) Offset(offset int) *Selector[T]
func (s *Selector[T]) Limit(limit int) *Selector[T]
Build() 流程:
s.model, err = s.db.r.Get(&t) => 从注册中心获取模型
s.sb.WriteString("SELECT ") => 构造固定语法
if err = s.buildColumns(); => 构造列
-> for i, c := range s.columns => 遍历所有列名
-> switch val := c.(type) => 判断列类型
-> case Column: => 普通列名构造
if err := s.buildColumn(val, true);
-> case Aggregate: => 构造 Aggregate 类型列
if err := s.buildAggregate(val, true);
-> case RawExpr: => 构造原生 sql 列
s.raw(val)
s.sb.WriteString(" FROM ") => 构造固定语法
s.quote(s.model.TableName) || s.sb.WriteString(s.table) => 构造表名
if len(s.where) > 0; if err = s.buildPredicates(s.where); => 构造 where 条件语句
-> for i := 1; i < len(ps); i++ { => 不同的 Predicate And 连接
p = p.And(ps[i])
-> b.buildExpression(p) => 构造表达式
if len(s.groupBy) > 0 => 构造 group by
if len(s.having) > 0 => 构造 having
if s.limit > 0 => 构造 limit
if s.offset > 0 => 构造 offset
Build() 流程图
graph TD
start(["调用selector的Build()"]) --> selector[/将selector字段作为输入/] -->
A(从注册中心获取模型) --> B(构造固定语法 SELECT)
B --> C(构造select选中列)
C ==> switch{判断列类型}
switch -->|Column| E(普通列名构造)
switch -->|Aggregate| F(构造Aggregate类型列)
switch -->|RawExpr| G(构造原生sql列)
E --> from(构造固定语法 FROM)
F --> from
G --> from
from --> table(构造表名)
from --> join(构造Join语句)
from --> subquery(构造子查询语句)
table --> where(构造WHERE条件语句)
join --> where
subquery --> where
where --> K(不同的Predicate AND连接)
K --> L(构造表达式)
L --> group(构造GROUP BY)
group --> having(构造HAVING)
having --> limit(构造LIMIT)
limit --> offset(构造OFFSET) -->
out[/构建完成的sql/] -->
finish(["selector的Build()结束"])
func (s *Selector[T]) Get(ctx context.Context) (*T, error)
func (s *Selector[T]) GetMulti(ctx context.Context) (res []*T, err error)
q, err := s.Build() => 构造 sql
rows, err := db.QueryContext(ctx, q.SQL, q.Args...) => 执行 sql
val := s.db.valCreator(tp, meta) => 构造 valuer
err = val.SetColumns(rows) => 填充数据
Get() 与 GetMulti() 流程图:
graph TD
start(["调用selector的Get() 或 GetMulti()()"]) -->
selector(/将selector字段作为输入/) -->
build(构造SQL语句) -->
exec(执行SQL) -->
sql[(查询数据库)] -->
|数据| valuer(构造Valuer) -->
SetColumns(填充数据) -->
data(/返回结果集/) -->
finish([结束])
- Executor
- QueryBuilder
type Deleter[T any] struct {
builder
tableName string
where []Predicate
db *DB
}
d.model, err = d.db.r.Get(&t) => 获取模型
d.sb.WriteString("DELETE FROM ") => 构造固定部分
if d.tableName == "" => 获取删除表名
if len(d.where) > 0 => 是否存在 where 条件
if err = d.buildPredicates(d.where); err != nil => 构造 where 条件
q, err := d.Build() => 构造 sql
res, err := d.db.db.ExecContext(ctx, q.SQL, q.Args...) => 执行 sql
- Executor
- QueryBuilder
type Inserter[T any] struct {
builder
values []*T
db *DB
columns []string
upsert *Upsert
}
m, err := i.db.r.Get(i.values[0]) => 获取模型
i.sb.WriteString("INSERT INTO ") => 构造固定部分
i.quote(m.TableName)
i.sb.WriteString("(")
fields := m.Fields => 获取列名
if len(i.columns) != 0
for idx, fd := range fields => 构造 insert 列名
i.sb.WriteString(") VALUES")
for vIdx, val := range i.values => 遍历传入参数, 构造 insert 值
-> refVal := i.db.valCreator(val, i.model) => 构造 valuer
for fIdx, field := range fields => 遍历列名
-> fdVal, err := refVal.Field(field.GoName) => 获取列名值, 加入 inserter args, 替换 ?
if i.upsert != nil => 支持 upsert 语句, 不同数据库格式不同
q, err := i.Build() => 构造 sql
res, err := i.db.db.ExecContext(ctx, q.SQL, q.Args...) => 执行 sql
- Executor
- QueryBuilder
u.model, err = u.db.r.Get(&t) => 获取模型
u.sb.WriteString("UPDATE ") => 构造固定部分
u.quote(u.model.TableName)
u.sb.WriteString(" SET ")
val := u.db.valCreator(u.val, u.model) => 构造 valuer
for i, a := range u.assigns => 遍历所有要更改的字段
-> switch assign := a.(type) => 判断不同的类型
if len(u.where) > 0 => 构造 where
q, err := u.Build() => 构造 sql
res, err := u.db.db.ExecContext(ctx, q.SQL, q.Args...) => 执行 sql
// Middleware 函数式中间件
type Middleware func(next HandleFunc) HandleFunc
type HandleFunc func(ctx context.Context, qc *QueryContext) *QueryResult
// QueryContext Middleware 向下传递的 Context
type QueryContext struct {
// Type 声明查询类型。即 SELECT, UPDATE, DELETE 和 INSERT
Type string
Builder QueryBuilder
// Model 向 Middleware 提供当前 sql 操作的元数据信息
Model *model.Model
}
// QueryResult 每个 Handler 向上返回的结果
type QueryResult struct {
Result any
Err error
}
这个项目作为Go ORM框架的中间件,在系统测试中确实需要特别关注各个组成部分的单元测试,以确保整体的稳定性、安全性和降低错误率。以下是对这些方面的更详细扩展:
单元测试的重要性:
- 系统稳定性: 单元测试有助于发现和修复代码中的潜在问题,确保每个组件都按照预期工作。通过检查每个模块的输入和输出,可以识别潜在的错误和边界情况,从而提高整个系统的稳定性。
- 安全性: 在ORM框架中,数据的处理和存储是至关重要的。通过单元测试,可以验证数据的正确性、完整性和安全性。检测潜在的安全漏洞,例如SQL注入、数据泄漏等,有助于提高系统的安全性。
- 错误率降低: 单元测试是在开发早期就发现和解决问题的有效手段。通过在每个组件上运行测试,可以迅速发现并修复潜在的错误,降低后期维护的成本,并提高整个项目的质量。
单元测试用例的设计:
- 模块独立性: 每个单元测试用例应该专注于测试一个特定的组件或模块,确保它在隔离的环境中运行。这有助于迅速定位和解决问题。
- 边界条件: 单元测试应覆盖各种输入情况,包括正常情况和边界条件。这可以确保系统在各种情况下都能正确运行,提高系统的健壮性。
- 错误处理: 测试应该包括对错误处理代码的覆盖,以确保系统在出现异常情况时能够正确响应并提供有用的错误信息。
- 性能测试: 虽然性能测试通常在系统测试阶段进行,但在单元测试中也可以包含一些基本的性能测试,以确保每个组件在合理的时间内完成任务。
- 数据一致性: 对于ORM框架,确保数据的一致性和正确性是至关重要的。单元测试应该验证数据的正确插入、更新和删除,并确保查询结果是准确的。
- Mock和Stub的使用: 使用Mock和Stub技术模拟外部依赖,以确保每个组件在测试中的独立性。这有助于在不同组件之间解耦,使测试更加可靠和可维护。
测试覆盖率的监控:
使用工具来监控测试覆盖率,确保所有关键代码路径都得到了测试。高测试覆盖率通常与更高的代码质量和可维护性相关。
持续集成和持续部署:
将单元测试集成到持续集成和持续部署流程中,以确保每次代码更改都通过了所有单元测试。这有助于快速检测和修复潜在问题,保持整个系统的健康状态。
通过认真设计和执行单元测试,您可以确保ORM框架的各个组成部分在各种情况下都能够正确、高效地运行,从而提高系统的可靠性和稳定性。
TestModelWithTableName
测试用例设计:
-
设置空字符串表名:
- 描述:测试是否允许将表名设置为空字符串。
- 输入:
WithTableName("")
。 - 期望结果:返回的
Model
的TableName
应为空字符串。
-
设置非空字符串表名:
- 描述:测试是否能够成功设置非空字符串的表名。
- 输入:
WithTableName("test_model_t")
。 - 期望结果:返回的
Model
的TableName
应为 "test_model_t"。
TestWithColumnName
测试用例设计:
-
设置新的列名:
- 描述:测试是否能够成功设置新的列名。
- 输入:
WithColumnName("FirstName", "first_name_new")
。 - 期望结果:返回的
Model
的对应字段的列名应为 "first_name_new"。
-
设置空字符串的新列名:
- 描述:测试是否允许将新列名设置为空字符串。
- 输入:
WithColumnName("FirstName", "")
。 - 期望结果:返回的
Model
的对应字段的列名应为空字符串。
-
设置不存在的字段的新列名:
- 描述:测试是否在设置新列名时能够正确处理不存在的字段。
- 输入:
WithColumnName("FirstNameXXX", "first_name")
。 - 期望结果:返回错误
ErrUnknownField("FirstNameXXX")
。
TestRegistry_get
测试用例设计:
-
测试 Model 类型:
- 描述:检查是否能够正确处理 Model 类型,即非指针的类型。
- 输入:
TestModel{}
。 - 期望结果:返回错误
errors.New("orm: 只支持一级指针作为输入,例如 *User")
。
-
测试指针类型:
- 描述:检查是否能够正确处理指针类型的 Model。
- 输入:
&TestModel{}
。 - 期望结果:返回包含正确表名、字段等信息的
Model
。
-
测试多级指针:
- 描述:检查是否能够正确处理多级指针。
- 输入:
func() any { val := &TestModel{}; return &val }()
。 - 期望结果:返回错误
errors.New("orm: 只支持一级指针作为输入,例如 *User")
。
-
测试非指针类型:
- 描述:检查是否能够正确处理非指针的 Map、Slice 和基础类型。
- 输入:
map[string]string{}
,[]int{}
,0
。 - 期望结果:返回错误
errors.New("orm: 只支持一级指针作为输入,例如 *User")
。
-
测试带有 column tag 的 Model:
- 描述:检查是否能够正确处理带有 column tag 的 Model。
- 输入:带有
column
tag 的结构体。 - 期望结果:返回包含正确表名、字段等信息的
Model
。
-
测试空 column tag:
- 描述:检查是否能够处理用户设置了
column
但没有指定值的情况。 - 输入:
type EmptyColumn struct { FirstName string "orm:\"column=\"" }
。 - 期望结果:返回包含正确表名、字段等信息的
Model
,其中列名为默认生成的。
- 描述:检查是否能够处理用户设置了
-
测试无效 tag:
- 描述:检查是否能够处理无效的 tag。
- 输入:
type InvalidTag struct { FirstName string "orm:\"column\"" }
。 - 期望结果:返回错误
errs.NewErrInvalidTagContent("column")
。
-
测试忽略的 tag:
- 描述:检查是否能够忽略无效的 tag 部分。
- 输入:
type IgnoreTag struct { FirstName string "orm:\"abc=abc\"" }
。 - 期望结果:返回包含正确表名、字段等信息的
Model
。
-
测试接口自定义模型信息:
- 描述:检查是否能够正确处理通过接口定义的自定义模型信息。
- 输入:
&CustomTableName{}
,&CustomTableNamePtr{}
,&EmptyTableName{}
。 - 期望结果:返回包含正确表名、字段等信息的
Model
。
Test_underscoreName
测试用例设计:
- 测试下划线转换:
- 描述:检查是否能够正确将大写字母和数字转换为下划线形式。
- 输入:多个字符串。
- 期望结果:返回正确的下划线形式的字符串。
TestReflectValue_Field
测试用例设计:
-
测试
NewReflectValue
函数:- 描述:测试
NewReflectValue
函数是否能够正确创建ReflectValue
实例,并且正确处理字段的获取。 - 输入:
NewReflectValue(&test.SimpleStruct{}, meta)
。 - 期望结果:返回正确的
ReflectValue
实例,并能够正确获取字段值。
- 描述:测试
-
测试零值情况:
- 描述:测试在结构体为零值的情况下,
ReflectValue
是否能够正确获取字段值。 - 输入:
entity := &test.SimpleStruct{}
。 - 期望结果:返回正确的字段值。
- 描述:测试在结构体为零值的情况下,
-
测试正常情况:
- 描述:测试在结构体为正常非零值的情况下,
ReflectValue
是否能够正确获取字段值。 - 输入:
entity := test.NewSimpleStruct(1)
。 - 期望结果:返回正确的字段值。
- 描述:测试在结构体为正常非零值的情况下,
-
测试字段为无效值:
- 描述:测试当请求获取的字段不存在时,
ReflectValue
是否能够正确处理并返回错误。 - 输入:
"UpdateTime"
字段。 - 期望结果:返回错误
errs.NewErrUnknownField("UpdateTime")
。
- 描述:测试当请求获取的字段不存在时,
Test_reflectValue_SetColumn
测试用例设计:
-
测试正常情况:
- 描述:测试在正常情况下,
ReflectValue
是否能够正确设置字段值。 - 输入:模拟数据库查询结果和结构体实例。
- 期望结果:返回正确设置后的结构体实例。
- 描述:测试在正常情况下,
-
测试字段不存在:
- 描述:测试当数据库查询结果中包含结构体中不存在的字段时,
ReflectValue
是否能够正确处理并返回错误。 - 输入:
map[string][]byte{"invalid_column": nil}
。 - 期望结果:返回错误
errs.NewErrUnknownColumn("invalid_column")
。
- 描述:测试当数据库查询结果中包含结构体中不存在的字段时,
Test_unsafeValue_SetColumn
测试用例设计:
-
测试正常情况:
- 描述:测试在正常情况下,
UnsafeValue
是否能够正确设置字段值。 - 输入:模拟数据库查询结果和结构体实例。
- 期望结果:返回正确设置后的结构体实例。
- 描述:测试在正常情况下,
-
测试字段不存在:
- 描述:测试当数据库查询结果中包含结构体中不存在的字段时,
UnsafeValue
是否能够正确处理并返回错误。 - 输入:
map[string][]byte{"invalid_column": nil}
。 - 期望结果:返回错误
errs.NewErrUnknownColumn("invalid_column")
。
- 描述:测试当数据库查询结果中包含结构体中不存在的字段时,
TestTx_Commit
测试用例设计:
- 正常提交事务:
- 描述:测试事务正常提交是否能够成功执行。
- 输入:
db.BeginTx(context.Background(), &sql.TxOptions{})
,然后调用tx.Commit()
。 - 期望结果:事务能够成功提交,不产生错误。
TestTx_Rollback
测试用例设计:
- 事务回滚:
- 描述:测试事务回滚是否能够成功执行。
- 输入:
db.BeginTx(context.Background(), &sql.TxOptions{})
,然后调用tx.Rollback()
。 - 期望结果:事务能够成功回滚,不产生错误。
TestSelector_Build() 测试用例设计
-
无 From 调用:
- 描述:测试在未调用
From
方法时,是否能够正确生成默认的 SQL 查询语句。 - 期望结果:生成的 SQL 语句为 "SELECT * FROM
test_model
;"。
- 描述:测试在未调用
-
调用 FROM 方法:
- 描述:测试在调用
From
方法时,是否能够正确生成指定表名的 SQL 查询语句。 - 期望结果:生成的 SQL 语句为 "SELECT * FROM
test_model_t
;"。
- 描述:测试在调用
-
调用 FROM 方法,传入空字符串:
- 描述:测试在调用
From
方法时传入空字符串,是否能够正确处理并生成默认的 SQL 查询语句。 - 期望结果:生成的 SQL 语句为 "SELECT * FROM
test_model
;"。
- 描述:测试在调用
-
调用 FROM 方法,传入带数据库的表名:
- 描述:测试在调用
From
方法时传入带数据库的表名,是否能够正确生成带有数据库的 SQL 查询语句。 - 期望结果:生成的 SQL 语句为 "SELECT * FROM
test_db
.test_model
;"。
- 描述:测试在调用
-
单一简单条件:
- 描述:测试在添加单一的简单条件时,是否能够正确生成带条件的 SQL 查询语句。
- 期望结果:生成的 SQL 语句为 "SELECT * FROM
test_model_t
WHEREid
= ?;",同时包含正确的参数。
-
多个条件:
- 描述:测试在添加多个条件时,是否能够正确生成带有多个条件的 SQL 查询语句。
- 期望结果:生成的 SQL 语句为 "SELECT * FROM
test_model
WHERE (age
> ?) AND (age
< ?);",同时包含正确的参数。
-
使用 AND 连接条件:
- 描述:测试在使用
And
连接条件时,是否能够正确生成带有 AND 连接的 SQL 查询语句。 - 期望结果:生成的 SQL 语句与第 6 个测试用例相同。
- 描述:测试在使用
-
使用 OR 连接条件:
- 描述:测试在使用
Or
连接条件时,是否能够正确生成带有 OR 连接的 SQL 查询语句。 - 期望结果:生成的 SQL 语句为 "SELECT * FROM
test_model
WHERE (age
> ?) OR (age
< ?);",同时包含正确的参数。
- 描述:测试在使用
-
使用 NOT 连接条件:
- 描述:测试在使用
Not
连接条件时,是否能够正确生成带有 NOT 连接的 SQL 查询语句。 - 期望结果:生成的 SQL 语句为 "SELECT * FROM
test_model
WHERE NOT (age
> ?);",同时包含正确的参数。
- 描述:测试在使用
-
非法列:
- 描述:测试在使用不存在的列名时,是否能够正确处理并返回预期的错误。
- 期望结果:返回一个
errs.NewErrUnknownField
的错误。
-
使用 RawExpr:
- 描述:测试在使用
RawExpr
时,是否能够正确生成带有原始表达式的 SQL 查询语句。 - 期望结果:生成的 SQL 语句为 "SELECT * FROM
test_model
WHEREage
< ?;",同时包含正确的参数。
- 描述:测试在使用
TestSelector_Get() 测试用例设计:
-
查询返回错误:
- 描述:测试当数据库查询返回错误时,
Get
方法是否能够正确处理并返回相应的错误。 - 期望结果:返回与查询错误相匹配的错误信息。
- 描述:测试当数据库查询返回错误时,
-
无行返回:
- 描述:测试当查询没有返回任何行时,
Get
方法是否能够正确处理并返回ErrNoRows
。 - 期望结果:返回
ErrNoRows
。
- 描述:测试当查询没有返回任何行时,
-
返回的列数过多:
- 描述:测试当查询返回的列数超过预期时,
Get
方法是否能够正确处理并返回ErrTooManyReturnedColumns
。 - 期望结果:返回
ErrTooManyReturnedColumns
。
- 描述:测试当查询返回的列数超过预期时,
-
获取数据成功:
- 描述:测试当查询成功返回数据时,
Get
方法是否能够正确处理并返回预期的数据。 - 期望结果:返回与查询结果相匹配的
TestModel
对象。
- 描述:测试当查询成功返回数据时,
在测试中,使用 sqlmock
模拟了数据库的行为,并为每个测试用例创建了相应的模拟数据。通过对每个测试用例的期望结果进行验证,确保了 Get
方法在不同情况下的正确性。在循环中,每个测试用例的查询模拟被期望并设置相应的返回值。
TestSelector_OffsetLimit 测试用例设计:
-
Offset 方法测试:
- 描述:测试
Offset
方法是否能够正确生成包含 Offset 的 SQL 查询语句。 - 期望结果:生成的 SQL 语句为 "SELECT * FROM
test_model
OFFSET ?;",同时包含正确的参数。
- 描述:测试
-
Limit 方法测试:
- 描述:测试
Limit
方法是否能够正确生成包含 Limit 的 SQL 查询语句。 - 期望结果:生成的 SQL 语句为 "SELECT * FROM
test_model
LIMIT ?;",同时包含正确的参数。
- 描述:测试
-
Limit 和 Offset 方法组合测试:
- 描述:测试当同时使用
Limit
和Offset
方法时,是否能够正确生成 SQL 查询语句。 - 期望结果:生成的 SQL 语句为 "SELECT * FROM
test_model
LIMIT ? OFFSET ?;",同时包含正确的参数。
- 描述:测试当同时使用
TestSelector_Having 测试用例设计:
-
GroupBy 后没有条件:
- 描述:测试在调用
GroupBy
后没有传递条件时,Having
方法是否能够正确生成 SQL 查询语句。 - 期望结果:生成的 SQL 语句为 "SELECT * FROM
test_model
GROUP BYage
;"。
- 描述:测试在调用
-
单一条件测试:
- 描述:测试在传递单一条件时,
Having
方法是否能够正确生成 SQL 查询语句。 - 期望结果:生成的 SQL 语句为 "SELECT * FROM
test_model
GROUP BYage
HAVINGfirst_name
= ?;",同时包含正确的参数。
- 描述:测试在传递单一条件时,
-
多个条件测试:
- 描述:测试在传递多个条件时,
Having
方法是否能够正确生成 SQL 查询语句。 - 期望结果:生成的 SQL 语句为 "SELECT * FROM
test_model
GROUP BYage
HAVING (first_name
= ?) AND (last_name
= ?);",同时包含正确的参数。
- 描述:测试在传递多个条件时,
-
聚合函数测试:
- 描述:测试在使用聚合函数时,
Having
方法是否能够正确生成 SQL 查询语句。 - 期望结果:生成的 SQL 语句为 "SELECT * FROM
test_model
GROUP BYage
HAVING AVG(age
) = ?;",同时包含正确的参数。
- 描述:测试在使用聚合函数时,
TestSelector_GroupBy() 测试用例设计:
-
GroupBy 后没有列名:
- 描述:测试在调用
GroupBy
后没有传递列名时,GroupBy
方法是否能够正确生成 SQL 查询语句。 - 期望结果:生成的 SQL 语句为 "SELECT * FROM
test_model
;"。
- 描述:测试在调用
-
单个列名测试:
- 描述:测试在传递单个列名时,
GroupBy
方法是否能够正确生成 SQL 查询语句。 - 期望结果:生成的 SQL 语句为 "SELECT * FROM
test_model
GROUP BYage
;"。
- 描述:测试在传递单个列名时,
-
多个列名测试:
- 描述:测试在传递多个列名时,
GroupBy
方法是否能够正确生成 SQL 查询语句。 - 期望结果:生成的 SQL 语句为 "SELECT * FROM
test_model
GROUP BYage
,first_name
;"。
- 描述:测试在传递多个列名时,
-
不存在的列名测试:
- 描述:测试在传递不存在的列名时,
GroupBy
方法是否能够正确处理并返回相应的错误。 - 期望结果:返回一个
errs.NewErrUnknownField
的错误。
- 描述:测试在传递不存在的列名时,
BenchmarkQuerier_Get() 性能测试:
这段代码包含了一个基准测试函数 BenchmarkQuerier_Get
,该函数对 Querier
接口的 Get
方法进行性能比较,使用了 unsafe
和 reflect
两种方式。
Benchmark 结果解释:
-
BenchmarkQuerier_Get/unsafe-8:
- 每次迭代花费时间:418,712 纳秒(ns)
- 每次迭代分配的内存:3,324 字节(B)
- 每次迭代的内存分配次数:112 次
-
BenchmarkQuerier_Get/reflect-8:
- 每次迭代花费时间:1,462,437 纳秒(ns)
- 每次迭代分配的内存:3,503 字节(B)
- 每次迭代的内存分配次数:120 次
性能比较:
- unsafe vs. reflect:
- 使用
unsafe
的性能远远快于直接使用reflect
。 -
unsafe
方式的迭代时间仅为reflect
方式的 1/3.5,内存分配次数也较少。
- 使用
测试配置:
-
测试环境:
- 操作系统: Windows
- 架构: AMD64
- CPU: Intel(R) Core(TM) i5-10210U CPU @ 1.60GHz
-
测试命令:
go test -bench=BenchmarkQuerier_Get -benchmem -benchtime=10000x
性能评估:
该基准测试明确表明,使用 unsafe
方式相对于 reflect
方式在性能上有显著的优势,因为它显著降低了每次迭代的时间和内存分配次数。因此,在追求性能的场景下,推荐使用 unsafe
方式。然而,使用 unsafe
需要谨慎,确保在使用过程中不会引起不安全的内存操作。
这段代码包含了一个针对 Deleter
接口的 Build
方法的测试函数 TestDeleter_Build
,测试了删除操作的 SQL 查询语句构建。
TestDeleter_Build() 测试用例设计:
-
无条件删除:
- 描述:测试在不添加任何条件的情况下,
Deleter
是否能够正确生成删除表中所有数据的 SQL 查询语句。 - 输入:
NewDeleter[TestModel](db).From("
test_model")
- 期望结果:生成的 SQL 语句为 "DELETE FROM
test_model
;"
- 描述:测试在不添加任何条件的情况下,
-
带条件删除:
- 描述:测试在添加条件的情况下,
Deleter
是否能够正确生成带有条件的删除 SQL 查询语句。 - 输入:
NewDeleter[TestModel](db).Where(C("Id").EQ(16))
- 期望结果:生成的 SQL 语句为 "DELETE FROM
test_model
WHEREid
= ?;",同时包含正确的参数。
- 描述:测试在添加条件的情况下,
-
From 和 Where 组合:
- 描述:测试在同时指定表名和条件的情况下,
Deleter
是否能够正确生成删除 SQL 查询语句。 - 输入:
NewDeleter[TestModel](db).From("
test_model").Where(C("Id").EQ(16))
- 期望结果:生成的 SQL 语句为 "DELETE FROM
test_model
WHEREid
= ?;",同时包含正确的参数。
- 描述:测试在同时指定表名和条件的情况下,
TestInserter_Build() 测试用例设计:
- 零行插入:
- 描述:测试插入操作时未提供任何值时是否能够返回正确的错误。
- 输入:NewInserterTestModel.Values()
- 期望结果:返回错误 errs.ErrInsertZeroRow。
- 单行插入:
- 描述:测试插入一行数据时是否能够正确生成 SQL 查询语句。
- 输入:NewInserterTestModel.Values(...)
- 期望结果:生成正确的 SQL 语句,包含正确的参数。
- 多行插入:
- 描述:测试插入多行数据时是否能够正确生成 SQL 查询语句。
- 输入:NewInserterTestModel.Values(..., ...)
- 期望结果:生成正确的 SQL 语句,包含正确的参数。
- 指定列插入:
- 描述:测试插入时指定列是否能够正确生成 SQL 查询语句。
- 输入:NewInserterTestModel.Values(...).Columns(...)
- 期望结果:生成正确的 SQL 语句,包含正确的参数。
- 指定列插入 - 无效列:
- 描述:测试插入时指定不存在的列是否能够返回正确的错误。
- 输入:NewInserterTestModel.Values(...).Columns("FirstName", "Invalid")
- 期望结果:返回错误 errs.NewErrUnknownField("Invalid")。
- upsert 操作:
- 描述:测试 upsert 操作时是否能够正确生成 SQL 查询语句。
- 输入:NewInserterTestModel.Values(...).OnDuplicateKey().Update(...)
- 期望结果:生成正确的 SQL 语句,包含正确的参数。
- upsert 操作 - 无效列:
- 描述:测试 upsert 操作时指定不存在的列是否能够返回正确的错误。
- 输入:NewInserterTestModel.Values(...).OnDuplicateKey().Update(Assign("Invalid", "Da"))
- 期望结果:返回错误 errs.NewErrUnknownField("Invalid")。
- upsert 操作 - 使用插入的值:
- 描述:测试 upsert 操作时是否能够使用原本插入的值。
- 输入:NewInserterTestModel.Values(...).OnDuplicateKey().Update(C("FirstName"), C("LastName"))
- 期望结果:生成正确的 SQL 语句,包含正确的参数。
TestUpsert_SQLite3_Build() 测试用例设计:
- upsert 操作 - SQLite3:
- 描述:测试在 SQLite3 中使用 upsert 操作时是否能够正确生成 SQL 查询语句。
- 输入:NewInserterTestModel.Values(...).OnDuplicateKey().ConflictColumns("Id").Update(...)
- 期望结果:生成正确的 SQL 语句,包含正确的参数。
- upsert 操作 - 无效列:
- 描述:测试在 SQLite3 中 upsert 操作时指定不存在的列是否能够返回正确的错误。
- 输入:NewInserterTestModel.Values(...).OnDuplicateKey().ConflictColumns("Id").Update(Assign("Invalid", "Da"))
- 期望结果:返回错误 errs.NewErrUnknownField("Invalid")。
- upsert 操作 - 冲突列无效:
- 描述:测试在 SQLite3 中 upsert 操作时指定冲突列不存在是否能够返回正确的错误。
- 输入:NewInserterTestModel.Values(...).OnDuplicateKey().ConflictColumns("Invalid").Update(Assign("FirstName", "Da"))
- 期望结果:返回错误 errs.NewErrUnknownField("Invalid")。
- upsert 操作 - 使用插入的值:
- 描述:测试在 SQLite3 中 upsert 操作时是否能够使用原本插入的值。
- 输入:NewInserterTestModel.Values(...).OnDuplicateKey().ConflictColumns("Id").Update(C("FirstName"), C("LastName"))
- 期望结果:生成正确的 SQL 语句,包含正确的参数。
TestUpdater_Build
() 测试用例设计:
-
无更新列:
- 描述:测试在更新操作时没有指定更新列是否能够返回正确的错误。
- 输入:
NewUpdater[TestModel](db)
- 期望结果:返回错误
errs.ErrNoUpdatedColumns
。
-
单列更新:
- 描述:测试更新操作时单列是否能够正确生成 SQL 查询语句。
- 输入:
NewUpdater[TestModel](db).Update(...).Set(C("Age"))
- 期望结果:生成正确的 SQL 语句,包含正确的参数。
-
多列更新:
- 描述:测试更新操作时多列是否能够正确生成 SQL 查询语句。
- 输入:
NewUpdater[TestModel](db).Update(...).Set(C("Age"), Assign("FirstName", "YangZhuolin"))
- 期望结果:生成正确的 SQL 语句,包含正确的参数。
-
带条件更新:
- 描述:测试更新操作时带有条件是否能够正确生成 SQL 查询语句。
- 输入:
NewUpdater[TestModel](db).Update(...).Set(...).Where(C("Id").EQ(1))
- 期望结果:生成正确的 SQL 语句,包含正确的参数。
-
增量更新:
- 描述:测试增量更新操作是否能够正确生成 SQL 查询语句。
- 输入:
NewUpdater[TestModel](db).Update(...).Set(Assign("Age", C("Age").Add(1)))
- 期望结果:生成正确的 SQL 语句,包含正确的参数。
-
增量更新 - 使用原始 SQL:
- 描述:测试使用原始 SQL 进行增量更新操作是否能够正确生成 SQL 查询语句。
- 输入:
NewUpdater[TestModel](db).Update(...).Set(Assign("Age", Raw("
age+?", 1)))
- 期望结果:生成正确的 SQL 语句,包含正确的参数。
SelectSuite
测试用例设计:
-
测试 MySQL 数据库查询功能:
- 描述:在
SelectSuite
中,对 MySQL 数据库进行查询功能的集成测试。 - 输入:数据库驱动为 "mysql",DSN 为 "root:root@tcp(localhost:13306)/integration_test"。
- 期望结果:测试查询功能是否能够正确执行,验证获取数据和处理不存在行的情况。
- 描述:在
-
SetupSuite
设置数据库插入数据:- 描述:在
SetupSuite
中,使用orm.NewInserter
在数据库中插入一条数据。 - 输入:插入一条
test.SimpleStruct
数据,ID 为 100。 - 期望结果:验证插入操作是否成功执行。
- 描述:在
-
测试
Get
方法:- 描述:在
TestGet
方法中,通过orm.NewSelector
和Get
方法测试从数据库中获取数据。 - 输入:包括获取存在的数据和获取不存在的数据的测试用例。
- 期望结果:验证获取数据和处理不存在行的情况是否正确。
- 描述:在
InsertSuite
测试用例设计:
-
测试 MySQL 数据库插入功能:
- 描述:在
InsertSuite
中,对 MySQL 数据库进行插入功能的集成测试。 - 输入:数据库驱动为 "mysql",DSN 为 "root:root@tcp(localhost:13306)/integration_test"。
- 期望结果:测试插入功能是否能够正确执行,验证单条和多条插入以及插入指定 ID 是否成功。
- 描述:在
-
测试
TestInsert
方法:- 描述:在
TestInsert
方法中,通过orm.NewInserter
和Exec
方法测试插入数据到数据库。 - 输入:包括插入一条数据、插入多条数据和插入指定 ID 的数据的测试用例。
- 期望结果:验证插入操作是否成功执行,且受影响的行数符合预期。
- 描述:在
测试执行:
$ go test -v ./...
测试结果:
$ go test -v ./...
? github.com/Ai-feier/lorm/appexample/rbacapp [no test files]
? github.com/Ai-feier/lorm/internal/errs [no test files]
=== RUN TestDeleter_Build
=== RUN TestDeleter_Build/no_where
=== RUN TestDeleter_Build/where
=== RUN TestDeleter_Build/from
--- PASS: TestDeleter_Build (0.00s)
--- PASS: TestDeleter_Build/no_where (0.00s)
--- PASS: TestDeleter_Build/where (0.00s)
--- PASS: TestDeleter_Build/from (0.00s)
=== RUN TestInserter_Build
=== RUN TestInserter_Build/no_value
=== RUN TestInserter_Build/single_values
=== RUN TestInserter_Build/multiple_values
=== RUN TestInserter_Build/specify_columns
=== RUN TestInserter_Build/invalid_columns
=== RUN TestInserter_Build/upsert
=== RUN TestInserter_Build/upsert_invalid_column
=== RUN TestInserter_Build/upsert_use_insert_value
--- PASS: TestInserter_Build (0.00s)
--- PASS: TestInserter_Build/no_value (0.00s)
--- PASS: TestInserter_Build/single_values (0.00s)
--- PASS: TestInserter_Build/multiple_values (0.00s)
--- PASS: TestInserter_Build/specify_columns (0.00s)
--- PASS: TestInserter_Build/invalid_columns (0.00s)
--- PASS: TestInserter_Build/upsert (0.00s)
--- PASS: TestInserter_Build/upsert_invalid_column (0.00s)
--- PASS: TestInserter_Build/upsert_use_insert_value (0.00s)
=== RUN TestUpsert_SQLite3_Build
=== RUN TestUpsert_SQLite3_Build/upsert
=== RUN TestUpsert_SQLite3_Build/upsert_invalid_column
=== RUN TestUpsert_SQLite3_Build/conflict_invalid_column
=== RUN TestUpsert_SQLite3_Build/upsert_use_insert_value
--- PASS: TestUpsert_SQLite3_Build (0.00s)
--- PASS: TestUpsert_SQLite3_Build/upsert (0.00s)
--- PASS: TestUpsert_SQLite3_Build/upsert_invalid_column (0.00s)
--- PASS: TestUpsert_SQLite3_Build/conflict_invalid_column (0.00s)
--- PASS: TestUpsert_SQLite3_Build/upsert_use_insert_value (0.00s)
=== RUN TestSQLite
=== RUN TestSQLite/TestCRUD
--- PASS: TestSQLite (0.00s)
--- PASS: TestSQLite/TestCRUD (0.00s)
=== RUN TestSelector_OffsetLimit
=== RUN TestSelector_OffsetLimit/offset_only
=== RUN TestSelector_OffsetLimit/limit_only
=== RUN TestSelector_OffsetLimit/limit_offset
--- PASS: TestSelector_OffsetLimit (0.00s)
--- PASS: TestSelector_OffsetLimit/offset_only (0.00s)
--- PASS: TestSelector_OffsetLimit/limit_only (0.00s)
--- PASS: TestSelector_OffsetLimit/limit_offset (0.00s)
=== RUN TestSelector_Having
=== RUN TestSelector_Having/none
=== RUN TestSelector_Having/single
=== RUN TestSelector_Having/multiple
=== RUN TestSelector_Having/avg
--- PASS: TestSelector_Having (0.00s)
--- PASS: TestSelector_Having/none (0.00s)
--- PASS: TestSelector_Having/single (0.00s)
--- PASS: TestSelector_Having/multiple (0.00s)
--- PASS: TestSelector_Having/avg (0.00s)
=== RUN TestSelector_GroupBy
=== RUN TestSelector_GroupBy/none
=== RUN TestSelector_GroupBy/single
=== RUN TestSelector_GroupBy/multiple
=== RUN TestSelector_GroupBy/invalid_column
--- PASS: TestSelector_GroupBy (0.00s)
--- PASS: TestSelector_GroupBy/none (0.00s)
--- PASS: TestSelector_GroupBy/single (0.00s)
--- PASS: TestSelector_GroupBy/multiple (0.00s)
--- PASS: TestSelector_GroupBy/invalid_column (0.00s)
=== RUN TestSelector_Build
=== RUN TestSelector_Build/no_from
=== RUN TestSelector_Build/with_from
=== RUN TestSelector_Build/empty_from
=== RUN TestSelector_Build/with_db
=== RUN TestSelector_Build/single_and_simple_predicate
=== RUN TestSelector_Build/multiple_predicates
=== RUN TestSelector_Build/and
=== RUN TestSelector_Build/or
=== RUN TestSelector_Build/not
=== RUN TestSelector_Build/invalid_columns
=== RUN TestSelector_Build/raw_expression
--- PASS: TestSelector_Build (0.00s)
--- PASS: TestSelector_Build/no_from (0.00s)
--- PASS: TestSelector_Build/with_from (0.00s)
--- PASS: TestSelector_Build/empty_from (0.00s)
--- PASS: TestSelector_Build/with_db (0.00s)
--- PASS: TestSelector_Build/single_and_simple_predicate (0.00s)
--- PASS: TestSelector_Build/multiple_predicates (0.00s)
--- PASS: TestSelector_Build/and (0.00s)
--- PASS: TestSelector_Build/or (0.00s)
--- PASS: TestSelector_Build/not (0.00s)
--- PASS: TestSelector_Build/invalid_columns (0.00s)
--- PASS: TestSelector_Build/raw_expression (0.00s)
=== RUN TestSelector_Get
=== RUN TestSelector_Get/query_error
=== RUN TestSelector_Get/no_row
=== RUN TestSelector_Get/too_many_columns
=== RUN TestSelector_Get/get_data
--- PASS: TestSelector_Get (0.00s)
--- PASS: TestSelector_Get/query_error (0.00s)
--- PASS: TestSelector_Get/no_row (0.00s)
--- PASS: TestSelector_Get/too_many_columns (0.00s)
--- PASS: TestSelector_Get/get_data (0.00s)
=== RUN TestTx_Commit
--- PASS: TestTx_Commit (0.00s)
=== RUN TestTx_Rollback
--- PASS: TestTx_Rollback (0.00s)
=== RUN TestUpdater_Build
=== RUN TestUpdater_Build/no_column
=== RUN TestUpdater_Build/single_column
=== RUN TestUpdater_Build/assignment
=== RUN TestUpdater_Build/where
=== RUN TestUpdater_Build/incremental
=== RUN TestUpdater_Build/incremental-raw
--- PASS: TestUpdater_Build (0.00s)
--- PASS: TestUpdater_Build/no_column (0.00s)
--- PASS: TestUpdater_Build/single_column (0.00s)
--- PASS: TestUpdater_Build/assignment (0.00s)
--- PASS: TestUpdater_Build/where (0.00s)
--- PASS: TestUpdater_Build/incremental (0.00s)
--- PASS: TestUpdater_Build/incremental-raw (0.00s)
PASS
ok github.com/Ai-feier/lorm (cached)
testing: warning: no tests to run
PASS
ok github.com/Ai-feier/lorm/internal/integration (cached) [no tests to run]
=== RUN TestJsonColumn_Scan
=== RUN TestJsonColumn_Scan/empty_string
=== RUN TestJsonColumn_Scan/no_fields
=== RUN TestJsonColumn_Scan/string
=== RUN TestJsonColumn_Scan/nil_bytes
=== RUN TestJsonColumn_Scan/empty_bytes
=== RUN TestJsonColumn_Scan/bytes
=== RUN TestJsonColumn_Scan/nil
=== RUN TestJsonColumn_Scan/empty_bytes_ptr
=== RUN TestJsonColumn_Scan/bytes_ptr
--- PASS: TestJsonColumn_Scan (0.00s)
--- PASS: TestJsonColumn_Scan/empty_string (0.00s)
--- PASS: TestJsonColumn_Scan/no_fields (0.00s)
--- PASS: TestJsonColumn_Scan/string (0.00s)
--- PASS: TestJsonColumn_Scan/nil_bytes (0.00s)
--- PASS: TestJsonColumn_Scan/empty_bytes (0.00s)
--- PASS: TestJsonColumn_Scan/bytes (0.00s)
--- PASS: TestJsonColumn_Scan/nil (0.00s)
--- PASS: TestJsonColumn_Scan/empty_bytes_ptr (0.00s)
--- PASS: TestJsonColumn_Scan/bytes_ptr (0.00s)
PASS
ok github.com/Ai-feier/lorm/internal/test (cached)
? github.com/Ai-feier/lorm/middleproviderexample/nodelete [no test files]
=== RUN TestReflectValue_Field
=== RUN TestReflectValue_Field/zero_value
=== RUN TestReflectValue_Field/zero_value/bool
=== RUN TestReflectValue_Field/zero_value/bool_pointer
=== RUN TestReflectValue_Field/zero_value/int
=== RUN TestReflectValue_Field/zero_value/int_pointer
=== RUN TestReflectValue_Field/zero_value/int8
=== RUN TestReflectValue_Field/zero_value/int8_pointer
=== RUN TestReflectValue_Field/zero_value/int16
=== RUN TestReflectValue_Field/zero_value/int16_pointer
=== RUN TestReflectValue_Field/zero_value/int32
=== RUN TestReflectValue_Field/zero_value/int32_pointer
=== RUN TestReflectValue_Field/zero_value/int64
=== RUN TestReflectValue_Field/zero_value/int64_pointer
=== RUN TestReflectValue_Field/zero_value/uint
=== RUN TestReflectValue_Field/zero_value/uint_pointer
=== RUN TestReflectValue_Field/zero_value/uint8
=== RUN TestReflectValue_Field/zero_value/uint8_pointer
=== RUN TestReflectValue_Field/zero_value/uint16
=== RUN TestReflectValue_Field/zero_value/uint16_pointer
=== RUN TestReflectValue_Field/zero_value/uint32
=== RUN TestReflectValue_Field/zero_value/uint32_pointer
=== RUN TestReflectValue_Field/zero_value/uint64
=== RUN TestReflectValue_Field/zero_value/uint64_pointer
=== RUN TestReflectValue_Field/zero_value/float32
=== RUN TestReflectValue_Field/zero_value/float32_pointer
=== RUN TestReflectValue_Field/zero_value/float64
=== RUN TestReflectValue_Field/zero_value/float64_pointer
=== RUN TestReflectValue_Field/zero_value/byte_array
=== RUN TestReflectValue_Field/zero_value/string
=== RUN TestReflectValue_Field/zero_value/NullStringPtr
=== RUN TestReflectValue_Field/zero_value/NullInt16Ptr
=== RUN TestReflectValue_Field/zero_value/NullInt32Ptr
=== RUN TestReflectValue_Field/zero_value/NullInt64Ptr
=== RUN TestReflectValue_Field/zero_value/NullFloat64Ptr
=== RUN TestReflectValue_Field/zero_value/JsonColumn
=== RUN TestReflectValue_Field/normal_value
=== RUN TestReflectValue_Field/normal_value/bool
=== RUN TestReflectValue_Field/normal_value/bool_pointer
=== RUN TestReflectValue_Field/normal_value/int
=== RUN TestReflectValue_Field/normal_value/int_pointer
=== RUN TestReflectValue_Field/normal_value/int8
=== RUN TestReflectValue_Field/normal_value/int8_pointer
=== RUN TestReflectValue_Field/normal_value/int16
=== RUN TestReflectValue_Field/normal_value/int16_pointer
=== RUN TestReflectValue_Field/normal_value/int32
=== RUN TestReflectValue_Field/normal_value/int32_pointer
=== RUN TestReflectValue_Field/normal_value/int64
=== RUN TestReflectValue_Field/normal_value/int64_pointer
=== RUN TestReflectValue_Field/normal_value/uint
=== RUN TestReflectValue_Field/normal_value/uint_pointer
=== RUN TestReflectValue_Field/normal_value/uint8
=== RUN TestReflectValue_Field/normal_value/uint8_pointer
=== RUN TestReflectValue_Field/normal_value/uint16
=== RUN TestReflectValue_Field/normal_value/uint16_pointer
=== RUN TestReflectValue_Field/normal_value/uint32
=== RUN TestReflectValue_Field/normal_value/uint32_pointer
=== RUN TestReflectValue_Field/normal_value/uint64
=== RUN TestReflectValue_Field/normal_value/uint64_pointer
=== RUN TestReflectValue_Field/normal_value/float32
=== RUN TestReflectValue_Field/normal_value/float32_pointer
=== RUN TestReflectValue_Field/normal_value/float64
=== RUN TestReflectValue_Field/normal_value/float64_pointer
=== RUN TestReflectValue_Field/normal_value/byte_array
=== RUN TestReflectValue_Field/normal_value/string
=== RUN TestReflectValue_Field/normal_value/NullStringPtr
=== RUN TestReflectValue_Field/normal_value/NullInt16Ptr
=== RUN TestReflectValue_Field/normal_value/NullInt32Ptr
=== RUN TestReflectValue_Field/normal_value/NullInt64Ptr
=== RUN TestReflectValue_Field/normal_value/NullFloat64Ptr
=== RUN TestReflectValue_Field/normal_value/JsonColumn
=== RUN TestReflectValue_Field/invalid_cases
=== RUN TestReflectValue_Field/invalid_cases/invalid_field
--- PASS: TestReflectValue_Field (0.00s)
--- PASS: TestReflectValue_Field/zero_value (0.00s)
--- PASS: TestReflectValue_Field/zero_value/bool (0.00s)
--- PASS: TestReflectValue_Field/zero_value/bool_pointer (0.00s)
--- PASS: TestReflectValue_Field/zero_value/int (0.00s)
--- PASS: TestReflectValue_Field/zero_value/int_pointer (0.00s)
--- PASS: TestReflectValue_Field/zero_value/int8 (0.00s)
--- PASS: TestReflectValue_Field/zero_value/int8_pointer (0.00s)
--- PASS: TestReflectValue_Field/zero_value/int16 (0.00s)
--- PASS: TestReflectValue_Field/zero_value/int16_pointer (0.00s)
--- PASS: TestReflectValue_Field/zero_value/int32 (0.00s)
--- PASS: TestReflectValue_Field/zero_value/int32_pointer (0.00s)
--- PASS: TestReflectValue_Field/zero_value/int64 (0.00s)
--- PASS: TestReflectValue_Field/zero_value/int64_pointer (0.00s)
--- PASS: TestReflectValue_Field/zero_value/uint (0.00s)
--- PASS: TestReflectValue_Field/zero_value/uint_pointer (0.00s)
--- PASS: TestReflectValue_Field/zero_value/uint8 (0.00s)
--- PASS: TestReflectValue_Field/zero_value/uint8_pointer (0.00s)
--- PASS: TestReflectValue_Field/zero_value/uint16 (0.00s)
--- PASS: TestReflectValue_Field/zero_value/uint16_pointer (0.00s)
--- PASS: TestReflectValue_Field/zero_value/uint32 (0.00s)
--- PASS: TestReflectValue_Field/zero_value/uint32_pointer (0.00s)
--- PASS: TestReflectValue_Field/zero_value/uint64 (0.00s)
--- PASS: TestReflectValue_Field/zero_value/uint64_pointer (0.00s)
--- PASS: TestReflectValue_Field/zero_value/float32 (0.00s)
--- PASS: TestReflectValue_Field/zero_value/float32_pointer (0.00s)
--- PASS: TestReflectValue_Field/zero_value/float64 (0.00s)
--- PASS: TestReflectValue_Field/zero_value/float64_pointer (0.00s)
--- PASS: TestReflectValue_Field/zero_value/byte_array (0.00s)
--- PASS: TestReflectValue_Field/zero_value/string (0.00s)
--- PASS: TestReflectValue_Field/zero_value/NullStringPtr (0.00s)
--- PASS: TestReflectValue_Field/zero_value/NullInt16Ptr (0.00s)
--- PASS: TestReflectValue_Field/zero_value/NullInt32Ptr (0.00s)
--- PASS: TestReflectValue_Field/zero_value/NullInt64Ptr (0.00s)
--- PASS: TestReflectValue_Field/zero_value/NullFloat64Ptr (0.00s)
--- PASS: TestReflectValue_Field/zero_value/JsonColumn (0.00s)
--- PASS: TestReflectValue_Field/normal_value (0.00s)
--- PASS: TestReflectValue_Field/normal_value/bool (0.00s)
--- PASS: TestReflectValue_Field/normal_value/bool_pointer (0.00s)
--- PASS: TestReflectValue_Field/normal_value/int (0.00s)
--- PASS: TestReflectValue_Field/normal_value/int_pointer (0.00s)
--- PASS: TestReflectValue_Field/normal_value/int8 (0.00s)
--- PASS: TestReflectValue_Field/normal_value/int8_pointer (0.00s)
--- PASS: TestReflectValue_Field/normal_value/int16 (0.00s)
--- PASS: TestReflectValue_Field/normal_value/int16_pointer (0.00s)
--- PASS: TestReflectValue_Field/normal_value/int32 (0.00s)
--- PASS: TestReflectValue_Field/normal_value/int32_pointer (0.00s)
--- PASS: TestReflectValue_Field/normal_value/int64 (0.00s)
--- PASS: TestReflectValue_Field/normal_value/int64_pointer (0.00s)
--- PASS: TestReflectValue_Field/normal_value/uint (0.00s)
--- PASS: TestReflectValue_Field/normal_value/uint_pointer (0.00s)
--- PASS: TestReflectValue_Field/normal_value/uint8 (0.00s)
--- PASS: TestReflectValue_Field/normal_value/uint8_pointer (0.00s)
--- PASS: TestReflectValue_Field/normal_value/uint16 (0.00s)
--- PASS: TestReflectValue_Field/normal_value/uint16_pointer (0.00s)
--- PASS: TestReflectValue_Field/normal_value/uint32 (0.00s)
--- PASS: TestReflectValue_Field/normal_value/uint32_pointer (0.00s)
--- PASS: TestReflectValue_Field/normal_value/uint64 (0.00s)
--- PASS: TestReflectValue_Field/normal_value/uint64_pointer (0.00s)
--- PASS: TestReflectValue_Field/normal_value/float32 (0.00s)
--- PASS: TestReflectValue_Field/normal_value/float32_pointer (0.00s)
--- PASS: TestReflectValue_Field/normal_value/float64 (0.00s)
--- PASS: TestReflectValue_Field/normal_value/float64_pointer (0.00s)
--- PASS: TestReflectValue_Field/normal_value/byte_array (0.00s)
--- PASS: TestReflectValue_Field/normal_value/string (0.00s)
--- PASS: TestReflectValue_Field/normal_value/NullStringPtr (0.00s)
--- PASS: TestReflectValue_Field/normal_value/NullInt16Ptr (0.00s)
--- PASS: TestReflectValue_Field/normal_value/NullInt32Ptr (0.00s)
--- PASS: TestReflectValue_Field/normal_value/NullInt64Ptr (0.00s)
--- PASS: TestReflectValue_Field/normal_value/NullFloat64Ptr (0.00s)
--- PASS: TestReflectValue_Field/normal_value/JsonColumn (0.00s)
--- PASS: TestReflectValue_Field/invalid_cases (0.00s)
--- PASS: TestReflectValue_Field/invalid_cases/invalid_field (0.00s)
=== RUN Test_reflectValue_SetColumn
=== RUN Test_reflectValue_SetColumn/normal_value
=== RUN Test_reflectValue_SetColumn/invalid_field
--- PASS: Test_reflectValue_SetColumn (0.00s)
--- PASS: Test_reflectValue_SetColumn/normal_value (0.00s)
--- PASS: Test_reflectValue_SetColumn/invalid_field (0.00s)
=== RUN Test_unsafeValue_SetColumn
=== RUN Test_unsafeValue_SetColumn/normal_value
=== RUN Test_unsafeValue_SetColumn/invalid_field
--- PASS: Test_unsafeValue_SetColumn (0.00s)
--- PASS: Test_unsafeValue_SetColumn/normal_value (0.00s)
--- PASS: Test_unsafeValue_SetColumn/invalid_field (0.00s)
PASS
ok github.com/Ai-feier/lorm/internal/valuer (cached)
? github.com/Ai-feier/lorm/middleproviderexample/opentelemetry [no test files]
? github.com/Ai-feier/lorm/middleproviderexample/prometheus [no test files]
? github.com/Ai-feier/lorm/middleproviderexample/safedml [no test files]
? github.com/Ai-feier/lorm/middleproviderexample/slowquery [no test files]
=== RUN TestNewMiddlewareBuilder
--- PASS: TestNewMiddlewareBuilder (0.00s)
PASS
ok github.com/Ai-feier/lorm/middleproviderexample/sqllog (cached)
=== RUN TestModelWithTableName
=== RUN TestModelWithTableName/empty_string
=== RUN TestModelWithTableName/table_name
--- PASS: TestModelWithTableName (0.00s)
--- PASS: TestModelWithTableName/empty_string (0.00s)
--- PASS: TestModelWithTableName/table_name (0.00s)
=== RUN TestWithColumnName
=== RUN TestWithColumnName/new_name
=== RUN TestWithColumnName/empty_new_name
=== RUN TestWithColumnName/invalid_Field_name
--- PASS: TestWithColumnName (0.00s)
--- PASS: TestWithColumnName/new_name (0.00s)
--- PASS: TestWithColumnName/empty_new_name (0.00s)
--- PASS: TestWithColumnName/invalid_Field_name (0.00s)
=== RUN TestRegistry_get
=== RUN TestRegistry_get/test_Model
=== RUN TestRegistry_get/pointer
=== RUN TestRegistry_get/multiple_pointer
=== RUN TestRegistry_get/map
=== RUN TestRegistry_get/slice
=== RUN TestRegistry_get/basic_type
=== RUN TestRegistry_get/column_tag
=== RUN TestRegistry_get/empty_column
=== RUN TestRegistry_get/invalid_tag
=== RUN Test_underscoreName/upper_cases
=== RUN Test_underscoreName/use_number
--- PASS: Test_underscoreName (0.00s)
--- PASS: Test_underscoreName/upper_cases (0.00s)
--- PASS: Test_underscoreName/use_number (0.00s)
PASS
ok github.com/Ai-feier/lorm/model (cached)
测试结果分析:
测试结果显示,所有测试用例都通过了。以下是一些关键部分的总结:
-
lorm/internal/integration
包:- 执行了多个集成测试,包括构建删除、插入、更新、查询等操作的测试。
- 执行了 SQLite3 相关的集成测试。
- 执行了与数据库交互的各个模块的测试,例如
TestSQLite
、TestSelector
、TestTx_Commit
等。
-
lorm/internal/test
包:- 包含了一系列用于测试 JSON 列扫描的测试用例,所有测试通过。
-
lorm/internal/valuer
包:- 执行了多个关于值处理的测试,包括通过反射设置列值、通过不安全的方式设置列值等。
- 所有测试用例通过。
-
lorm/middleproviderexample/sqllog
包:- 执行了测试,包括
TestNewMiddlewareBuilder
,所有测试通过。
- 执行了测试,包括
-
lorm/model
包:- 执行了多个与模型、字段、注册表等相关的测试,所有测试通过。
-
lorm
包:- 执行了一系列基础功能的测试,包括构建删除、插入、更新、查询等操作的测试。
- 执行了与数据库交互的多个模块的测试,例如
TestSQLite
、TestSelector
、TestTx_Commit
等。 - 执行了一些中间件提供者的测试,如
TestModelWithTableName
、TestWithColumnName
等。 - 所有测试用例通过。
总体而言,测试覆盖了核心功能和一些边缘情况,通过率为100%,代码质量较高。
项目概览:
本项目是一个使用Go语言开发的ORM框架,旨在提供高效、灵活的对象关系映射解决方案。在项目的开发过程中,我们注重面向接口的设计,并充分利用Go语言的反射和unsafe特性,以支持复杂的数据模型映射。
**技术亮点:
-
面向接口编程:项目中广泛应用了面向接口编程的思想,通过定义清晰的接口,降低了各个组件之间的耦合度。依赖注入容器的使用进一步简化了组件的管理和替换。
-
Go反射与unsafe编程:充分利用Go语言的反射特性,使得对象关系映射更加灵活。同时,对于涉及到unsafe包的操作,我们在代码中注入了足够的安全性检查,确保代码的稳定性。
-
并发编程:在支持高并发的Go语言中,项目中对并发编程的需求进行了深入理解。ORM框架的设计考虑到了协程与通道的使用,保证了在并发环境下的安全性和高效性。
-
SQL编程:ORM框架在SQL生成与执行方面进行了精心设计,以提供高性能的数据库访问。事务支持的完善性确保了在复杂业务场景下的数据一致性。
挑战与反思:
-
技术选型:在项目的初期,我们进行了全面的技术选型,涉及到测试框架、依赖注入容器、反射库等多个方面。不同技术的组合需要保证协同工作,因此我们在选择时充分考虑了它们的兼容性。
-
学习曲线:由于项目使用了一些高级的Go语言特性,包括反射和unsafe包的使用,对于团队成员的技术水平提出了一定的挑战。我们通过培训和知识分享确保了团队对这些特性的深入理解。
-
文档与社区:在项目中,我们注重编写详细的文档,以便团队成员更好地理解框架的使用和设计思想。此外,我们也积极参与Go社区,获取反馈并分享经验,确保项目的健康发展。
未来展望:
在项目的未来,我们计划进一步优化框架的性能和稳定性。同时,考虑到行业的变化和新的技术趋势,我们将不断更新和升级框架,以保持其在ORM领域的竞争力。通过持续的学习和技术创新,我们将确保项目保持活力并满足用户的需求。