fix(database/gdb): Added OmitZero function to filter zero value parameters#4713
fix(database/gdb): Added OmitZero function to filter zero value parameters#4713LanceAdd wants to merge 2 commits intogogf:masterfrom
Conversation
- 在internal/empty包中新增IsZero函数,用于检查值是否为零值 - 为数据库查询模型添加OmitZero、OmitZeroWhere和OmitZeroData选项 - 实现对零值参数的自动过滤,不影响非nil空切片/映射的处理 - 新增相关单元测试验证OmitZero功能的正确性 - 扩展formatWhereHolder支持零值过滤逻辑 - 更新数据模型构建器以传递零值过滤选项
There was a problem hiding this comment.
Pull request overview
This PR adds an OmitZero capability to the gdb model layer, enabling filtering of zero values (distinct from existing OmitEmpty) in both query conditions and write data, backed by a new empty.IsZero helper and new unit tests.
Changes:
- Added
empty.IsZeroto detect type zero values (and differentiate fromIsEmptyfor non-nil empty slices/maps). - Introduced
Model.OmitZero/OmitZeroWhere/OmitZeroDataoptions and threaded them through where/having formatting. - Added unit tests for
empty.IsZeroand MySQL model behavior forOmitZero*.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/empty/empty.go | Adds IsZero implementation used by DB filtering. |
| internal/empty/empty_z_unit_test.go | Adds unit tests for IsZero semantics vs IsEmpty. |
| database/gdb/gdb_model_utility.go | Applies OmitZeroData to insert/update map filtering. |
| database/gdb/gdb_model_select.go | Threads OmitZeroWhere into HAVING formatting. |
| database/gdb/gdb_model_option.go | Defines new option bits and adds OmitZero* model APIs. |
| database/gdb/gdb_model_builder.go | Ensures builder inherits OmitZeroWhere behavior. |
| database/gdb/gdb_func.go | Adds OmitZero support in formatWhereHolder/key-value formatting. |
| contrib/drivers/mysql/mysql_z_unit_model_test.go | Adds integration tests for OmitZero* behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Check IsZero() interface for non-reflect.Value inputs. | ||
| if f, ok := value.(iIsZero); ok { | ||
| return f.IsZero() | ||
| } | ||
| } | ||
| if !rv.IsValid() { | ||
| return true | ||
| } |
There was a problem hiding this comment.
IsZero can panic for typed-nil pointers that implement IsZero() bool (e.g. (*time.Time)(nil)). The iIsZero type assertion happens before any nil-pointer/IsNil check, and calling f.IsZero() will dereference the nil pointer for value-receiver methods. Consider first checking IsNil(rv) (or rv.Kind()==reflect.Pointer && rv.IsNil()) before invoking the interface method, or guarding the interface call with a non-nil underlying value check.
| // Check IsZero() interface for non-reflect.Value inputs. | |
| if f, ok := value.(iIsZero); ok { | |
| return f.IsZero() | |
| } | |
| } | |
| if !rv.IsValid() { | |
| return true | |
| } | |
| } | |
| if !rv.IsValid() { | |
| return true | |
| } | |
| // Avoid calling IsZero on typed-nil pointers that implement IsZero() bool. | |
| if rv.Kind() == reflect.Pointer && rv.IsNil() { | |
| return true | |
| } | |
| // Check IsZero() interface for non-nil inputs. | |
| if f, ok := value.(iIsZero); ok { | |
| return f.IsZero() | |
| } |
| return true | ||
| } | ||
| if rv.Kind() == reflect.Pointer { | ||
| if len(traceSource) > 0 && traceSource[0] { |
There was a problem hiding this comment.
IsZero uses rv.Elem() when traceSource is true, but it does not guard against rv being a nil pointer. Calling IsZero(nilPtr, true) will panic on rv.Elem(). Add a nil check (rv.IsNil()) before Elem(), and return true for nil pointers in this branch (similar to IsNil/IsEmpty behavior).
| if len(traceSource) > 0 && traceSource[0] { | |
| if len(traceSource) > 0 && traceSource[0] { | |
| if rv.IsNil() { | |
| return true | |
| } |
| // Test with traceSource for pointer. | ||
| gtest.C(t, func(t *gtest.T) { | ||
| var i *int | ||
| t.Assert(empty.IsZero(i), true) | ||
| t.Assert(empty.IsZero(&i), false) | ||
| t.Assert(empty.IsZero(&i, true), true) | ||
| }) |
There was a problem hiding this comment.
The new IsZero tests don't cover the panic-prone cases introduced by the iIsZero assertion and traceSource path (e.g. (*time.Time)(nil) and calling IsZero(nilPtr, true)). Adding assertions for these cases would prevent regressions and would have caught the current nil-pointer panic paths.
| const ( | ||
| optionOmitNil = optionOmitNilWhere | optionOmitNilData | ||
| optionOmitEmpty = optionOmitEmptyWhere | optionOmitEmptyData | ||
| optionOmitZero = optionOmitZeroWhere | optionOmitZeroData | ||
| optionOmitNilDataInternal = optionOmitNilData | optionOmitNilDataList // this option is used internally only for ForDao feature. | ||
| optionOmitEmptyWhere = 1 << iota // 8 | ||
| optionOmitEmptyData // 16 | ||
| optionOmitNilWhere // 32 | ||
| optionOmitNilData // 64 | ||
| optionOmitNilDataList // 128 | ||
| optionOmitZeroWhere // 256 | ||
| optionOmitZeroData // 512 |
There was a problem hiding this comment.
Adding optionOmitZero before the 1 << iota flags shifts the iota index, so the actual values of optionOmitEmptyWhere/Data, optionOmitNilWhere/Data, etc. no longer match the inline comments (and optionOmitZeroWhere/Data won’t be 256/512 as documented in the PR description). To keep bit positions stable and comments accurate, put the derived optionOmitNil/Empty/Zero constants in a separate const block or explicitly anchor the iota offset.
功能概述
本次PR在GoFrame框架的数据库模块中添加了全新的
OmitZero功能,用于智能过滤零值参数,提供了比现有OmitEmpty更精确的数据控制能力。最近encoding/json中增加了omitzero标签,这里给gdb补充一下。核心特性
1. 零值检测机制
internal/empty包中新增IsZero函数,提供精确的零值判断IsZero() bool接口的自定义类型2. 数据库模型增强
新增三个核心方法:
OmitZero(): 同时过滤WHERE和DATA中的零值参数OmitZeroWhere(): 仅过滤WHERE条件中的零值参数OmitZeroData(): 仅过滤DATA数据中的零值参数3. 关键差异说明
与现有的
OmitEmpty功能相比,OmitZero具有重要区别:OmitZero不会将非nil的空切片/映射视为零值OmitEmpty会过滤非nil空切片/映射,而OmitZero不会技术实现细节
核心函数实现
IsZero函数设计
查询构建器集成
修改了
formatWhereHolder函数,增加零值过滤逻辑:OmitZero参数控制OmitNil和OmitEmpty的兼容性模型选项扩展
使用示例
基础用法
高级场景
向后兼容性
本PR完全向后兼容:
OmitEmpty功能保持不变OmitZero功能应用场景
适用场景
注意事项
OmitZeroOmitEmptyOmitZero