feat: support query grouped endpoints (e.g. `#` of users by `type` field)
Opened this issue · 2 comments
Add endpoints for grouping (with filtering). Could see much more than the below example, but would be a good start.
Example request, which is something very helpful for a UI which has a filter/selection dropdown for only showing results based of users with a specific type, and showing the number of that type:
/users/countBy<Field>?<filter-options>
Example response:
{
"results": [
{
"foo": 123,
"bar": 100,
"baz": 51
}
]
}
Would be used for something like:
some more example code:
r.Get("/count-types", func(w http.ResponseWriter, r *http.Request) {
type counter struct {
Typ string `json:"type"`
Count int `json:"count"`
}
var v []counter
// Can still .Order(ent.Desc(pet.FieldType)) if we want to, but not by count.
err := db.Debug().Pet.Query().GroupBy(pet.FieldType).Aggregate(ent.As(ent.Count(), "count")).Scan(ctx, &v)
if err != nil {
srv.DefaultErrorHandler(w, r, rest.OperationRead, err)
return
}
slices.SortFunc(v, func(a, b counter) int {
if a.Count == b.Count {
return 0
}
if a.Count > b.Count {
return 1
}
return -1
})
slices.Reverse(v)
rest.JSON(w, r, http.StatusOK, v)
})
r.Get("/avg-age-by-type", func(w http.ResponseWriter, r *http.Request) {
var v []struct {
Typ string `json:"type"`
AgeSum float64 `json:"age_sum"`
Count int `json:"count"`
}
err := db.Pet.Query().
GroupBy(pet.FieldType).
Aggregate(ent.As(ent.Mean(pet.FieldAge), "age_sum"), ent.Count()).
Scan(ctx, &v)
if err != nil {
srv.DefaultErrorHandler(w, r, rest.OperationRead, err)
return
}
rest.JSON(w, r, http.StatusOK, v)
})
Some additional thoughts:
- Should we still support filtering for these endpoints?
- Will make the resulting spec much larger.
- Could reuse the
List<Type>Params
structs.
- Sorting? Count & the field, and that's it?
Another form for this type of endpoint could be edge-based counts. An example used on https://liam.sh is:
type LabelsCountResponse struct {
ent.Label
PostCount int `json:"post_count"`
GithubRepositoryCount int `json:"githubrepository_count"`
Total int `json:"total_count"`
}
func (h *handler) getLabelsCount(w http.ResponseWriter, r *http.Request) {
v := []*LabelsCountResponse{}
err := h.db.Label.Query().GroupBy(label.FieldID).
Aggregate(
func(s *sql.Selector) string {
s.AppendSelect(label.Columns...)
t1 := sql.Table(label.GithubRepositoriesTable)
s.LeftJoin(t1).On(s.C(label.FieldID), t1.C(label.GithubRepositoriesPrimaryKey[0]))
t1count := sql.Count(sql.Distinct(t1.C(label.GithubRepositoriesPrimaryKey[1])))
t2 := sql.Table(label.PostsTable)
s.LeftJoin(t2).On(s.C(label.FieldID), t2.C(label.PostsPrimaryKey[0]))
t2count := sql.Count(sql.Distinct(t2.C(label.PostsPrimaryKey[1])))
s.AppendSelectAs(t1count, "githubrepository_count")
s.AppendSelectAs(t2count, "post_count")
s.AppendSelectExprAs(sql.ColumnsOp(t1count, t2count, sql.OpAdd), "total_count")
s.OrderBy(sql.Desc("total_count"))
return ""
},
).Scan(context.Background(), &v)
if err != nil {
chix.Error(w, r, err)
return
}
chix.JSON(w, r, http.StatusOK, v)
}
Where labels <-> github_repository
and labels <-> post
, and we're calculating the number of edges, and accumulating the total in one query/result. This massages the results into a wrapped *ent.<type>
, and includes all fields. Not sure exactly how one would eager-load in this scenario, though.