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

グラフ トラバーサル

この例では、以下のグラフを生成します。

er-traversal-graph

最初のステップは、3つのスキーマを生成することです。PetUserGroupです。

go run entgo.io/ent/cmd/ent init Pet User Group

スキーマに必要なフィールドやエッジを追加します。

ent/schema/pet.go

// Pet は、Pet エンティティのスキーマ定義を保持する
type Pet struct {
ent.Schema
}
// Fields of the Pet.
func (Pet) Fields() []ent.Field {
return []ent.Field{
field.String("name"),
}
}
// Petのエッジ
func (Pet) Edges() []ent.Edge {
return []ent.Edge{
edge.To("friends", Pet.Type),
edge.From("owner", User.Type).
Ref("pets").
Unique(),
}
}

ent/schema/user.go

// Userは、Userエンティティのスキーマ定義を保持する
type User struct {
ent.Schema
}
// Userのフィールド
func (User) Fields() []ent.Field {
return []ent.Field{
field.Int("age"),
field.String("name"),
}
}
// Userのエッジ
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("pets", Pet.Type),
edge.To("friends", User.Type),
edge.From("groups", Group.Type).
Ref("users"),
edge.From("manage", Group.Type).
Ref("admin"),
}
}

ent/schema/group.go

// Groupは、Groupエンティティのスキーマ定義を保持する
type Group struct {
ent.Schema
}
// Groupのフィールド
func (Group) Fields() []ent.Field {
return []ent.Field{
field.String("name"),
}
}
// Groupのエッジ
func (Group) Edges() []ent.Edge {
return []ent.Edge{
edge.To("users", User.Type),
edge.To("admin", User.Type).
Unique(),
}
}

ノードとエッジをグラフに追加するコードを書いてみましょう。

func Gen(ctx context.Context, client *ent.Client) error {
hub, err := client.Group.
Create().
SetName("Github").
Save(ctx)
if err != nil {
return fmt.Errorf("failed creating the group: %w", err)
}
// グループの管理者を作成する
// `Save` と異なり、`SaveX` はエラーが発生するとパニックになります。
dan := client.User.
Create().
SetAge(29).
SetName("Dan").
AddManage(hub).
SaveX(ctx)
// ユーザー"Ariel"とそのペット達を作成する
a8m := client.User.
Create().
SetAge(30).
SetName("Ariel").
AddGroups(hub).
AddFriends(dan).
SaveX(ctx)
pedro := client.Pet.
Create().
SetName("Pedro").
SetOwner(a8m).
SaveX(ctx)
xabi := client.Pet.
Create().
SetName("Xabi").
SetOwner(a8m).
SaveX(ctx)
// ユーザー"Alex"とそのペット達を作成する
alex := client.User.
Create().
SetAge(37).
SetName("Alex").
SaveX(ctx)
coco := client.Pet.
Create().
SetName("Coco").
SetOwner(alex).
AddFriends(pedro).
SaveX(ctx)
fmt.Println("Pets created:", pedro, xabi, coco)
// Output:
// Pets created: Pet(id=1, name=Pedro) Pet(id=2, name=Xabi) Pet(id=3, name=Coco)
return nil
}

いくつかのトラバーサルを確認し、それらのコードを見てみましょう。

er-traversal-graph-gopher

上記のトラバーサルは、Groupのエンティティから始まり、admin(エッジ) へと続き、friends(エッジ) へと続き、pets(エッジ) を取得し、各ペットのfriends(エッジ) を取得し、彼らの飼い主達を要求しています。

func Traverse(ctx context.Context, client *ent.Client) error {
owner, err := client.Group. // GroupClient.
Query(). // Query builder.
Where(group.Name("Github")). // Githubグループに絞り込む (1件のみ該当).
QueryAdmin(). // グループの管理者: Dan.
QueryFriends(). // Danの友人: [Ariel].
QueryPets(). // 彼らのペット達: [Pedro, Xabi].
QueryFriends(). // Pedroの友達: [Coco], Xabiの友達: [].
QueryOwner(). // Cocoの飼い主: Alex.
Only(ctx) // クエリでは1つのエンティティのみが返されることを期待する
if err != nil {
return fmt.Errorf("failed querying the owner: %w", err)
}
fmt.Println(owner)
// Output:
// User(id=3, age=37, name=Alex)
return nil
}

次のようなトラバーサルはどうでしょうか?

er-traversal-graph-gopher-query

あるグループの管理者(エッジ) の友人(エッジ) である飼い主(エッジ) を持つすべてのペット(エンティティ) を取得したいです。

func Traverse(ctx context.Context, client *ent.Client) error {
pets, err := client.Pet.
Query().
Where(
pet.HasOwnerWith(
user.HasFriendsWith(
user.HasManage(),
),
),
).
All(ctx)
if err != nil {
return fmt.Errorf("failed querying the pets: %w", err)
}
fmt.Println(pets)
// Output:
// [Pet(id=1, name=Pedro) Pet(id=2, name=Xabi)]
return nil
}

完全なサンプルは、GitHubにあります。