跳到主要内容

视图

Ent 支持数据库视图。不同于通常表示为表的常规Ent类型(模式),视图作为“虚拟表”存在,其数据源自查询结果。 以下示例演示了如何在Ent中定义 VIEW 。有关不同选项的详细信息,请参阅本指南后续内容。

ent/schema/user.go
// CleanUser represents a user without its PII field.
type CleanUser struct {
ent.View
}

// Annotations of the 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"))
}),
}
}

// Fields of the CleanUser.
func (CleanUser) Fields() []ent.Field {
return []ent.Field{
field.String("name"),
field.String("public_info"),
}
}
表与视图核心不同
  • 视图是只读的,因此不会为其生成突变构建器。若需定义可插入/可更新的视图,请将其作为常规模式进行定义,并遵循以下指南配置其迁移操作。
  • 不同同于 ent.Schemaent.View 没有 ID 字段。如果要在视图中包含 id 字段,需要显式地将它定义为一个字段。
  • 因为视图是只读的,因此钩子不能在视图上注册。
  • Atlas 为 Ent 视图版本化迁移和测试提供了内置的支持。如果你不使用 Atlas 却想使用视图,那么需要手工管理迁移,因为 Ent 不为他们提供模式迁移。

介绍

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

ent/schema/user.go
// CleanUser represents a user without its PII field.
type CleanUser struct {
ent.View
}

// Fields of the CleanUser.
func (CleanUser) Fields() []ent.Field {
return []ent.Field{
// Note, unlike real schemas (tables, defined with ent.Schema),
// the "id" field should be defined manually if needed.
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 或其 Pro 订阅 非强制要求。 Ent 并不要求特定的迁移引擎,只要视图存在于数据库中,客户端就应该能够查询到它。

配置视图定义 (AS SELECT ...),有两种选择:

  1. 在 Go 语言代码的 ent/schema 定义。
  2. 保持 ent/schema 独立于视图定义并通过手工或 Atlas 外部创建它。

让我们看一下这两种选择:

Go 语言定义

此示例演示了如何在 Ent 模式中定义具有 SQL定义(AS ...)的 ent.View

此方法的主要优势在于,CREATE VIEW 的正确性是在迁移过程中进行验证,而非在查询时验证。 例如,若 ent/schema 中定义的某个 ent.Field 字段在 SQL 定义中不存在, PostgreSQL 将返回以下错误:

create "clean_users" view: pq: CREATE VIEW specifies more column names than columns

以下是一个视图定义的示例,包含其字段及其 SELECT 查询:

使用 entsql.ViewFor API 时,你可以通过支持方言的构建器来定义视图。请注意,你可以为不同方言定义多个视图,Atlas 将自动选用与迁移方言匹配的视图。

ent/schema/user.go
// CleanUser represents a user without its PII field.
type CleanUser struct {
ent.View
}

// Annotations of the 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"))
}),
}
}

// Fields of the CleanUser.
func (CleanUser) Fields() []ent.Field {
return []ent.Field{
// Note, unlike real schemas (tables, defined with ent.Schema),
// the "id" field should be defined manually if needed.
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 represents a user without its PII field.
type CleanUser struct {
ent.View
}

// Fields of the 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
-- Create "clean_users" view
CREATE VIEW "clean_users" ("id", "name", "public_info") AS SELECT id,
name,
public_info
FROM users;

下一步我们创建一个带有 composite_schema 的配置文件 atlas.hcl,包括了 ent/schemaschema.sql 文件: Next, we create an atlas.hcl config file with a composite_schema that includes both our ent/schema and the schema.sql file:

atlas.hcl
data "composite_schema" "app" {
# Load the ent schema first with all its tables.
schema "public" {
url = "ent://ent/schema"
}
# Then, load the views defined in the schema.sql file.
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 表之后的模式中定义。

-- Create "users" table
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"));
-- Create "clean_users" view
CREATE VIEW "clean_users" ("id", "name", "public_info") AS SELECT id,
name,
public_info
FROM users;

生成模式的迁移

运行下面的命令生成模式的迁移:

atlas migrate diff \
--env local

会创建具有下面内容的迁移文件:

migrations/20240712090543.sql
-- Create "users" table
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"));
-- Create "clean_users" view
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)
}
// Automatically update the database with the desired schema.
// Another option, is to use 'migrate apply' or 'schema apply' manually.
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)
}
// Run assertions.
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 represents a user without its PII field.
type CleanUser struct {
ent.Schema
}

// Annotations of the CleanUser.
func (CleanUser) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Skip(),
}
}

// Fields of the CleanUser.
func (CleanUser) Fields() []ent.Field {
return []ent.Field{
field.Int("id"),
field.String("name"),
field.String("public_info"),
}
}