Skip to content

Commit

Permalink
Use db.ListOptions directly instead of Paginator interface to make it…
Browse files Browse the repository at this point in the history
…easier to use and fix performance of /pulls and /issues (go-gitea#29990) (go-gitea#30447)

backport go-gitea#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>
  • Loading branch information
3 people committed Apr 13, 2024
1 parent fc4e08f commit 09df5c9
Show file tree
Hide file tree
Showing 8 changed files with 49 additions and 34 deletions.
22 changes: 5 additions & 17 deletions models/issues/issue_search.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (

// IssuesOptions represents options of an issue.
type IssuesOptions struct { //nolint
db.Paginator
Paginator *db.ListOptions
RepoIDs []int64 // overwrites RepoCond if the length is not 0
RepoCond builder.Cond
AssigneeID int64
Expand Down Expand Up @@ -103,23 +103,11 @@ func applyLimit(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
return sess
}

// Warning: Do not use GetSkipTake() for *db.ListOptions
// Its implementation could reset the page size with setting.API.MaxResponseItems
if listOptions, ok := opts.Paginator.(*db.ListOptions); ok {
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)
}
return sess
start := 0
if opts.Paginator.Page > 1 {
start = (opts.Paginator.Page - 1) * opts.Paginator.PageSize
}

start, limit := opts.Paginator.GetSkipTake()
sess.Limit(limit, start)
sess.Limit(opts.Paginator.PageSize, start)

return sess
}
Expand Down
6 changes: 5 additions & 1 deletion models/issues/issue_stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,17 @@ func CountIssuesByRepo(ctx context.Context, opts *IssuesOptions) (map[int64]int6
}

// 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).
Select("COUNT(issue.id) AS count").
Table("issue").
Join("INNER", "repository", "`issue`.repo_id = `repository`.id")
applyConditions(sess, opts)

for _, cond := range otherConds {
sess.And(cond)
}

return sess.Count()
}

Expand Down
21 changes: 7 additions & 14 deletions modules/indexer/internal/paginator.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

// 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
unlimited := math.MaxInt32
if len(max) > 0 {
Expand All @@ -19,22 +19,15 @@ func ParsePaginator(paginator db.Paginator, max ...int) (int, int) {
}

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
}

// Warning: Do not use GetSkipTake() for *db.ListOptions
// Its implementation could reset the page size with setting.API.MaxResponseItems
if listOptions, ok := paginator.(*db.ListOptions); ok {
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
if paginator.PageSize == 0 {
// Do not return any results when searching, it's used to get the total count only.
return 0, 0
}

return paginator.GetSkipTake()
Expand Down
11 changes: 11 additions & 0 deletions modules/indexer/issues/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,17 @@ func (i *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
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)
if err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion modules/indexer/issues/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,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.
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)
return total, err
Expand Down
2 changes: 1 addition & 1 deletion modules/indexer/issues/internal/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ type SearchOptions struct {
UpdatedAfterUnix *int64
UpdatedBeforeUnix *int64

db.Paginator
Paginator *db.ListOptions

SortBy SortBy // sort by field
}
Expand Down
7 changes: 7 additions & 0 deletions modules/indexer/issues/internal/tests/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ func TestIndexer(t *testing.T, indexer internal.Indexer) {
assert.Equal(t, c.ExpectedIDs, ids)
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)
})
}
}
Expand Down
12 changes: 12 additions & 0 deletions modules/indexer/issues/meilisearch/meilisearch.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,14 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (

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
}

// to make it non fuzzy ("typo tolerance" in meilisearch terms), we have to quote the keyword(s)
// https://www.meilisearch.com/docs/reference/api/search#phrase-search
keyword := doubleQuoteKeyword(options.Keyword)
Expand All @@ -226,6 +234,10 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
return nil, err
}

if counting {
searchRes.Hits = nil
}

hits := make([]internal.Match, 0, len(searchRes.Hits))
for _, hit := range searchRes.Hits {
hits = append(hits, internal.Match{
Expand Down

0 comments on commit 09df5c9

Please sign in to comment.