mirror of
https://github.com/go-gitea/gitea
synced 2024-11-13 21:41:16 +01:00
Use db.ListOptions directly instead of Paginator interface to make it easier to use and fix performance of /pulls and /issues (#29990)
This PR uses `db.ListOptions` instead of `Paginor` to make the code simpler. And it also fixed the performance problem when viewing /pulls or /issues. Before the counting in fact will also do the search. --------- Co-authored-by: Jason Song <i@wolfogre.com> Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
parent
ec3d467f15
commit
3f26fe2fa2
8 changed files with 49 additions and 34 deletions
|
@ -21,7 +21,7 @@ import (
|
||||||
|
|
||||||
// IssuesOptions represents options of an issue.
|
// IssuesOptions represents options of an issue.
|
||||||
type IssuesOptions struct { //nolint
|
type IssuesOptions struct { //nolint
|
||||||
db.Paginator
|
Paginator *db.ListOptions
|
||||||
RepoIDs []int64 // overwrites RepoCond if the length is not 0
|
RepoIDs []int64 // overwrites RepoCond if the length is not 0
|
||||||
AllPublic bool // include also all public repositories
|
AllPublic bool // include also all public repositories
|
||||||
RepoCond builder.Cond
|
RepoCond builder.Cond
|
||||||
|
@ -104,23 +104,11 @@ func applyLimit(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
||||||
return sess
|
return sess
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning: Do not use GetSkipTake() for *db.ListOptions
|
start := 0
|
||||||
// Its implementation could reset the page size with setting.API.MaxResponseItems
|
if opts.Paginator.Page > 1 {
|
||||||
if listOptions, ok := opts.Paginator.(*db.ListOptions); ok {
|
start = (opts.Paginator.Page - 1) * opts.Paginator.PageSize
|
||||||
if listOptions.Page >= 0 && listOptions.PageSize > 0 {
|
|
||||||
var start int
|
|
||||||
if listOptions.Page == 0 {
|
|
||||||
start = 0
|
|
||||||
} else {
|
|
||||||
start = (listOptions.Page - 1) * listOptions.PageSize
|
|
||||||
}
|
}
|
||||||
sess.Limit(listOptions.PageSize, start)
|
sess.Limit(opts.Paginator.PageSize, start)
|
||||||
}
|
|
||||||
return sess
|
|
||||||
}
|
|
||||||
|
|
||||||
start, limit := opts.Paginator.GetSkipTake()
|
|
||||||
sess.Limit(limit, start)
|
|
||||||
|
|
||||||
return sess
|
return sess
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,13 +68,17 @@ func CountIssuesByRepo(ctx context.Context, opts *IssuesOptions) (map[int64]int6
|
||||||
}
|
}
|
||||||
|
|
||||||
// CountIssues number return of issues by given conditions.
|
// CountIssues number return of issues by given conditions.
|
||||||
func CountIssues(ctx context.Context, opts *IssuesOptions) (int64, error) {
|
func CountIssues(ctx context.Context, opts *IssuesOptions, otherConds ...builder.Cond) (int64, error) {
|
||||||
sess := db.GetEngine(ctx).
|
sess := db.GetEngine(ctx).
|
||||||
Select("COUNT(issue.id) AS count").
|
Select("COUNT(issue.id) AS count").
|
||||||
Table("issue").
|
Table("issue").
|
||||||
Join("INNER", "repository", "`issue`.repo_id = `repository`.id")
|
Join("INNER", "repository", "`issue`.repo_id = `repository`.id")
|
||||||
applyConditions(sess, opts)
|
applyConditions(sess, opts)
|
||||||
|
|
||||||
|
for _, cond := range otherConds {
|
||||||
|
sess.And(cond)
|
||||||
|
}
|
||||||
|
|
||||||
return sess.Count()
|
return sess.Count()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParsePaginator parses a db.Paginator into a skip and limit
|
// ParsePaginator parses a db.Paginator into a skip and limit
|
||||||
func ParsePaginator(paginator db.Paginator, max ...int) (int, int) {
|
func ParsePaginator(paginator *db.ListOptions, max ...int) (int, int) {
|
||||||
// Use a very large number to indicate no limit
|
// Use a very large number to indicate no limit
|
||||||
unlimited := math.MaxInt32
|
unlimited := math.MaxInt32
|
||||||
if len(max) > 0 {
|
if len(max) > 0 {
|
||||||
|
@ -19,22 +19,15 @@ func ParsePaginator(paginator db.Paginator, max ...int) (int, int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if paginator == nil || paginator.IsListAll() {
|
if paginator == nil || paginator.IsListAll() {
|
||||||
|
// It shouldn't happen. In actual usage scenarios, there should not be requests to search all.
|
||||||
|
// But if it does happen, respect it and return "unlimited".
|
||||||
|
// And it's also useful for testing.
|
||||||
return 0, unlimited
|
return 0, unlimited
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning: Do not use GetSkipTake() for *db.ListOptions
|
if paginator.PageSize == 0 {
|
||||||
// Its implementation could reset the page size with setting.API.MaxResponseItems
|
// Do not return any results when searching, it's used to get the total count only.
|
||||||
if listOptions, ok := paginator.(*db.ListOptions); ok {
|
return 0, 0
|
||||||
if listOptions.Page >= 0 && listOptions.PageSize > 0 {
|
|
||||||
var start int
|
|
||||||
if listOptions.Page == 0 {
|
|
||||||
start = 0
|
|
||||||
} else {
|
|
||||||
start = (listOptions.Page - 1) * listOptions.PageSize
|
|
||||||
}
|
|
||||||
return start, listOptions.PageSize
|
|
||||||
}
|
|
||||||
return 0, unlimited
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return paginator.GetSkipTake()
|
return paginator.GetSkipTake()
|
||||||
|
|
|
@ -78,6 +78,17 @@ func (i *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If pagesize == 0, return total count only. It's a special case for search count.
|
||||||
|
if options.Paginator != nil && options.Paginator.PageSize == 0 {
|
||||||
|
total, err := issue_model.CountIssues(ctx, opt, cond)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &internal.SearchResult{
|
||||||
|
Total: total,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
ids, total, err := issue_model.IssueIDs(ctx, opt, cond)
|
ids, total, err := issue_model.IssueIDs(ctx, opt, cond)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -308,7 +308,7 @@ func SearchIssues(ctx context.Context, opts *SearchOptions) ([]int64, int64, err
|
||||||
|
|
||||||
// CountIssues counts issues by options. It is a shortcut of SearchIssues(ctx, opts) but only returns the total count.
|
// CountIssues counts issues by options. It is a shortcut of SearchIssues(ctx, opts) but only returns the total count.
|
||||||
func CountIssues(ctx context.Context, opts *SearchOptions) (int64, error) {
|
func CountIssues(ctx context.Context, opts *SearchOptions) (int64, error) {
|
||||||
opts = opts.Copy(func(options *SearchOptions) { opts.Paginator = &db_model.ListOptions{PageSize: 0} })
|
opts = opts.Copy(func(options *SearchOptions) { options.Paginator = &db_model.ListOptions{PageSize: 0} })
|
||||||
|
|
||||||
_, total, err := SearchIssues(ctx, opts)
|
_, total, err := SearchIssues(ctx, opts)
|
||||||
return total, err
|
return total, err
|
||||||
|
|
|
@ -106,7 +106,7 @@ type SearchOptions struct {
|
||||||
UpdatedAfterUnix optional.Option[int64]
|
UpdatedAfterUnix optional.Option[int64]
|
||||||
UpdatedBeforeUnix optional.Option[int64]
|
UpdatedBeforeUnix optional.Option[int64]
|
||||||
|
|
||||||
db.Paginator
|
Paginator *db.ListOptions
|
||||||
|
|
||||||
SortBy SortBy // sort by field
|
SortBy SortBy // sort by field
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,13 @@ func TestIndexer(t *testing.T, indexer internal.Indexer) {
|
||||||
assert.Equal(t, c.ExpectedIDs, ids)
|
assert.Equal(t, c.ExpectedIDs, ids)
|
||||||
assert.Equal(t, c.ExpectedTotal, result.Total)
|
assert.Equal(t, c.ExpectedTotal, result.Total)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// test counting
|
||||||
|
c.SearchOptions.Paginator = &db.ListOptions{PageSize: 0}
|
||||||
|
countResult, err := indexer.Search(context.Background(), c.SearchOptions)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, countResult.Hits)
|
||||||
|
assert.Equal(t, result.Total, countResult.Total)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -218,6 +218,14 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
|
||||||
|
|
||||||
skip, limit := indexer_internal.ParsePaginator(options.Paginator, maxTotalHits)
|
skip, limit := indexer_internal.ParsePaginator(options.Paginator, maxTotalHits)
|
||||||
|
|
||||||
|
counting := limit == 0
|
||||||
|
if counting {
|
||||||
|
// If set limit to 0, it will be 20 by default, and -1 is not allowed.
|
||||||
|
// See https://www.meilisearch.com/docs/reference/api/search#limit
|
||||||
|
// So set limit to 1 to make the cost as low as possible, then clear the result before returning.
|
||||||
|
limit = 1
|
||||||
|
}
|
||||||
|
|
||||||
keyword := options.Keyword
|
keyword := options.Keyword
|
||||||
if !options.IsFuzzyKeyword {
|
if !options.IsFuzzyKeyword {
|
||||||
// to make it non fuzzy ("typo tolerance" in meilisearch terms), we have to quote the keyword(s)
|
// to make it non fuzzy ("typo tolerance" in meilisearch terms), we have to quote the keyword(s)
|
||||||
|
@ -236,6 +244,10 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if counting {
|
||||||
|
searchRes.Hits = nil
|
||||||
|
}
|
||||||
|
|
||||||
hits, err := convertHits(searchRes)
|
hits, err := convertHits(searchRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
Loading…
Reference in a new issue