snapshot of https://github.com/ent/ent.git (master, 2026-09-15; blobs > 131072B omitted)
This commit is contained in:
216
doc/md/traversals.md
Normal file
216
doc/md/traversals.md
Normal file
@@ -0,0 +1,216 @@
|
||||
---
|
||||
id: traversals
|
||||
title: Graph Traversal
|
||||
---
|
||||
|
||||
For the purpose of the example, we'll generate the following graph:
|
||||
|
||||
|
||||

|
||||
|
||||
The first step is to generate the 3 schemas: `Pet`, `User`, `Group`.
|
||||
|
||||
```console
|
||||
go run -mod=mod entgo.io/ent/cmd/ent new Pet User Group
|
||||
```
|
||||
|
||||
Add the necessary fields and edges for the schemas:
|
||||
|
||||
`ent/schema/pet.go`
|
||||
|
||||
```go
|
||||
// Pet holds the schema definition for the Pet entity.
|
||||
type Pet struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
// Fields of the Pet.
|
||||
func (Pet) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.String("name"),
|
||||
}
|
||||
}
|
||||
|
||||
// Edges of the 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`
|
||||
|
||||
```go
|
||||
// User holds the schema definition for the User entity.
|
||||
type User struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
// Fields of the User.
|
||||
func (User) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.Int("age"),
|
||||
field.String("name"),
|
||||
}
|
||||
}
|
||||
|
||||
// Edges of the 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`
|
||||
|
||||
```go
|
||||
// Group holds the schema definition for the Group entity.
|
||||
type Group struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
// Fields of the Group.
|
||||
func (Group) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.String("name"),
|
||||
}
|
||||
}
|
||||
|
||||
// Edges of the Group.
|
||||
func (Group) Edges() []ent.Edge {
|
||||
return []ent.Edge{
|
||||
edge.To("users", User.Type),
|
||||
edge.To("admin", User.Type).
|
||||
Unique(),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Let's write the code for populating the vertices and the edges to the graph:
|
||||
|
||||
```go
|
||||
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)
|
||||
}
|
||||
// Create the admin of the group.
|
||||
// Unlike `Save`, `SaveX` panics if an error occurs.
|
||||
dan := client.User.
|
||||
Create().
|
||||
SetAge(29).
|
||||
SetName("Dan").
|
||||
AddManage(hub).
|
||||
SaveX(ctx)
|
||||
|
||||
// Create "Ariel" and its pets.
|
||||
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)
|
||||
|
||||
// Create "Alex" and its pets.
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
Let's go over a few traversals, and show the code for them:
|
||||
|
||||

|
||||
|
||||
The traversal above starts from a `Group` entity, continues to its `admin` (edge),
|
||||
continues to its `friends` (edge), gets their `pets` (edge), gets each pet's `friends` (edge),
|
||||
and requests their owners.
|
||||
|
||||
```go
|
||||
func Traverse(ctx context.Context, client *ent.Client) error {
|
||||
owner, err := client.Group. // GroupClient.
|
||||
Query(). // Query builder.
|
||||
Where(group.Name("Github")). // Filter only Github group (only 1).
|
||||
QueryAdmin(). // Getting Dan.
|
||||
QueryFriends(). // Getting Dan's friends: [Ariel].
|
||||
QueryPets(). // Their pets: [Pedro, Xabi].
|
||||
QueryFriends(). // Pedro's friends: [Coco], Xabi's friends: [].
|
||||
QueryOwner(). // Coco's owner: Alex.
|
||||
Only(ctx) // Expect only one entity to return in the query.
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
What about the following traversal?
|
||||
|
||||

|
||||
|
||||
We want to get all pets (entities) that have an `owner` (`edge`) that is a `friend`
|
||||
(edge) of some group `admin` (edge).
|
||||
|
||||
```go
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
The full example exists in [GitHub](https://github.com/ent/ent/tree/master/examples/traversal).
|
||||
Reference in New Issue
Block a user