视图(Views)
Ent 支持操作数据库视图。与通常由表支撑的常规 Ent 类型(模式)不同,视图充当“虚拟表”,其数据来自查询结果。以下示例演示了如何在 Ent 中定义 VIEW。有关不同选项的更多详细信息,请参阅本指南的其余部分。
- Builder Definition
- Raw Definition
- External Definition
// CleanUser 表示不含 PII 字段的用户。
type CleanUser struct {
ent.View
}
// CleanUser 的注解。
func (CleanUser) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.ViewFor(dialect.Postgres, func(s *sql.Selector) {
s.Select("name", "public_info").From(sql.Table("users"))
}),
}
}
// CleanUser 的字段。
func (CleanUser) Fields() []ent.Field {
return []ent.Field{
field.String("name"),
field.String("public_info"),
}
}
// CleanUser 表示不含 PII 字段的用户。
type CleanUser struct {
ent.View
}
// CleanUser 的注解。
func (CleanUser) Annotations() []schema.Annotation {
return []schema.Annotation{
// 或者,您可以使用原始定义来定义视图。
// 但请注意,如果我们为目标方言(Postgres)定义了 ViewFor 注解,
// 则此定义将被跳过。
entsql.View(`SELECT name, public_info FROM users`),
}
}
// CleanUser 的字段。
func (CleanUser) Fields() []ent.Field {
return []ent.Field{
field.String("name"),
field.String("public_info"),
}
}
// CleanUser 表示不含 PII 字段的用户。
type CleanUser struct {
ent.View
}
// 视图定义在单独的文件(`schema.sql`)中指定,
// 并使用 Atlas 的 `composite_schema` 数据源加载。
// CleanUser 的字段。
func (CleanUser) Fields() []ent.Field {
return []ent.Field{
field.String("name"),
field.String("public_info"),
}
}
- 视图是只读的,因此不会为它们生成变更构建器。如果您想要定义可插入/更新的视图,请将其定义为常规模式,并按照下面的指南配置它们的迁移。
- 与
ent.Schema不同,ent.View没有默认的ID字段。如果要在视图中包含id字段,可以显式地将其定义为一个字段。 - 无法在视图上注册钩子,因为它们是只读的。
- Atlas 为 Ent 视图提供内置支持,包括版本化迁移和测试。但是,如果您不使用 Atlas 且希望使用视图,则需要手动管理它们的迁移,因为 Ent 不提供对它们的模式迁移支持。
简介
在 ent/schema 包中定义的视图会嵌入 ent.View 类型,而不是 ent.Schema 类型。除了字段,它们还可以有边(edge)、拦截器(interceptor)和注解(annotation),以实现额外的集成。例如:
// CleanUser 表示不含 PII 字段的用户。
type CleanUser struct {
ent.View
}
// CleanUser 的字段。
func (CleanUser) Fields() []ent.Field {
return []ent.Field{
// 请注意,与真实模式(表,用 ent.Schema 定义)不同,
// 如果需要,“id”字段应手动定义。
field.Int("id"),
field.String("name"),
field.String("public_info"),
}
}
定义完成后,您可以运行 go generate ./ent 来创建与此视图交互所需的资源。例如:
client.CleanUser.Query().OnlyX(ctx)
请注意,不会为 ent.View 生成 Create/Update/Delete 构建器。
迁移与测试
定义视图模式后,我们需要告知 Ent(和 Atlas)定义此视图的 SQL 查询。如果未配置,运行如上所述的 Ent 查询将会失败,因为数据库中不存在名为 clean_users 的表。
本文档的其余部分假设您将 Ent 与 Atlas Pro 一起使用,因为 Ent 除了对表和关系之外,不支持视图或其他数据库对象的迁移。但是,使用 Atlas 或其专业订阅不是强制性的。Ent 不依赖特定的迁移引擎,只要视图存在于数据库中,客户端就应该能够查询它。
要配置我们的视图定义(AS SELECT ...),我们有两个选项:
- 在
ent/schema的 Go 代码中定义它。 - 将
ent/schema与视图定义分离,并在外部创建它。可以手动创建,也可以使用 Atlas 自动创建。
让我们探讨这两个选项:
Go 定义
此示例演示了如何定义一个 ent.View,并在 Ent 模式中指定其 SQL 定义(AS ...)。
这种方法的主要优点是,CREATE VIEW 的正确性在迁移时检查,而不是在查询时。例如,如果在您的 ent.schema 中定义的某个 ent.Field 在您的 SQL 定义中不存在,PostgreSQL 将返回以下错误:
以下是一个视图定义的示例,它同时定义了字段及其 SELECT 查询:
- Builder Definition
- Raw Definition
使用 entsql.ViewFor API,您可以使用方言感知构建器来定义视图。请注意,您可以为不同方言提供多个视图定义,Atlas 将使用与迁移方言匹配的定义。
// CleanUser 表示不含 PII 字段的用户。
type CleanUser struct {
ent.View
}
// CleanUser 的注解。
func (CleanUser) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.ViewFor(dialect.Postgres, func(s *sql.Selector) {
s.Select("id", "name", "public_info").From(sql.Table("users"))
}),
}
}
// CleanUser 的字段。
func (CleanUser) Fields() []ent.Field {
return []ent.Field{
// 请注意,与真实模式(表,用 ent.Schema 定义)不同,
// 如果需要,“id”字段应手动定义。
field.Int("id"),
field.String("name"),
field.String("public_info"),
}
}
或者,您可以使用原始定义来定义视图。但请注意,如果我们为目标方言(此处为 Postgres)定义了 ViewFor 注解,则此定义将被跳过。
// CleanUser 表示不含 PII 字段的用户。
type CleanUser struct {
ent.View
}
// CleanUser 的注解。
func (CleanUser) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.View(`SELECT id, name, public_info FROM users`),
}
}
// CleanUser 的字段。
func (CleanUser) Fields() []ent.Field {
return []ent.Field{
// 请注意,与真实模式(表,用 ent.Schema 定义)不同,
// 如果需要,“id”字段应手动定义。
field.Int("id"),
field.String("name"),
field.String("public_info"),
}
}
让我们通过创建一个包含必要参数的 atlas.hcl 文件来简化配置。我们将在下面的用法部分使用此配置文件:
env "local" {
src = "ent://ent/schema"
dev = "docker://postgres/16/dev?search_path=public"
}
完整示例可在 Ent 代码库 中找到。
外部定义
此示例演示了如何定义 ent.View,但将其定义保留在单独的文件(schema.sql)中或在数据库中手动创建。
// CleanUser 表示不含 PII 字段的用户。
type CleanUser struct {
ent.View
}
// CleanUser 的字段。
func (CleanUser) Fields() []ent.Field {
return []ent.Field{
field.Int("id"),
field.String("name"),
field.String("public_info"),
}
}
在 Ent 中定义视图模式后,需要单独配置(或创建)SQL CREATE VIEW 定义,以确保当 Ent 运行时查询数据库时该视图存在。
对于此示例,我们将使用 Atlas 的 composite_schema 数据源,从我们的 ent/schema 包和一个描述此视图的 SQL 文件构建模式图。让我们创建一个名为 schema.sql 的文件,并将视图定义粘贴到其中:
-- 创建 "clean_users" 视图
CREATE VIEW "clean_users" ("id", "name", "public_info") AS SELECT id,
name,
public_info
FROM users;
接下来,我们创建一个 atlas.hcl 配置文件,其中包含一个 composite_schema,它同时包含我们的 ent/schema 和 schema.sql 文件:
data "composite_schema" "app" {
# 首先加载包含所有表的 ent 模式。
schema "public" {
url = "ent://ent/schema"
}
# 然后,加载 schema.sql 文件中定义的视图。
schema "public" {
url = "file://schema.sql"
}
}
env "local" {
src = data.composite_schema.app.url
dev = "docker://postgres/15/dev?search_path=public"
}
完整示例可在 Ent 代码库 中找到。
用法
设置好模式后,我们可以使用 atlas schema inspect 命令获取其表示形式,为其生成迁移,将其应用到数据库等。以下是一些命令,帮助您开始使用 Atlas:
检查模式
atlas schema inspect 命令通常用于检查数据库。但是,我们也可以使用它来检查我们的 ent/schema 并打印其 SQL 表示形式:
atlas schema inspect \
--env local \
--url env://src \
--format '{{ sql . }}'
上面的命令会打印以下 SQL。请注意,clean_users 视图在模式中定义在 users 表之后:
-- 创建 "users" 表
CREATE TABLE "users" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "name" character varying NOT NULL, "public_info" character varying NOT NULL, "private_info" character varying NOT NULL, PRIMARY KEY ("id"));
-- 创建 "clean_users" 视图
CREATE VIEW "clean_users" ("id", "name", "public_info") AS SELECT id,
name,
public_info
FROM users;
为模式生成迁移
要为模式生成迁移,请运行以下命令:
atlas migrate diff \
--env local
请注意,将创建一个新的迁移文件,内容如下:
-- 创建 "users" 表
CREATE TABLE "users" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "name" character varying NOT NULL, "public_info" character varying NOT NULL, "private_info" character varying NOT NULL, PRIMARY KEY ("id"));
-- 创建 "clean_users" 视图
CREATE VIEW "clean_users" ("id", "name", "public_info") AS SELECT id,
name,
public_info
FROM users;
应用迁移
要将上述生成的迁移应用到数据库,请运行以下命令:
atlas migrate apply \
--env local \
--url "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable"
有时,需要直接将模式应用到数据库,而无需生成迁移文件。例如,在试验模式更改、为测试启动数据库等情况下。在这种情况下,您可以使用以下命令直接将模式应用到数据库:
atlas schema apply \
--env local \
--url "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable"
或者,在编写测试时,您可以在运行断言之前使用 Atlas Go SDK 来使模式与数据库保持一致:
ac, err := atlasexec.NewClient(".", "atlas")
if err != nil {
log.Fatalf("failed to initialize client: %w", err)
}
// 自动使用所需模式更新数据库。
// 另一种选择是手动使用 'migrate apply' 或 'schema apply'。
if _, err := ac.SchemaApply(ctx, &atlasexec.SchemaApplyParams{
Env: "local",
URL: "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable",
AutoApprove: true,
}); err != nil {
log.Fatalf("failed to apply schema changes: %w", err)
}
// 运行断言。
u1 := client.User.Create().SetName("a8m").SetPrivateInfo("secret").SetPublicInfo("public").SaveX(ctx)
v1 := client.CleanUser.Query().OnlyX(ctx)
require.Equal(t, u1.ID, v1.ID)
require.Equal(t, u1.Name, v1.Name)
require.Equal(t, u1.PublicInfo, v1.PublicInfo)
可插入/可更新视图
如果您想定义一个可插入/可更新视图,请将其设置为常规类型(ent.Schema)并添加 entsql.Skip() 注解,以防止 Ent 为此视图生成 CREATE TABLE 语句。然后,按照上面外部定义部分所述,在数据库中定义该视图。
// CleanUser 表示不含 PII 字段的用户。
type CleanUser struct {
ent.Schema
}
// CleanUser 的注解。
func (CleanUser) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Skip(),
}
}
// CleanUser 的字段。
func (CleanUser) Fields() []ent.Field {
return []ent.Field{
field.Int("id"),
field.String("name"),
field.String("public_info"),
}
}