Skip to content

Commit c7cc4a2

Browse files
feat: allow aggregate ordering of through table edges
1 parent bdcdeac commit c7cc4a2

File tree

11 files changed

+122
-4
lines changed

11 files changed

+122
-4
lines changed

entgql/internal/todo/ent.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1387,6 +1387,7 @@ Properties by which User connections can be ordered.
13871387
"""
13881388
enum UserOrderField {
13891389
GROUPS_COUNT
1390+
FRIENDS_COUNT
13901391
}
13911392
"""
13921393
UserWhereInput is used for filtering User objects.

entgql/internal/todo/ent.resolvers.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

entgql/internal/todo/ent/gql_pagination.go

Lines changed: 25 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

entgql/internal/todo/ent/schema/user.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,10 @@ func (User) Edges() []ent.Edge {
5555
),
5656
edge.To("friends", User.Type).
5757
Through("friendships", Friendship.Type).
58-
Annotations(entgql.RelayConnection()),
58+
Annotations(
59+
entgql.RelayConnection(),
60+
entgql.OrderField("FRIENDS_COUNT"),
61+
),
5962
}
6063
}
6164

entgql/internal/todo/todo_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,6 +1989,84 @@ func TestMutation_ClearChildren(t *testing.T) {
19891989
require.False(t, root.QueryChildren().ExistX(ctx))
19901990
}
19911991

1992+
func TestQuery_SortUserByFriendshipsCount(t *testing.T) {
1993+
ec := enttest.Open(t, dialect.SQLite,
1994+
fmt.Sprintf("file:%s?mode=memory&cache=shared&_fk=1", t.Name()),
1995+
enttest.WithMigrateOptions(migrate.WithGlobalUniqueID(true)),
1996+
)
1997+
srv := handler.NewDefaultServer(gen.NewSchema(ec))
1998+
srv.Use(entgql.Transactioner{TxOpener: ec})
1999+
gqlc := client.New(srv)
2000+
2001+
ctx := context.Background()
2002+
user := ec.User.Create().SetRequiredMetadata(map[string]any{}).SaveX(ctx)
2003+
friend := ec.User.Create().SetRequiredMetadata(map[string]any{}).AddFriends(user).SaveX(ctx)
2004+
secondFriend := ec.User.Create().SetRequiredMetadata(map[string]any{}).AddFriends(user, friend).SaveX(ctx)
2005+
thirdFried := ec.User.Create().SetRequiredMetadata(map[string]any{}).AddFriends(user).SaveX(ctx)
2006+
2007+
require.True(t, user.QueryFriends().ExistX(ctx))
2008+
require.True(t, friend.QueryFriends().ExistX(ctx))
2009+
require.True(t, secondFriend.QueryFriends().ExistX(ctx))
2010+
require.True(t, thirdFried.QueryFriends().ExistX(ctx))
2011+
2012+
var rsp struct {
2013+
Users struct {
2014+
Edges []struct {
2015+
Node struct {
2016+
ID string
2017+
Friends struct {
2018+
TotalCount int
2019+
}
2020+
}
2021+
}
2022+
}
2023+
}
2024+
2025+
query := `
2026+
query testThroughEdgeOrderByQuery($orderDirection: OrderDirection!) {
2027+
users (orderBy:{ field: FRIENDS_COUNT, direction: $orderDirection }) {
2028+
edges {
2029+
node {
2030+
id
2031+
friends {
2032+
totalCount
2033+
}
2034+
}
2035+
}
2036+
}
2037+
}
2038+
`
2039+
2040+
testCases := []struct {
2041+
direction string
2042+
expectedCountOrder []int
2043+
}{
2044+
{
2045+
direction: "DESC",
2046+
expectedCountOrder: []int{3, 2, 2, 1},
2047+
},
2048+
{
2049+
direction: "ASC",
2050+
expectedCountOrder: []int{1, 2, 2, 3},
2051+
},
2052+
}
2053+
2054+
for _, tc := range testCases {
2055+
t.Run(tc.direction, func(t *testing.T) {
2056+
err := gqlc.Post(
2057+
query,
2058+
&rsp,
2059+
client.Var("orderDirection", tc.direction))
2060+
2061+
require.NoError(t, err)
2062+
require.Len(t, rsp.Users.Edges, 4)
2063+
for i, edge := range rsp.Users.Edges {
2064+
require.Equal(t, edge.Node.Friends.TotalCount, tc.expectedCountOrder[i])
2065+
}
2066+
})
2067+
}
2068+
}
2069+
19922070
func TestMutation_ClearFriend(t *testing.T) {
19932071
ec := enttest.Open(t, dialect.SQLite,
19942072
fmt.Sprintf("file:%s?mode=memory&cache=shared&_fk=1", t.Name()),

entgql/internal/todogotype/generated.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

entgql/internal/todopulid/generated.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

entgql/internal/todouuid/generated.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

entgql/template.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,12 +452,16 @@ func orderFields(n *gen.Type) ([]*OrderTerm, error) {
452452
})
453453
}
454454
}
455+
edgeNamesWithThroughTables := make(map[string]interface{})
455456
for _, e := range n.Edges {
456457
name := strings.ToUpper(e.Name)
457458
switch ant, err := annotation(e.Annotations); {
458459
case err != nil:
459460
return nil, err
460461
case ant.Skip.Is(SkipOrderField), ant.OrderField == "":
462+
case strings.HasSuffix(ant.OrderField, "_COUNT") &&
463+
edgeNamesWithThroughTables[strings.TrimSuffix(ant.OrderField, "_COUNT")] != nil:
464+
// skip the through table annotations, annotations are applied on the `edge.To` edge instead
461465
case ant.OrderField == fmt.Sprintf("%s_COUNT", name):
462466
// Validate that the edge has a count ordering.
463467
if _, err := e.OrderCountName(); err != nil {
@@ -471,7 +475,7 @@ func orderFields(n *gen.Type) ([]*OrderTerm, error) {
471475
Count: true,
472476
})
473477
case strings.HasPrefix(ant.OrderField, name+"_"):
474-
// Validate that the edge has a edge field ordering.
478+
// Validate that the edge has an edge field ordering.
475479
if _, err := e.OrderFieldName(); err != nil {
476480
return nil, fmt.Errorf("entgql: invalid order field %s defined on edge %s.%s: %w", ant.OrderField, n.Name, e.Name, err)
477481
}
@@ -493,6 +497,9 @@ func orderFields(n *gen.Type) ([]*OrderTerm, error) {
493497
default:
494498
return nil, fmt.Errorf("entgql: invalid order field defined on edge %s.%s", n.Name, e.Name)
495499
}
500+
if e.Through != nil {
501+
edgeNamesWithThroughTables[name] = true
502+
}
496503
}
497504
return terms, nil
498505
}

entgql/testdata/schema.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,4 +313,5 @@ Properties by which User connections can be ordered.
313313
"""
314314
enum UserOrderField {
315315
GROUPS_COUNT
316+
FRIENDS_COUNT
316317
}

entgql/testdata/schema_relay.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,6 +1335,7 @@ Properties by which User connections can be ordered.
13351335
"""
13361336
enum UserOrderField {
13371337
GROUPS_COUNT
1338+
FRIENDS_COUNT
13381339
}
13391340
"""
13401341
UserWhereInput is used for filtering User objects.

0 commit comments

Comments
 (0)