重构: 虚拟资源改用 NewVirtualResource 显式构造,移除 WithVirtual

WithVirtual(bool) 只是给 New() 打一个标记位,虚拟资源需要的表达式(子查询)没有
承载的地方,之前借用 table string 字段硬拼字符串。改成 NewVirtualResource(res,
code, table, opts...) 专门构造:table 参数直接接收 exp.SQLExpression,
GetTableExpr 对虚拟资源直接用它生成子查询表达式,不用再手工拼括号和字符串;
container/conn/historyRoles 从传入的父资源上直接复用。

GetTableExpr 返回值类型从 exp.LiteralExpression 放宽成 exp.Aliaseable,
兼容 db.T(...)(IdentifierExpression)和 db.L(...)(LiteralExpression)两种
返回值。
This commit is contained in:
2026-08-20 15:58:03 +08:00
parent acb55bb53a
commit 5cdf1cc00d
4 changed files with 46 additions and 15 deletions
+1 -1
View File
@@ -64,7 +64,7 @@ type Resource interface {
// GetTable 原始表名 // GetTable 原始表名
GetTable() string GetTable() string
// GetTableExpr 可用于 FROM/JOIN 的表引用表达式 // GetTableExpr 可用于 FROM/JOIN 的表引用表达式
GetTableExpr() exp.LiteralExpression GetTableExpr() exp.Aliaseable
DB() *db.Database DB() *db.Database
BeginTransaction() (*db.TxDatabase, error) BeginTransaction() (*db.TxDatabase, error)
+4 -5
View File
@@ -565,14 +565,13 @@ func (t *hooksTest) TestVirtualResource_SkipsRolesConditionAndOnAfter() {
}, nil }, nil
}) })
res := resx.New(app, "User", "users", baseRes := resx.New(app, "User", "users", resx.WithConn("default"))
resx.WithConn("default"), res := resx.NewVirtualResource(baseRes, "User", db.From(db.T("users")),
resx.WithVirtual(true),
resx.WithFields(resx.NewResField("name", "User", resx.FieldWithName("姓名"), resx.FieldWithDataType(req.ResString))), resx.WithFields(resx.NewResField("name", "User", resx.FieldWithName("姓名"), resx.FieldWithDataType(req.ResString))),
) )
// 实际生成的 SQL(虚拟资源用字面量拼表名,且没有 owned_user 权限条件): // 实际生成的 SQL(虚拟资源拿子查询拼表名,且没有 owned_user 权限条件):
// UPDATE (users) AS `User` SET `name`='新名字' WHERE (`id` = 1) // UPDATE (SELECT * FROM `users`) AS `User` SET `name`='新名字' WHERE (`id` = 1)
sql, _, _ := res.GetDBTable(base.GetAnonymous()).Update(). sql, _, _ := res.GetDBTable(base.GetAnonymous()).Update().
Set(db.Record{"name": "新名字"}). Set(db.Record{"name": "新名字"}).
Where(db.C("id").Eq(1)). Where(db.C("id").Eq(1)).
+8 -9
View File
@@ -95,6 +95,10 @@ type resource struct {
conn string conn string
isResVirtual bool isResVirtual bool
table string table string
// virtualTable 只有虚拟资源(NewVirtualResource 构造的)才会设置,table 留空。GetTableExpr
// 用它代替 table 拼子查询:db.V(sd.Expression()) 让方言自己生成/加括号,不用手写 "(" + sql + ")"
// 拼字符串。
virtualTable exp.SQLExpression
primarykey string primarykey string
historyRoles []string historyRoles []string
fields []req.ResField fields []req.ResField
@@ -150,11 +154,6 @@ func WithConn(v string) Option {
return func(r *resource) { r.conn = v } return func(r *resource) { r.conn = v }
} }
// WithVirtual 标记为虚拟资源
func WithVirtual(v bool) Option {
return func(r *resource) { r.isResVirtual = v }
}
// WithPrimarykey 设置主键字段名,不设置则默认为 "id" // WithPrimarykey 设置主键字段名,不设置则默认为 "id"
func WithPrimarykey(v string) Option { func WithPrimarykey(v string) Option {
return func(r *resource) { r.primarykey = v } return func(r *resource) { r.primarykey = v }
@@ -290,17 +289,17 @@ func (this *resource) autoCreateTable(conn *db.Database) error {
}) })
} }
// GetTable 原始表名 // GetTable 原始表名;虚拟资源没有物理表名,返回空字符串
func (this *resource) GetTable() string { func (this *resource) GetTable() string {
return this.table return this.table
} }
// GetTableExpr 获取资源对应的可用于 FROM/JOIN 的表引用表达式 // GetTableExpr 获取资源对应的可用于 FROM/JOIN 的表引用表达式
func (this *resource) GetTableExpr() exp.LiteralExpression { func (this *resource) GetTableExpr() exp.Aliaseable {
if this.isResVirtual { if this.isResVirtual {
return db.L("(" + this.table + ")") return db.L("?", this.virtualTable.Clone())
} }
return db.V(db.T(this.table)) return db.T(this.table)
} }
// GetDBTable 获取资源对应的数据库连接 // GetDBTable 获取资源对应的数据库连接
+33
View File
@@ -0,0 +1,33 @@
package resx
import (
"sync"
"git.fsdpf.net/go/db/exp"
"git.fsdpf.net/go/req"
)
func NewVirtualResource(res req.Resource, code string, table exp.SQLExpression, opts ...Option) req.Resource {
pRes, ok := res.(*resource)
if !ok {
panic("resx.NewVirtualResource: res 必须是 resx.New 构造出来的 req.Resource")
}
r := &resource{
container: pRes.container,
conn: pRes.conn,
historyRoles: pRes.historyRoles,
initOnce: &sync.Once{},
uuid: code,
code: code,
name: code,
table: code,
isResVirtual: true,
virtualTable: table,
primarykey: "id",
}
for _, opt := range opts {
opt(r)
}
return r
}