在 Ent 模式中使用函数索引
函数索引是一种索引类型,其键部分基于表达式值而非列值。这种索引类型对于索引未存储在表中的函数或表达式结果非常有用。MySQL、MariaDB、PostgreSQL 和 SQLite 均支持此功能。
本指南介绍了如何通过函数索引扩展您的 Ent 模式,并使用 Atlas 配置模式迁移,以将函数索引和 Ent 模式作为单个迁移单元进行管理。
本指南中使用的 复合模式 (Composite Schema) 的 Atlas 支持功能为 Pro 用户专享。要使用此功能,请运行:
atlas login
安装 Atlas
To install the latest release of Atlas, simply run one of the following commands in your terminal, or check out the Atlas website:
- macOS + Linux
- Homebrew
- Docker
- Windows
curl -sSf https://atlasgo.sh | sh
brew install ariga/tap/atlas
docker pull arigaio/atlas
docker run --rm arigaio/atlas --help
If the container needs access to the host network or a local directory, use the --net=host flag and mount the desired
directory:
docker run --rm --net=host \
-v $(pwd)/migrations:/migrations \
arigaio/atlas migrate apply
--url "mysql://root:pass@:3306/test"
Download the latest release and move the atlas binary to a file location on your system PATH.
登录 Atlas
$ atlas login a8m
您现已连接到 Atlas Cloud 上的 "a8m" 账户。
复合模式
ent/schema 包主要用于定义 Ent 类型(对象)、其字段、边和逻辑。函数索引在 Ent 模式中没有表示形式,因为 Ent 支持在字段、边(外键)及其组合上定义索引。
为了将带有函数索引的 PostgreSQL 模式迁移扩展到我们的 Ent 类型(表),我们配置 Atlas 以从 复合模式 (Composite Schema) 数据源读取模式状态。请按照以下步骤为您的项目进行配置:
- 定义一个简单的模式,包含一个类型(表):
User(表users):
// User 持有 User 实体的模式定义。
type User struct {
ent.Schema
}
// User 的字段。
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
Comment("在 schema.sql 中为 lower(name) 定义了唯一索引"),
}
}
- 接下来,在
schema.sql文件中为name字段定义一个函数索引:
-- 在 name 列的小写值上创建一个函数(唯一)索引。
CREATE UNIQUE INDEX unique_name ON "users" ((lower("name")));
- 创建一个简单的
atlas.hcl配置文件,其中包含一个composite_schema,该配置同时包含在schema.sql中定义的函数索引和您的 Ent 模式:
data "composite_schema" "app" {
# 首先加载包含所有表的 ent 模式。
schema "public" {
url = "ent://ent/schema"
}
# 然后,加载函数索引。
schema "public" {
url = "file://schema.sql"
}
}
env "local" {
src = data.composite_schema.app.url
dev = "docker://postgres/15/dev?search_path=public"
}
使用
设置好复合模式后,我们可以使用 atlas schema inspect 命令获取其表示形式,为其生成模式迁移,将其应用到数据库等。以下是一些命令,帮助您开始使用 Atlas:
检查模式
atlas schema inspect 命令通常用于检查数据库。但是,我们也可以使用它来检查我们的 composite_schema 并打印其 SQL 表示形式:
atlas schema inspect \
--env local \
--url env://src \
--format '{{ sql . }}'
上述命令打印以下 SQL。
-- 创建 "users" 表
CREATE TABLE "users" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "name" character varying NOT NULL, PRIMARY KEY ("id"));
-- 为表 "users" 创建索引 "unique_name"
CREATE UNIQUE INDEX "unique_name" ON "users" ((lower((name)::text)));
请注意,我们的函数索引是在 users 表的 name 字段上定义的。
为模式生成迁移
要生成模式的迁移,请运行以下命令:
atlas migrate diff \
--env local
请注意,会创建一个新的迁移文件,其内容如下:
-- 创建 "users" 表
CREATE TABLE "users" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "name" character varying NOT NULL, PRIMARY KEY ("id"));
-- 为表 "users" 创建索引 "unique_name"
CREATE UNIQUE INDEX "unique_name" ON "users" ((lower((name)::text)));
应用迁移
要将上面生成的迁移应用到数据库,请运行以下命令:
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?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?sslmode=disable",
AutoApprove: true,
}); err != nil {
log.Fatalf("failed to apply schema changes: %w", err)
}
代码示例
在设置了带有函数索引的 Ent 模式后,我们希望数据库能强制 users 表中 name 字段的唯一性:
// 测试唯一索引是否被强制执行。
client.User.Create().SetName("Ariel").SaveX(ctx)
err = client.User.Create().SetName("ariel").Exec(ctx)
require.EqualError(t, err, `ent: constraint failed: pq: duplicate key value violates unique constraint "unique_name"`)
// 类型断言返回的错误。
var pqerr *pq.Error
require.True(t, errors.As(err, &pqerr))
require.Equal(t, `duplicate key value violates unique constraint "unique_name"`, pqerr.Message)
require.Equal(t, user.Table, pqerr.Table)
require.Equal(t, "unique_name", pqerr.Constraint)
require.Equal(t, pq.ErrorCode("23505"), pqerr.Code, "unique violation")
本指南的代码可以在 GitHub 上找到。