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

インデックス

複数のフィールド

インデックスは、データ取得の 速度を向上させたり、一意性を定義したりするために、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)

Storage Key

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

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