id: traversals

title: Graph Traversal

为了举例,我们会生成一个下面这样的图:

er-traversal-graph

第一步,生成 3 个模式: Pet, User, Group.

  1. entc init Pet User Group

然后,为模式添加一些必要的字段和边:

ent/schema/pet.go

  1. // Pet holds the schema definition for the Pet entity.
  2. type Pet struct {
  3. ent.Schema
  4. }
  5. // Fields of the Pet.
  6. func (Pet) Fields() []ent.Field {
  7. return []ent.Field{
  8. field.String("name"),
  9. }
  10. }
  11. // Edges of the Pet.
  12. func (Pet) Edges() []ent.Edge {
  13. return []ent.Edge{
  14. edge.To("friends", Pet.Type),
  15. edge.From("owner", User.Type).
  16. Ref("pets").
  17. Unique(),
  18. }
  19. }

ent/schema/user.go

  1. // User holds the schema definition for the User entity.
  2. type User struct {
  3. ent.Schema
  4. }
  5. // Fields of the User.
  6. func (User) Fields() []ent.Field {
  7. return []ent.Field{
  8. field.Int("age"),
  9. field.String("name"),
  10. }
  11. }
  12. // Edges of the User.
  13. func (User) Edges() []ent.Edge {
  14. return []ent.Edge{
  15. edge.To("pets", Pet.Type),
  16. edge.To("friends", User.Type),
  17. edge.From("groups", Group.Type).
  18. Ref("users"),
  19. }
  20. }

ent/schema/group.go

  1. // Group holds the schema definition for the Group entity.
  2. type Group struct {
  3. ent.Schema
  4. }
  5. // Fields of the Group.
  6. func (Group) Fields() []ent.Field {
  7. return []ent.Field{
  8. field.String("name"),
  9. }
  10. }
  11. // Edges of the Group.
  12. func (Group) Edges() []ent.Edge {
  13. return []ent.Edge{
  14. edge.To("users", User.Type),
  15. edge.To("admin", User.Type).
  16. Unique(),
  17. }
  18. }

让我们开始编写用于填充图的顶点和边的代码:

  1. func Gen(ctx context.Context, client *ent.Client) error {
  2. hub, err := client.Group.
  3. Create().
  4. SetName("Github").
  5. Save(ctx)
  6. if err != nil {
  7. return fmt.Errorf("failed creating the group: %v", err)
  8. }
  9. // 为群组添加一个 admin.
  10. // 不同于 `Save`, `SaveX` 遇到错误时会引起 panics.
  11. dan := client.User.
  12. Create().
  13. SetAge(29).
  14. SetName("Dan").
  15. AddManage(hub).
  16. SaveX(ctx)
  17. // 创建 "Ariel" 用户和他的宠物
  18. a8m := client.User.
  19. Create().
  20. SetAge(30).
  21. SetName("Ariel").
  22. AddGroups(hub).
  23. AddFriends(dan).
  24. SaveX(ctx)
  25. pedro := client.Pet.
  26. Create().
  27. SetName("Pedro").
  28. SetOwner(a8m).
  29. SaveX(ctx)
  30. xabi := client.Pet.
  31. Create().
  32. SetName("Xabi").
  33. SetOwner(a8m).
  34. SaveX(ctx)
  35. // 创建 "Alex" 用户和他的宠物。
  36. alex := client.User.
  37. Create().
  38. SetAge(37).
  39. SetName("Alex").
  40. SaveX(ctx)
  41. coco := client.Pet.
  42. Create().
  43. SetName("Coco").
  44. SetOwner(alex).
  45. AddFriends(pedro).
  46. SaveX(ctx)
  47. fmt.Println("Pets created:", pedro, xabi, coco)
  48. // Output:
  49. // Pets created: Pet(id=1, name=Pedro) Pet(id=2, name=Xabi) Pet(id=3, name=Coco)
  50. return nil
  51. }

再看一下我们要遍历的图及其代码:

er-traversal-graph-gopher

上面的遍历开始于一个 Group 实体:通过 admin 边、 friends 边、pets 边找到他们的宠物,然后再获取每个宠物的朋友(宠物的 frieds 边)的主人。

  1. func Traverse(ctx context.Context, client *ent.Client) error {
  2. owner, err := client.Group. // GroupClient.
  3. Query(). // Query builder.
  4. Where(group.Name("Github")). // 要求群组名为 Github
  5. QueryAdmin(). // 找到 Dan.
  6. QueryFriends(). // 找到 Dan 的朋友列表: [Ariel].
  7. QueryPets(). // 他们的宠物列表: [Pedro, Xabi].
  8. QueryFriends(). // Pedro 的朋友: [Coco], Xabi 的朋友: [].
  9. QueryOwner(). // Coco 的主人: Alex.
  10. Only(ctx) // 本次遍历中期望只返回一个实体。
  11. if err != nil {
  12. return fmt.Errorf("failed querying the owner: %v", err)
  13. }
  14. fmt.Println(owner)
  15. // Output:
  16. // User(id=3, age=37, name=Alex)
  17. return nil
  18. }

下面这个图又怎么遍历呢?

er-traversal-graph-gopher-query

我们想获取某个群组的 adminfriend 的全部宠物。

  1. func Traverse(ctx context.Context, client *ent.Client) error {
  2. pets, err := client.Pet.
  3. Query().
  4. Where(
  5. pet.HasOwnerWith(
  6. user.HasFriendsWith(
  7. user.HasManage(),
  8. ),
  9. ),
  10. ).
  11. All(ctx)
  12. if err != nil {
  13. return fmt.Errorf("failed querying the pets: %v", err)
  14. }
  15. fmt.Println(pets)
  16. // Output:
  17. // [Pet(id=1, name=Pedro) Pet(id=2, name=Xabi)]
  18. return nil
  19. }

完整的实例请参考 GitHub.