Skip to content

Commit

Permalink
feat: allow aggregate ordering of through table edges
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelcaulley committed Oct 20, 2023
1 parent 58da6fd commit b056f45
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 4 deletions.
1 change: 1 addition & 0 deletions entgql/internal/todo/ent.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,7 @@ input UserOrder {
"""Properties by which User connections can be ordered."""
enum UserOrderField {
GROUPS_COUNT
FRIENDS_COUNT
}
"""
UserWhereInput is used for filtering User objects.
Expand Down
1 change: 1 addition & 0 deletions entgql/internal/todo/ent.resolvers.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 25 additions & 2 deletions entgql/internal/todo/ent/gql_pagination.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion entgql/internal/todo/ent/schema/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ func (User) Edges() []ent.Edge {
),
edge.To("friends", User.Type).
Through("friendships", Friendship.Type).
Annotations(entgql.RelayConnection()),
Annotations(
entgql.RelayConnection(),
entgql.OrderField("FRIENDS_COUNT"),
),
}
}

Expand Down
78 changes: 78 additions & 0 deletions entgql/internal/todo/todo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1989,6 +1989,84 @@ func TestMutation_ClearChildren(t *testing.T) {
require.False(t, root.QueryChildren().ExistX(ctx))
}

func TestQuery_SortUserByFriendshipsCount(t *testing.T) {
ec := enttest.Open(t, dialect.SQLite,
fmt.Sprintf("file:%s?mode=memory&cache=shared&_fk=1", t.Name()),
enttest.WithMigrateOptions(migrate.WithGlobalUniqueID(true)),
)
srv := handler.NewDefaultServer(gen.NewSchema(ec))
srv.Use(entgql.Transactioner{TxOpener: ec})
gqlc := client.New(srv)

ctx := context.Background()
user := ec.User.Create().SetRequiredMetadata(map[string]any{}).SaveX(ctx)
friend := ec.User.Create().SetRequiredMetadata(map[string]any{}).AddFriends(user).SaveX(ctx)
secondFriend := ec.User.Create().SetRequiredMetadata(map[string]any{}).AddFriends(user, friend).SaveX(ctx)
thirdFried := ec.User.Create().SetRequiredMetadata(map[string]any{}).AddFriends(user).SaveX(ctx)

require.True(t, user.QueryFriends().ExistX(ctx))
require.True(t, friend.QueryFriends().ExistX(ctx))
require.True(t, secondFriend.QueryFriends().ExistX(ctx))
require.True(t, thirdFried.QueryFriends().ExistX(ctx))

var rsp struct {
Users struct {
Edges []struct {
Node struct {
ID string
Friends struct {
TotalCount int
}
}
}
}
}

query := `
query testThroughEdgeOrderByQuery($orderDirection: OrderDirection!) {
users (orderBy:{ field: FRIENDS_COUNT, direction: $orderDirection }) {
edges {
node {
id
friends {
totalCount
}
}
}
}
}
`

testCases := []struct {
direction string
expectedCountOrder []int
}{
{
direction: "DESC",
expectedCountOrder: []int{3, 2, 2, 1},
},
{
direction: "ASC",
expectedCountOrder: []int{1, 2, 2, 3},
},
}

for _, tc := range testCases {
t.Run(tc.direction, func(t *testing.T) {
err := gqlc.Post(
query,
&rsp,
client.Var("orderDirection", tc.direction))

require.NoError(t, err)
require.Len(t, rsp.Users.Edges, 4)
for i, edge := range rsp.Users.Edges {
require.Equal(t, edge.Node.Friends.TotalCount, tc.expectedCountOrder[i])
}
})
}
}

func TestMutation_ClearFriend(t *testing.T) {
ec := enttest.Open(t, dialect.SQLite,
fmt.Sprintf("file:%s?mode=memory&cache=shared&_fk=1", t.Name()),
Expand Down
1 change: 1 addition & 0 deletions entgql/internal/todogotype/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions entgql/internal/todopulid/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions entgql/internal/todouuid/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion entgql/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,12 +443,16 @@ func orderFields(n *gen.Type) ([]*OrderTerm, error) {
})
}
}
edgeNamesWithThroughTables := make(map[string]interface{})
for _, e := range n.Edges {
name := strings.ToUpper(e.Name)
switch ant, err := annotation(e.Annotations); {
case err != nil:
return nil, err
case ant.Skip.Is(SkipOrderField), ant.OrderField == "":
case strings.HasSuffix(ant.OrderField, "_COUNT") &&
edgeNamesWithThroughTables[strings.TrimSuffix(ant.OrderField, "_COUNT")] != nil:
// skip the through table annotations, annotations are applied on the `edge.To` edge instead
case ant.OrderField == fmt.Sprintf("%s_COUNT", name):
// Validate that the edge has a count ordering.
if _, err := e.OrderCountName(); err != nil {
Expand All @@ -461,7 +465,7 @@ func orderFields(n *gen.Type) ([]*OrderTerm, error) {
Count: true,
})
case strings.HasPrefix(ant.OrderField, name+"_"):
// Validate that the edge has a edge field ordering.
// Validate that the edge has an edge field ordering.
if _, err := e.OrderFieldName(); err != nil {
return nil, fmt.Errorf("entgql: invalid order field %s defined on edge %s.%s: %w", ant.OrderField, n.Name, e.Name, err)
}
Expand All @@ -482,6 +486,9 @@ func orderFields(n *gen.Type) ([]*OrderTerm, error) {
default:
return nil, fmt.Errorf("entgql: invalid order field defined on edge %s.%s", n.Name, e.Name)
}
if e.Through != nil {
edgeNamesWithThroughTablesSet[name] = true
}
}
return terms, nil
}
Expand Down
1 change: 1 addition & 0 deletions entgql/testdata/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -264,4 +264,5 @@ input UserOrder {
"""Properties by which User connections can be ordered."""
enum UserOrderField {
GROUPS_COUNT
FRIENDS_COUNT
}
1 change: 1 addition & 0 deletions entgql/testdata/schema_relay.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,7 @@ input UserOrder {
"""Properties by which User connections can be ordered."""
enum UserOrderField {
GROUPS_COUNT
FRIENDS_COUNT
}
"""
UserWhereInput is used for filtering User objects.
Expand Down

0 comments on commit b056f45

Please sign in to comment.