功能标志(Feature Flags)
本框架提供了一组代码生成功能,可通过标志进行添加或移除。
使用
功能标志可以通过 CLI 标志提供,也可以作为 gen 包的参数提供。
CLI
go run -mod=mod entgo.io/ent/cmd/ent generate --feature privacy,entql ./ent/schema
Go
// +build ignore
package main
import (
"log"
"text/template"
"entgo.io/ent/entc"
"entgo.io/ent/entc/gen"
)
func main() {
err := entc.Generate("./schema", &gen.Config{
Features: []gen.Feature{
gen.FeaturePrivacy,
gen.FeatureEntQL,
},
Templates: []*gen.Template{
gen.MustParse(gen.NewTemplate("static").
Funcs(template.FuncMap{"title": strings.ToTitle}).
ParseFiles("template/static.tmpl")),
},
})
if err != nil {
log.Fatalf("running ent codegen: %v", err)
}
}
功能列表
自动解决合并冲突
schema/snapshot 选项告知 entc(ent 代码生成)在内部包中存储最新的模式快照,并在用户模式无法被创建时使用快照自动解决合并冲突。
这个选项可以使用 --feature schema/snapshot 标志添加到项目中,参见 ent/ent/issues/852 了解更多背景信息。
隐私层(Privacy Layer)
隐私层允许为数据库中实体的查询和突变配置隐私策略。
这个选项可以使用 --feature privacy 标志添加到项目中,参见 隐私(Privacy) 了解更多信息。
EntQL 过滤
entql 选项为不同查询构建器在运行时提供通用且动态的过滤能力。
这个选项可以使用 --feature entql 标志添加到项目中,参见 隐私(Privacy) 了解更多信息。
命名边(Named Edges)
namedges 选项为预加载自定义名称的边提供 API。
这个选项可以使用 --feature namedges 标志添加到项目中,参见 预加载 了解更多信息。
双向边引用
bidiedges 选项在预加载一对多或多对多(O2M 或 O2O)边时指导 Ent 设置双向引用。
这个选项可以使用 --feature bidiedges 标志添加到项目中。
使用标准 encoding/json.MarshalJSON 时须在调用 json.Marshal 之前解除循环引用。
模式配置
sql/schemaconfig 选项可以让你向模型传入替代的 SQL 数据库名称。当你的模型不在同一个数据库中且分散在不同的模式中时此选项比较有用。
这个选项可以使用 --feature sql/schemaconfig 标志添加到项目中。一旦你生成代码,可以像如下使用新的选项:
c, err := ent.Open(dialect, conn, ent.AlternateSchema(ent.SchemaConfig{
User: "usersdb",
Car: "carsdb",
}))
c.User.Query().All(ctx) // SELECT * FROM `usersdb`.`users`
c.Car.Query().All(ctx) // SELECT * FROM `carsdb`.`cars`
行级锁定(Row-level Locks)
sql/lock 选项可以让你使用 SQL 的 SELECT ... FOR {UPDATE | SHARE} 语法配置行级锁定。
这个选项可以使用 --feature sql/lock 标志添加到项目中。
tx, err := client.Tx(ctx)
if err != nil {
log.Fatal(err)
}
tx.Pet.Query().
Where(pet.Name(name)).
ForUpdate().
Only(ctx)
tx.Pet.Query().
Where(pet.ID(id)).
ForShare(
sql.WithLockTables(pet.Table),
sql.WithLockAction(sql.NoWait),
).
Only(ctx)
自定义 SQL 修改器
sql/modifier 选项可以让你添加自定义 SQL 修改器到构建器中,并在执行前修改语句。
这个选项可以使用 --feature sql/modifier 标志添加到项目中。
修改示例 1
client.Pet.
Query().
Modify(func(s *sql.Selector) {
s.Select("SUM(LENGTH(name))")
}).
IntX(ctx)
上面的代码会产生如下 SQL 查询:
SELECT SUM(LENGTH(name)) FROM `pet`
选择和扫描动态值
如果你正在使用 SQL 修改器并需要扫描 Ent 模式定义中不存在的动态值,比如聚合或自定义排序,你可以在 sql.Selector 中使用 AppendSelect/AppendSelectAs。
随后使用每个实体中定义的 Value 方法访问他们:
const as = "name_length"
// Query the entity with the dynamic value.
p := client.Pet.Query().
Modify(func(s *sql.Selector) {
s.AppendSelectAs("LENGTH(name)", as)
}).
FirstX(ctx)
// Read the value from the entity.
n, err := p.Value(as)
if err != nil {
log.Fatal(err)
}
fmt.Println("Name length: %d == %d", n, len(p.Name))
修改示例 2
var p1 []struct {
ent.Pet
NameLength int `sql:"length"`
}
client.Pet.Query().
Order(ent.Asc(pet.FieldID)).
Modify(func(s *sql.Selector) {
s.AppendSelect("LENGTH(name)")
}).
ScanX(ctx, &p1)
上面的代码会产生如下 SQL 查询:
SELECT `pet`.*, LENGTH(name) FROM `pet` ORDER BY `pet`.`id` ASC
修改示例 3
var v []struct {
Count int `json:"count"`
Price int `json:"price"`
CreatedAt time.Time `json:"created_at"`
}
client.User.
Query().
Where(
user.CreatedAtGT(x),
user.CreatedAtLT(y),
).
Modify(func(s *sql.Selector) {
s.Select(
sql.As(sql.Count("*"), "count"),
sql.As(sql.Sum("price"), "price"),
sql.As("DATE(created_at)", "created_at"),
).
GroupBy("DATE(created_at)").
OrderBy(sql.Desc("DATE(created_at)"))
}).
ScanX(ctx, &v)
上面的代码会产生如下 SQL 查询:
SELECT
COUNT(*) AS `count`,
SUM(`price`) AS `price`,
DATE(created_at) AS `created_at`
FROM
`users`
WHERE
`created_at` > x AND `created_at` < y
GROUP BY
DATE(created_at)
ORDER BY
DATE(created_at) DESC
修改示例 4
var gs []struct {
ent.Group
UsersCount int `sql:"users_count"`
}
client.Group.Query().
Order(ent.Asc(group.FieldID)).
Modify(func(s *sql.Selector) {
t := sql.Table(group.UsersTable)
s.LeftJoin(t).
On(
s.C(group.FieldID),
t.C(group.UsersPrimaryKey[1]),
).
// Append the "users_count" column to the selected columns.
AppendSelect(
sql.As(sql.Count(t.C(group.UsersPrimaryKey[1])), "users_count"),
).
GroupBy(s.C(group.FieldID))
}).
ScanX(ctx, &gs)
上面的代码会产生如下 SQL 查询:
SELECT
`groups`.*,
COUNT(`t1`.`group_id`) AS `users_count`
FROM
`groups` LEFT JOIN `user_groups` AS `t1`
ON
`groups`.`id` = `t1`.`group_id`
GROUP BY
`groups`.`id`
ORDER BY
`groups`.`id` ASC
修改示例 5
client.User.Update().
Modify(func(s *sql.UpdateBuilder) {
s.Set(user.FieldName, sql.Expr(fmt.Sprintf("UPPER(%s)", user.FieldName)))
}).
ExecX(ctx)
上面的代码会产生如下 SQL 查询:
UPDATE `users` SET `name` = UPPER(`name`)
修改示例 6
client.User.Update().
Modify(func(u *sql.UpdateBuilder) {
u.Set(user.FieldID, sql.ExprFunc(func(b *sql.Builder) {
b.Ident(user.FieldID).WriteOp(sql.OpAdd).Arg(1)
}))
u.OrderBy(sql.Desc(user.FieldID))
}).
ExecX(ctx)
上面的代码会产生如下 SQL 查询:
UPDATE `users` SET `id` = `id` + 1 ORDER BY `id` DESC
修改示例 7
Append elements to the values array in a JSON column:
client.User.Update().
Modify(func(u *sql.UpdateBuilder) {
sqljson.Append(u, user.FieldTags, []string{"tag1", "tag2"}, sqljson.Path("values"))
}).
ExecX(ctx)
上面的代码会产生如下 SQL 查询:
UPDATE `users` SET `tags` = CASE
WHEN (JSON_TYPE(JSON_EXTRACT(`tags`, '$.values')) IS NULL OR JSON_TYPE(JSON_EXTRACT(`tags`, '$.values')) = 'NULL')
THEN JSON_SET(`tags`, '$.values', JSON_ARRAY(?, ?))
ELSE JSON_ARRAY_APPEND(`tags`, '$.values', ?, '$.values', ?) END
WHERE `id` = ?
SQL 原始 API(Raw API)
sql/execquery 选项可以让你使用驱动程序底层的 ExecContext/QueryContext 执行语句。
完整文档参见 DB.ExecContext 和 DB.QueryContext。
// From ent.Client.
if _, err := client.ExecContext(ctx, "TRUNCATE t1"); err != nil {
return err
}
// From ent.Tx.
tx, err := client.Tx(ctx)
if err != nil {
return err
}
if err := tx.User.Create().Exec(ctx); err != nil {
return err
}
if _, err := tx.ExecContext("SAVEPOINT user_created"); err != nil {
return err
}
// ...
使用 ExecContext/QueryContext 执行的语句不经过 Ent,可能会跳过应用程序的基础层,比如钩子、隐私(认证)或验证器。
更新插入(Upsert)
sql/upsert 选项可以让你使用 SQL 的 ON CONFLICT 或 ON DUPLICATE KEY 语法配置更新插入或批量更新插入。
完整示例参见 更新或插入一个实体。
这个选项可以使用 --feature sql/upsert 标志添加到项目中。
// Use the new values that were set on create.
id, err := client.User.
Create().
SetAge(30).
SetName("Ariel").
OnConflict().
UpdateNewValues().
ID(ctx)
// In PostgreSQL, the conflict target is required.
err := client.User.
Create().
SetAge(30).
SetName("Ariel").
OnConflictColumns(user.FieldName).
UpdateNewValues().
Exec(ctx)
// Bulk upsert is also supported.
client.User.
CreateBulk(builders...).
OnConflict(
sql.ConflictWhere(...),
sql.UpdateWhere(...),
).
UpdateNewValues().
Exec(ctx)
// INSERT INTO "users" (...) VALUES ... ON CONFLICT WHERE ... DO UPDATE SET ... WHERE ...
全球唯一 ID
默认情况下,SQL 主键在每个表中从 1 开始计数,这意味着不同类型的多个实体可能共享相同的ID。 这与 AWS Neptune 不同,后者使用 UUID 作为节点 ID。
如果你使用 GraphQL 则不能很好地运行,因为它要求对象 ID 是唯一的。
可以简单地使用 --feature sql/globalid 为你的项目启动全球唯一 ID 支持。
如果你过去已经在使用 migrate.WithGlobalUniqueID(true) 迁移选项,请你在项目中使用新的全球 ID 功能前查看 本指南 。
它如何工作? ent 迁移为每个实体(表)分配 1<<32 范围的 ID,并将这些信息存储在生成的代码中(internal/globalid.go)。
例如类型 A 的 ID 范围是 [1,4294967296),类型 B 的范围是 [4294967296,8589934592) 等。
注意如果此选项被启用,可能存在的表的最大数量是 65535。