Skip to content

Revert "Revert "feat(database/gdb): Added OmitZero function to filter zero value parameters"" - #4819

Open
hailaz wants to merge 3 commits into
masterfrom
revert-4818-revert-4713-fix/omitzero
Open

Revert "Revert "feat(database/gdb): Added OmitZero function to filter zero value parameters""#4819
hailaz wants to merge 3 commits into
masterfrom
revert-4818-revert-4713-fix/omitzero

Conversation

@hailaz

@hailaz hailaz commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Reverts #4818

源PR #4713 因为合并回滚了,没法重新打开,顾再整了一个回滚中的回滚

@hailaz hailaz added the discuss We need discuss to make decision. label Jul 30, 2026
Copilot AI review requested due to automatic review settings July 30, 2026 08:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR re-applies the previously reverted OmitZero capability to GoFrame’s gdb ORM by introducing a new empty.IsZero helper and wiring a new model option through WHERE/HAVING formatting and DATA filtering.

Changes:

  • Add internal/empty.IsZero for “zero value” detection (distinct from IsEmpty, notably keeping non-nil empty slices/maps as non-zero).
  • Add Model.OmitZero/OmitZeroWhere/OmitZeroData options and propagate them into condition formatting and insert/update data filtering.
  • Add unit coverage for IsZero and OmitZero* behaviors (including slice/map edge cases and builder inheritance).

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
internal/empty/empty.go Introduces IsZero zero-value detection with pointer tracing support.
internal/empty/empty_z_unit_test.go Adds unit tests validating IsZero semantics vs IsEmpty.
database/gdb/gdb_model_utility.go Filters insert/update DATA maps using the new OmitZeroData option.
database/gdb/gdb_model_select.go Enables OmitZeroWhere behavior for HAVING condition formatting.
database/gdb/gdb_model_option.go Adds new option bits and exposes OmitZero* model methods.
database/gdb/gdb_model_builder.go Ensures WhereBuilder inherits/applies OmitZeroWhere during build.
database/gdb/gdb_func.go Threads OmitZero through formatWhereHolder/formatWhereKeyValue and adds omit checks.
contrib/drivers/mysql/mysql_z_unit_model_test.go Adds MySQL integration tests for OmitZero* and builder inheritance.
Comments suppressed due to low confidence (1)

contrib/drivers/mysql/mysql_z_unit_model_test.go:1758

  • RowsAffected() error is currently discarded (n, _ := ...). Please assert/handle the error so test failures aren’t masked.
		t.AssertNil(err)
		n, _ := result.RowsAffected()
		t.Assert(n, 1)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread contrib/drivers/mysql/mysql_z_unit_model_test.go
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 31, 2026 00:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review is ineligible. To be eligible to request a review, you need a paid Copilot license, or your organization must enable Copilot code review.

Copilot AI review requested due to automatic review settings July 31, 2026 01:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review is ineligible. To be eligible to request a review, you need a paid Copilot license, or your organization must enable Copilot code review.

@LanceAdd

Copy link
Copy Markdown
Member

gdb.OmitZero必要性分析

核心结论

OmitNilOmitZeroOmitEmpty表达的是三种不同语义:

方法 判断目标
OmitNil 值是否为nil
OmitZero 值是否为其 Go 类型的零值
OmitEmpty 值的内容是否为空

最关键的两个反例是:

make([]string, 0):是空值,但不是零值。
[16]byte{}:是零值,但不是空值。

因此,OmitZero不是OmitNilOmitEmpty的别名,而是将 Go 的零值语义引入gdb

Go 本身要求开发者区分零值和空值。Go 1.24在encoding/json中增加omitzero标签,进一步明确了这种语义边界。标准库也提供了reflect.Value.IsZero(),自定义类型还可以通过IsZero() bool定义自己的零值规则。因此,不能笼统地使用指针或空值判断代替零值语义。

案例一:清空 JSON 数组

type UserUpdate struct {
LoginCount int      `orm:"login_count"`
Tags       []string `orm:"tags"`
}

input := UserUpdate{
Tags: make([]string, 0), // 明确清空标签
}

_, err := db.Model("users").
Where("id", userID).
OmitZeroData().
Data(input).
Update()

业务意图是忽略默认的LoginCount=0,但将Tags=[]写入数据库:

方法 结果
OmitNilData 同时保留两者,可能误写LoginCount=0
OmitEmptyData 同时过滤两者,无法清空标签
OmitZeroData 过滤LoginCount=0,保留Tags=[]

这个案例证明:空值不一定是零值

案例二:过滤零 UUID

UUID通常由固定长度的[16]byte表示:

type UUID [16]byte

var id UUID

users, err := db.Model("users").
OmitZeroWhere().
Where("uuid", id).
All()

此时id是全零 UUID:

00000000-0000-0000-0000-000000000000

UUID{}是类型零值,但其数组长度为16,并不是空集合:

方法 是否过滤零 UUID
OmitNilWhere 否,UUID 是值类型
OmitEmptyWhere 否,数组长度不为0
OmitZeroWhere 是,UUID{}是类型零值

这个案例证明:零值也不一定是空值

案例三:过滤零时间

time.Time通过IsZero()明确提供零值判断:

var createdAt time.Time

orders, err := db.Model("orders").
OmitZeroWhere().
Where("created_at", createdAt).
All()

OmitZeroWhere可以过滤time.Time{},避免生成无意义的零时间条件。

当前OmitEmptyWhere也能识别零时间,但这是对时间类型的专门支持。OmitZeroWhere则通过统一的零值协议表达调用意图,语义更加直接。

案例四:自定义IsZero

业务类型可以自行定义零值:

type AccountID string

func (id AccountID) IsZero() bool {
return id == "" || id == "UNASSIGNED"
}

accountID := AccountID("UNASSIGNED")

records, err := db.Model("account_records").
OmitZeroWhere().
Where("account_id", accountID).
All()

accountID既不是nil,底层字符串也不为空,但其IsZero()返回true

方法 是否过滤UNASSIGNED
OmitNilWhere
OmitEmptyWhere
OmitZeroWhere

借助IsZero(),UUID、金额、版本号和业务 ID 等类型可以定义自己的零值,无需让gdb硬编码具体类型规则。

API 心智负担对比

从行为可预测性看,三种 API 的心智负担并不相同:

API 规则来源 心智负担
OmitNil Go 的nil语义 最低,判断边界明确
OmitZero Go 类型零值、reflect.Value.IsZero和类型的IsZero() 较低,规则由类型负责
OmitEmpty 框架对“空内容”的组合判断 最高,需要记忆框架规则

OmitEmpty中的“空”不是 Go 语言定义的统一状态。使用者需要额外了解以下值是否会被过滤:

  • nil0false""
  • 长度为0的切片、映射和通道。
  • 字段都为空的结构体。
  • 时间类型及其他特殊类型。

相比之下,OmitNil回答“值是否存在”,OmitZero回答“值是否处于该类型的初始状态”。这两个问题都有明确的 Go 语言边界。OmitEmpty回答的则是“框架是否认为该值没有有效内容”,需要开发者同时理解类型状态和框架策略。

这种心智负担在WHERE条件中还会转化为实际风险:

ids := make([]int, 0)

users, err := db.Model("users").
OmitEmptyWhere().
Where("id", ids).
All()

调用方可能认为空 ID 集合表示“不匹配任何用户”,但OmitEmptyWhere会把空切片和整个条件一起删除,查询可能退化为全表查询。OmitZeroWhere则会保留这个非零空切片,使其继续表达空集合条件。

因此,从低到高可以概括为:

OmitNil < OmitZero < OmitEmpty

新增OmitZero表面上增加了一个 API,实际上拆分了原本被OmitEmpty混在一起的“类型零值”和“内容为空”两种语义。调用方可以明确选择需要的过滤规则,不必依赖复杂的空值判断,因此整体上降低了认知成本和误用风险。

使用边界

零值不一定表示“没有提供”。false0也可能是合法业务值。需要区分“未提供”和“明确传入零值”时,应使用指针或其他可选值类型:

type UserUpdate struct {
Enabled *bool
Count   *int
}

因此,OmitZero应保持显式、按需启用,而不应成为默认过滤行为。

最终结论

nil空数组证明了空值不一定是零值,全零 UUID 证明了零值也不一定是空值,time.Time.IsZero和自定义IsZero()则说明零值是类型可以正式声明的行为。三者相比,OmitEmpty才是规则最宽泛、心智负担最高的 API。新增OmitZero不是制造重复概念,而是将 Go 已有的零值语义从框架的空值策略中明确拆分出来。它既提高了 API 的可预测性,也避免空集合条件被错误删除等实际风险,因此属于正确性能力,而不是重复的便利 API。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

discuss We need discuss to make decision.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants