メインコンテンツへスキップする

インデックス

複数のフィールド

インデックスは、データ取得の 速度を向上させたり、一意性を定義したりするために、1つ以上のフィールドに設定できます。

package schema

import (
"entgo.io/ent"
"entgo.io/ent/schema/index"
)

// User holds the schema definition for the User entity.
type User struct {
ent.Schema
}

func (User) Indexes() []ent.Index {
return []ent.Index{
// 非一意インデックス
index.Fields("field1", "field2"),
// 一意インデックス
index.Fields("first_name", "last_name").
Unique(),
}
}

単一のフィールドを一意に設定するには、フィールドビルダーの Unique メソッドを以下のように使用してください:

func (User) Fields() []ent.Field {
return []ent.Field{
field.String("phone").
Unique(),
}
}

エッジ上のインデックス

インデックスは、フィールドとエッジの組み合わせでも設定できます。 主なユースケース は、特定の関係にあるフィールドに一意性を設定することです。 ひとつ、例を見てみましょう:

er-city-streets

この例では、Cityには複数のStreetがあります。そして、それぞれのCityごとに、同じ名前のStreetが存在しないよう設定したいと思います。

ent/schema/city.go

// City holds the schema definition for the City entity.
type City struct {
ent.Schema
}

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

// Edges of the City.
func (City) Edges() []ent.Edge {
return []ent.Edge{
edge.To("streets", Street.Type),
}
}

ent/schema/street.go

// Street holds the schema definition for the Street entity.
type Street struct {
ent.Schema
}

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

// Edges of the Street.
func (Street) Edges() []ent.Edge {
return []ent.Edge{
edge.From("city", City.Type).
Ref("streets").
Unique(),
}
}

// Indexes of the Street.
func (Street) Indexes() []ent.Index {
return []ent.Index{
index.Fields("name").
Edges("city").
Unique(),
}
}

example.go

func Do(ctx context.Context, client *ent.Client) error {
// `Save`とは違い、`SaveX`はエラーが起きた場合panicになります
tlv := client.City.
Create().
SetName("TLV").
SaveX(ctx)
nyc := client.City.
Create().
SetName("NYC").
SaveX(ctx)
// "ST"という道路を"TLV"に追加します
client.Street.
Create().
SetName("ST").
SetCity(tlv).
SaveX(ctx)
// "TLV"に"ST"はすでに作られているので
// この操作は失敗します。
if err := client.Street.
Create().
SetName("ST").
SetCity(tlv).
Exec(ctx); err == nil {
return fmt.Errorf("expecting creation to fail")
}
// "ST"という道路を"NYC"に追加します
client.Street.
Create().
SetName("ST").
SetCity(nyc).
SaveX(ctx)
return nil
}

完全な例はGitHubにあります

エッジフィールド上のインデックス

現在、Edgesカラムは常にFieldsカラムの後に追加されます。 しかし、インデックスによっては、特定の最適化を実現するために、これらのカラムを先に追加する必要があります。 You can work around this problem by making use of Edge Fields.

// Card holds the schema definition for the Card entity.
type Card struct {
ent.Schema
}
// Fields of the Card.
func (Card) Fields() []ent.Field {
return []ent.Field{
field.String("number").
Optional(),
field.Int("owner_id").
Optional(),
}
}
// Edges of the Card.
func (Card) Edges() []ent.Edge {
return []ent.Edge{
edge.From("owner", User.Type).
Ref("card").
Field("owner_id").
Unique(),
}
}
// Indexes of the Card.
func (Card) Indexes() []ent.Index {
return []ent.Index{
index.Fields("owner_id", "number"),
}
}

方言サポート

Dialect specific features are allowed using annotations. For example, in order to use index prefixes in MySQL, use the following configuration:

// Indexes of the User.
func (User) Indexes() []ent.Index {
return []ent.Index{
index.Fields("description").
Annotations(entsql.Prefix(128)),
index.Fields("c1", "c2", "c3").
Annotations(
entsql.PrefixColumn("c1", 100),
entsql.PrefixColumn("c2", 200),
)
}
}

上記のコードは、次の SQL 文を生成します:

CREATE INDEX `users_description` ON `users`(`description`(128))

CREATE INDEX `users_c1_c2_c3` ON `users`(`c1`(100), `c2`(200), `c3`)

Atlas Support

Starting with v0.10, Ent running migration with Atlas. This option provides more control on indexes such as, configuring their types or define indexes in a reverse order.

func (User) Indexes() []ent.Index {
return []ent.Index{
index.Fields("c1").
Annotations(entsql.Desc()),
index.Fields("c1", "c2", "c3").
Annotations(entsql.DescColumns("c1", "c2")),
index.Fields("c4").
Annotations(entsql.IndexType("HASH")),
// Enable FULLTEXT search on MySQL,
// and GIN on PostgreSQL.
index.Fields("c5").
Annotations(
entsql.IndexTypes(map[string]string{
dialect.MySQL: "FULLTEXT",
dialect.Postgres: "GIN",
}),
),
// For PostgreSQL, we can include in the index
// non-key columns.
index.Fields("workplace").
Annotations(
entsql.IncludeColumns("address"),
),
// Define a partial index on SQLite and PostgreSQL.
index.Fields("nickname").
Annotations(
entsql.IndexWhere("active"),
),
// Define a custom operator class.
index.Fields("phone").
Annotations(
entsql.OpClass("bpchar_pattern_ops"),
),
}
}

The code above generates the following SQL statements:

CREATE INDEX `users_c1` ON `users` (`c1` DESC)

CREATE INDEX `users_c1_c2_c3` ON `users` (`c1` DESC, `c2` DESC, `c3`)

CREATE INDEX `users_c4` ON `users` USING HASH (`c4`)

-- MySQL only.
CREATE FULLTEXT INDEX `users_c5` ON `users` (`c5`)

-- PostgreSQL only.
CREATE INDEX "users_c5" ON "users" USING GIN ("c5")

-- Include index-only scan on PostgreSQL.
CREATE INDEX "users_workplace" ON "users" ("workplace") INCLUDE ("address")

-- Define partial index on SQLite and PostgreSQL.
CREATE INDEX "users_nickname" ON "users" ("nickname") WHERE "active"

-- PostgreSQL only.
CREATE INDEX "users_phone" ON "users" ("phone" bpchar_pattern_ops)

Functional Indexes

The Ent schema supports defining indexes on fields and edges (foreign-keys), but there is no API for defining index parts as expressions, such as function calls. If you are using Atlas for managing schema migrations, you can define functional indexes as described in this guide.

Storage Key

Like Fields, custom index name can be configured using the StorageKey method. It's mapped to an index name in SQL dialects.

func (User) Indexes() []ent.Index {
return []ent.Index{
index.Fields("field1", "field2").
StorageKey("custom_index"),
}
}