跳到主要内容

视图(Views)

Ent 支持操作数据库视图。与通常由表支撑的常规 Ent 类型(模式)不同,视图充当“虚拟表”,其数据来自查询结果。以下示例演示了如何在 Ent 中定义 VIEW。有关不同选项的更多详细信息,请参阅本指南的其余部分。

ent/schema/user.go
// 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"),
}
}
表与视图的主要区别
  • 视图是只读的,因此不会为它们生成变更构建器。如果您想要定义可插入/更新的视图,请将其定义为常规模式,并按照下面的指南配置它们的迁移。
  • ent.Schema 不同,ent.View 没有默认的 ID 字段。如果要在视图中包含 id 字段,可以显式地将其定义为一个字段。
  • 无法在视图上注册钩子,因为它们是只读的。
  • Atlas 为 Ent 视图提供内置支持,包括版本化迁移和测试。但是,如果您不使用 Atlas 且希望使用视图,则需要手动管理它们的迁移,因为 Ent 不提供对它们的模式迁移支持。

简介

ent/schema 包中定义的视图会嵌入 ent.View 类型,而不是 ent.Schema 类型。除了字段,它们还可以有边(edge)、拦截器(interceptor)和注解(annotation),以实现额外的集成。例如:

ent/schema/user.go
// 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 的表。

Atlas 指南

本文档的其余部分假设您将 Ent 与 Atlas Pro 一起使用,因为 Ent 除了对表和关系之外,不支持视图或其他数据库对象的迁移。但是,使用 Atlas 或其专业订阅不是强制性的。Ent 不依赖特定的迁移引擎,只要视图存在于数据库中,客户端就应该能够查询它。

要配置我们的视图定义(AS SELECT ...),我们有两个选项:

  1. ent/schema 的 Go 代码中定义它。
  2. ent/schema 与视图定义分离,并在外部创建它。可以手动创建,也可以使用 Atlas 自动创建。

让我们探讨这两个选项:

Go 定义

此示例演示了如何定义一个 ent.View,并在 Ent 模式中指定其 SQL 定义(AS ...)。

这种方法的主要优点是,CREATE VIEW 的正确性在迁移时检查,而不是在查询时。例如,如果在您的 ent.schema 中定义的某个 ent.Field 在您的 SQL 定义中不存在,PostgreSQL 将返回以下错误:

create "clean_users" view: pq: CREATE VIEW 指定的列名多于列数

以下是一个视图定义的示例,它同时定义了字段及其 SELECT 查询:

使用 entsql.ViewFor API,您可以使用方言感知构建器来定义视图。请注意,您可以为不同方言提供多个视图定义,Atlas 将使用与迁移方言匹配的定义。

ent/schema/user.go
// 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"),
}
}

让我们通过创建一个包含必要参数的 atlas.hcl 文件来简化配置。我们将在下面的用法部分使用此配置文件:

atlas.hcl
env "local" {
src = "ent://ent/schema"
dev = "docker://postgres/16/dev?search_path=public"
}

完整示例可在 Ent 代码库 中找到。

外部定义

此示例演示了如何定义 ent.View,但将其定义保留在单独的文件(schema.sql)中或在数据库中手动创建。

ent/schema/user.go
// 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 的文件,并将视图定义粘贴到其中:

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/schemaschema.sql 文件:

atlas.hcl
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

请注意,将创建一个新的迁移文件,内容如下:

migrations/20240712090543.sql
-- 创建 "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 语句。然后,按照上面外部定义部分所述,在数据库中定义该视图。

ent/schema/user.go
// 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"),
}
}