diff --git a/models/commit_status.go b/models/commit_status.go index 1ee3ead2170..5926930ec88 100644 --- a/models/commit_status.go +++ b/models/commit_status.go @@ -97,7 +97,7 @@ func GetCommitStatuses(repo *Repository, sha string, opts *CommitStatusOptions) } countSession := listCommitStatusesStatement(repo, sha, opts) - countSession = opts.setSessionPagination(countSession) + countSession = setSessionPagination(countSession, opts) maxResults, err := countSession.Count(new(CommitStatus)) if err != nil { log.Error("Count PRs: %v", err) @@ -106,7 +106,7 @@ func GetCommitStatuses(repo *Repository, sha string, opts *CommitStatusOptions) statuses := make([]*CommitStatus, 0, opts.PageSize) findSession := listCommitStatusesStatement(repo, sha, opts) - findSession = opts.setSessionPagination(findSession) + findSession = setSessionPagination(findSession, opts) sortCommitStatusesSession(findSession, opts.SortType) return statuses, maxResults, findSession.Find(&statuses) } @@ -149,7 +149,7 @@ func getLatestCommitStatus(e Engine, repoID int64, sha string, listOptions ListO Select("max( id ) as id"). GroupBy("context_hash").OrderBy("max( id ) desc") - sess = listOptions.setSessionPagination(sess) + sess = setSessionPagination(sess, &listOptions) err := sess.Find(&ids) if err != nil { diff --git a/models/gpg_key.go b/models/gpg_key.go index 1072813b1b7..cd286604a3f 100644 --- a/models/gpg_key.go +++ b/models/gpg_key.go @@ -64,7 +64,7 @@ func ListGPGKeys(uid int64, listOptions ListOptions) ([]*GPGKey, error) { func listGPGKeys(e Engine, uid int64, listOptions ListOptions) ([]*GPGKey, error) { sess := e.Table(&GPGKey{}).Where("owner_id=? AND primary_key_id=''", uid) if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = setSessionPagination(sess, &listOptions) } keys := make([]*GPGKey, 0, 2) diff --git a/models/issue_comment.go b/models/issue_comment.go index 10a7d0b114c..b079ea96032 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -1007,7 +1007,7 @@ func findComments(e Engine, opts *FindCommentsOptions) ([]*Comment, error) { } if opts.Page != 0 { - sess = opts.setSessionPagination(sess) + sess = setSessionPagination(sess, opts) } // WARNING: If you change this order you will need to fix createCodeComment diff --git a/models/issue_label.go b/models/issue_label.go index 936dd71e381..7d7d6702088 100644 --- a/models/issue_label.go +++ b/models/issue_label.go @@ -459,7 +459,7 @@ func getLabelsByRepoID(e Engine, repoID int64, sortType string, listOptions List } if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = setSessionPagination(sess, &listOptions) } return labels, sess.Find(&labels) @@ -576,7 +576,7 @@ func getLabelsByOrgID(e Engine, orgID int64, sortType string, listOptions ListOp } if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = setSessionPagination(sess, &listOptions) } return labels, sess.Find(&labels) diff --git a/models/issue_milestone.go b/models/issue_milestone.go index e6976a46c71..cfd11c5fa1b 100644 --- a/models/issue_milestone.go +++ b/models/issue_milestone.go @@ -408,7 +408,7 @@ func GetMilestones(opts GetMilestonesOption) (MilestoneList, int64, error) { sess := x.Where(opts.toCond()) if opts.Page != 0 { - sess = opts.setSessionPagination(sess) + sess = setSessionPagination(sess, &opts) } switch opts.SortType { diff --git a/models/issue_reaction.go b/models/issue_reaction.go index 80fae1ca5f9..5721d8cc1ff 100644 --- a/models/issue_reaction.go +++ b/models/issue_reaction.go @@ -87,7 +87,7 @@ func findReactions(e Engine, opts FindReactionsOptions) ([]*Reaction, error) { In("reaction.`type`", setting.UI.Reactions). Asc("reaction.issue_id", "reaction.comment_id", "reaction.created_unix", "reaction.id") if opts.Page != 0 { - e = opts.setEnginePagination(e) + e = setEnginePagination(e, &opts) reactions := make([]*Reaction, 0, opts.PageSize) return reactions, e.Find(&reactions) diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go index 9322e26bf23..b3022cfc27e 100644 --- a/models/issue_stopwatch.go +++ b/models/issue_stopwatch.go @@ -45,7 +45,7 @@ func GetUserStopwatches(userID int64, listOptions ListOptions) ([]*Stopwatch, er sws := make([]*Stopwatch, 0, 8) sess := x.Where("stopwatch.user_id = ?", userID) if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = setSessionPagination(sess, &listOptions) } err := sess.Find(&sws) diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go index e7769b41dd4..09db45bf90a 100644 --- a/models/issue_tracked_time.go +++ b/models/issue_tracked_time.go @@ -113,7 +113,7 @@ func (opts *FindTrackedTimesOptions) toSession(e Engine) Engine { sess = sess.Where(opts.toCond()) if opts.Page != 0 { - sess = opts.setEnginePagination(sess) + sess = setEnginePagination(sess, opts) } return sess diff --git a/models/issue_watch.go b/models/issue_watch.go index a3cbbf2c1d9..56811e5085d 100644 --- a/models/issue_watch.go +++ b/models/issue_watch.go @@ -111,7 +111,7 @@ func getIssueWatchers(e Engine, issueID int64, listOptions ListOptions) (IssueWa Join("INNER", "`user`", "`user`.id = `issue_watch`.user_id") if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = setSessionPagination(sess, &listOptions) watches := make([]*IssueWatch, 0, listOptions.PageSize) return watches, sess.Find(&watches) } diff --git a/models/list_options.go b/models/list_options.go index ff02933f9ba..8b6196793cb 100644 --- a/models/list_options.go +++ b/models/list_options.go @@ -10,38 +10,49 @@ import ( "xorm.io/xorm" ) -// ListOptions options to paginate results -type ListOptions struct { - PageSize int - Page int // start from 1 +// Paginator is the base for different ListOptions types +type Paginator interface { + GetSkipTake() (skip, take int) + GetStartEnd() (start, end int) } -func (opts *ListOptions) getPaginatedSession() *xorm.Session { - opts.setDefaultValues() +// getPaginatedSession creates a paginated database session +func getPaginatedSession(p Paginator) *xorm.Session { + skip, take := p.GetSkipTake() - return x.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) + return x.Limit(take, skip) } -func (opts *ListOptions) setSessionPagination(sess *xorm.Session) *xorm.Session { - opts.setDefaultValues() +// setSessionPagination sets pagination for a database session +func setSessionPagination(sess *xorm.Session, p Paginator) *xorm.Session { + skip, take := p.GetSkipTake() - if opts.PageSize <= 0 { - return sess - } - return sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) + return sess.Limit(take, skip) } -func (opts *ListOptions) setEnginePagination(e Engine) Engine { - opts.setDefaultValues() +// setSessionPagination sets pagination for a database engine +func setEnginePagination(e Engine, p Paginator) Engine { + skip, take := p.GetSkipTake() + + return e.Limit(take, skip) +} + +// ListOptions options to paginate results +type ListOptions struct { + PageSize int + Page int // start from 1 +} - return e.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) +// GetSkipTake returns the skip and take values +func (opts *ListOptions) GetSkipTake() (skip, take int) { + opts.setDefaultValues() + return (opts.Page - 1) * opts.PageSize, opts.PageSize } // GetStartEnd returns the start and end of the ListOptions func (opts *ListOptions) GetStartEnd() (start, end int) { - opts.setDefaultValues() - start = (opts.Page - 1) * opts.PageSize - end = start + opts.PageSize + start, take := opts.GetSkipTake() + end = start + take return } @@ -56,3 +67,33 @@ func (opts *ListOptions) setDefaultValues() { opts.Page = 1 } } + +// AbsoluteListOptions absolute options to paginate results +type AbsoluteListOptions struct { + skip int + take int +} + +// NewAbsoluteListOptions creates a list option with applied limits +func NewAbsoluteListOptions(skip, take int) *AbsoluteListOptions { + if skip < 0 { + skip = 0 + } + if take <= 0 { + take = setting.API.DefaultPagingNum + } + if take > setting.API.MaxResponseItems { + take = setting.API.MaxResponseItems + } + return &AbsoluteListOptions{skip, take} +} + +// GetSkipTake returns the skip and take values +func (opts *AbsoluteListOptions) GetSkipTake() (skip, take int) { + return opts.skip, opts.take +} + +// GetStartEnd returns the start and end values +func (opts *AbsoluteListOptions) GetStartEnd() (start, end int) { + return opts.skip, opts.skip + opts.take +} diff --git a/models/list_options_test.go b/models/list_options_test.go new file mode 100644 index 00000000000..3145aa7c162 --- /dev/null +++ b/models/list_options_test.go @@ -0,0 +1,62 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package models + +import ( + "testing" + + "code.gitea.io/gitea/modules/setting" + + "github.com/stretchr/testify/assert" +) + +func TestPaginator(t *testing.T) { + cases := []struct { + Paginator + Skip int + Take int + Start int + End int + }{ + { + Paginator: &ListOptions{Page: -1, PageSize: -1}, + Skip: 0, + Take: setting.API.DefaultPagingNum, + Start: 0, + End: setting.API.DefaultPagingNum, + }, + { + Paginator: &ListOptions{Page: 2, PageSize: 10}, + Skip: 10, + Take: 10, + Start: 10, + End: 20, + }, + { + Paginator: NewAbsoluteListOptions(-1, -1), + Skip: 0, + Take: setting.API.DefaultPagingNum, + Start: 0, + End: setting.API.DefaultPagingNum, + }, + { + Paginator: NewAbsoluteListOptions(2, 10), + Skip: 2, + Take: 10, + Start: 2, + End: 12, + }, + } + + for _, c := range cases { + skip, take := c.Paginator.GetSkipTake() + start, end := c.Paginator.GetStartEnd() + + assert.Equal(t, c.Skip, skip) + assert.Equal(t, c.Take, take) + assert.Equal(t, c.Start, start) + assert.Equal(t, c.End, end) + } +} diff --git a/models/notification.go b/models/notification.go index 30bb7596a8a..5a35c4d732a 100644 --- a/models/notification.go +++ b/models/notification.go @@ -110,7 +110,7 @@ func (opts *FindNotificationOptions) ToCond() builder.Cond { func (opts *FindNotificationOptions) ToSession(e Engine) *xorm.Session { sess := e.Where(opts.ToCond()) if opts.Page != 0 { - sess = opts.setSessionPagination(sess) + sess = setSessionPagination(sess, opts) } return sess } diff --git a/models/oauth2_application.go b/models/oauth2_application.go index 9cf236f0cb4..93766079d7d 100644 --- a/models/oauth2_application.go +++ b/models/oauth2_application.go @@ -275,7 +275,7 @@ func ListOAuth2Applications(uid int64, listOptions ListOptions) ([]*OAuth2Applic Desc("id") if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = setSessionPagination(sess, &listOptions) apps := make([]*OAuth2Application, 0, listOptions.PageSize) total, err := sess.FindAndCount(&apps) diff --git a/models/org.go b/models/org.go index 9540c4240e1..a3229470a48 100644 --- a/models/org.go +++ b/models/org.go @@ -569,7 +569,7 @@ func GetOrgUsersByUserID(uid int64, opts *SearchOrganizationsOptions) ([]*OrgUse } if opts.PageSize != 0 { - sess = opts.setSessionPagination(sess) + sess = setSessionPagination(sess, opts) } err := sess. @@ -589,7 +589,7 @@ func getOrgUsersByOrgID(e Engine, opts *FindOrgMembersOpts) ([]*OrgUser, error) sess.And("is_public = ?", true) } if opts.ListOptions.PageSize > 0 { - sess = opts.setSessionPagination(sess) + sess = setSessionPagination(sess, opts) ous := make([]*OrgUser, 0, opts.PageSize) return ous, sess.Find(&ous) diff --git a/models/org_team.go b/models/org_team.go index c380c8cd8ef..7516016a1db 100644 --- a/models/org_team.go +++ b/models/org_team.go @@ -168,7 +168,7 @@ func (t *Team) GetRepositories(opts *SearchTeamOptions) error { return t.getRepositories(x) } - return t.getRepositories(opts.getPaginatedSession()) + return t.getRepositories(getPaginatedSession(opts)) } func (t *Team) getMembers(e Engine) (err error) { @@ -182,7 +182,7 @@ func (t *Team) GetMembers(opts *SearchMembersOptions) (err error) { return t.getMembers(x) } - return t.getMembers(opts.getPaginatedSession()) + return t.getMembers(getPaginatedSession(opts)) } // AddMember adds new membership of the team to the organization, diff --git a/models/pull_list.go b/models/pull_list.go index 2f685e19f5e..8fa80307de5 100644 --- a/models/pull_list.go +++ b/models/pull_list.go @@ -100,7 +100,7 @@ func PullRequests(baseRepoID int64, opts *PullRequestsOptions) ([]*PullRequest, log.Error("listPullRequestStatement: %v", err) return nil, maxResults, err } - findSession = opts.setSessionPagination(findSession) + findSession = setSessionPagination(findSession, opts) prs := make([]*PullRequest, 0, opts.PageSize) return prs, maxResults, findSession.Find(&prs) } diff --git a/models/release.go b/models/release.go index 1ce88a8210c..7def2f1cb49 100644 --- a/models/release.go +++ b/models/release.go @@ -208,7 +208,7 @@ func GetReleasesByRepoID(repoID int64, opts FindReleasesOptions) ([]*Release, er Where(opts.toConds(repoID)) if opts.PageSize != 0 { - sess = opts.setSessionPagination(sess) + sess = setSessionPagination(sess, &opts.ListOptions) } rels := make([]*Release, 0, opts.PageSize) diff --git a/models/repo.go b/models/repo.go index 709fe6ab0e8..575b8acbc87 100644 --- a/models/repo.go +++ b/models/repo.go @@ -1770,7 +1770,7 @@ func GetUserRepositories(opts *SearchRepoOptions) ([]*Repository, int64, error) sess.Where(cond).OrderBy(opts.OrderBy.String()) repos := make([]*Repository, 0, opts.PageSize) - return repos, count, opts.setSessionPagination(sess).Find(&repos) + return repos, count, setSessionPagination(sess, opts).Find(&repos) } // GetUserMirrorRepositories returns a list of mirror repositories of given user. @@ -2061,7 +2061,7 @@ func (repo *Repository) GetForks(listOptions ListOptions) ([]*Repository, error) return forks, x.Find(&forks, &Repository{ForkID: repo.ID}) } - sess := listOptions.getPaginatedSession() + sess := getPaginatedSession(&listOptions) forks := make([]*Repository, 0, listOptions.PageSize) return forks, sess.Find(&forks, &Repository{ForkID: repo.ID}) } diff --git a/models/repo_collaboration.go b/models/repo_collaboration.go index a8b715bbcfe..84e49b2c656 100644 --- a/models/repo_collaboration.go +++ b/models/repo_collaboration.go @@ -65,7 +65,7 @@ func (repo *Repository) getCollaborations(e Engine, listOptions ListOptions) ([] return collaborations, e.Find(&collaborations, &Collaboration{RepoID: repo.ID}) } - e = listOptions.setEnginePagination(e) + e = setEnginePagination(e, &listOptions) collaborations := make([]*Collaboration, 0, listOptions.PageSize) return collaborations, e.Find(&collaborations, &Collaboration{RepoID: repo.ID}) diff --git a/models/repo_watch.go b/models/repo_watch.go index 656696b34f2..043449f0ec5 100644 --- a/models/repo_watch.go +++ b/models/repo_watch.go @@ -165,7 +165,7 @@ func (repo *Repository) GetWatchers(opts ListOptions) ([]*User, error) { Join("LEFT", "watch", "`user`.id=`watch`.user_id"). And("`watch`.mode<>?", RepoWatchModeDont) if opts.Page > 0 { - sess = opts.setSessionPagination(sess) + sess = setSessionPagination(sess, &opts) users := make([]*User, 0, opts.PageSize) return users, sess.Find(&users) diff --git a/models/review.go b/models/review.go index 1ffff8feb64..ff5c21e52ea 100644 --- a/models/review.go +++ b/models/review.go @@ -195,7 +195,7 @@ func findReviews(e Engine, opts FindReviewOptions) ([]*Review, error) { reviews := make([]*Review, 0, 10) sess := e.Where(opts.toCond()) if opts.Page > 0 { - sess = opts.ListOptions.setSessionPagination(sess) + sess = setSessionPagination(sess, &opts) } return reviews, sess. Asc("created_unix"). diff --git a/models/ssh_key.go b/models/ssh_key.go index a2f4c610e40..a4745d8e394 100644 --- a/models/ssh_key.go +++ b/models/ssh_key.go @@ -195,7 +195,7 @@ func SearchPublicKey(uid int64, fingerprint string) ([]*PublicKey, error) { func ListPublicKeys(uid int64, listOptions ListOptions) ([]*PublicKey, error) { sess := x.Where("owner_id = ? AND type != ?", uid, KeyTypePrincipal) if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = setSessionPagination(sess, &listOptions) keys := make([]*PublicKey, 0, listOptions.PageSize) return keys, sess.Find(&keys) diff --git a/models/ssh_key_deploy.go b/models/ssh_key_deploy.go index e7d486b9f56..1631da7179d 100644 --- a/models/ssh_key_deploy.go +++ b/models/ssh_key_deploy.go @@ -295,7 +295,7 @@ func listDeployKeys(e Engine, opts *ListDeployKeysOptions) ([]*DeployKey, error) sess := e.Where(opts.toCond()) if opts.Page != 0 { - sess = opts.setSessionPagination(sess) + sess = setSessionPagination(sess, opts) keys := make([]*DeployKey, 0, opts.PageSize) return keys, sess.Find(&keys) diff --git a/models/ssh_key_principals.go b/models/ssh_key_principals.go index 3459e43c8b0..48108557741 100644 --- a/models/ssh_key_principals.go +++ b/models/ssh_key_principals.go @@ -114,7 +114,7 @@ func CheckPrincipalKeyString(user *User, content string) (_ string, err error) { func ListPrincipalKeys(uid int64, listOptions ListOptions) ([]*PublicKey, error) { sess := x.Where("owner_id = ? AND type = ?", uid, KeyTypePrincipal) if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = setSessionPagination(sess, &listOptions) keys := make([]*PublicKey, 0, listOptions.PageSize) return keys, sess.Find(&keys) diff --git a/models/star.go b/models/star.go index 2d9496caf50..f71d39ce2c5 100644 --- a/models/star.go +++ b/models/star.go @@ -73,7 +73,7 @@ func (repo *Repository) GetStargazers(opts ListOptions) ([]*User, error) { sess := x.Where("star.repo_id = ?", repo.ID). Join("LEFT", "star", "`user`.id = star.uid") if opts.Page > 0 { - sess = opts.setSessionPagination(sess) + sess = setSessionPagination(sess, &opts) users := make([]*User, 0, opts.PageSize) return users, sess.Find(&users) diff --git a/models/token.go b/models/token.go index 9baa763f1c2..9862fabb61a 100644 --- a/models/token.go +++ b/models/token.go @@ -145,7 +145,7 @@ func ListAccessTokens(opts ListAccessTokensOptions) ([]*AccessToken, error) { sess = sess.Desc("id") if opts.Page != 0 { - sess = opts.setSessionPagination(sess) + sess = setSessionPagination(sess, &opts) tokens := make([]*AccessToken, 0, opts.PageSize) return tokens, sess.Find(&tokens) diff --git a/models/topic.go b/models/topic.go index 7fc34f5bef1..52f9a274a7c 100644 --- a/models/topic.go +++ b/models/topic.go @@ -190,7 +190,7 @@ func FindTopics(opts *FindTopicOptions) ([]*Topic, int64, error) { sess.Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id") } if opts.PageSize != 0 && opts.Page != 0 { - sess = opts.setSessionPagination(sess) + sess = setSessionPagination(sess, opts) } topics := make([]*Topic, 0, 10) total, err := sess.Desc("topic.repo_count").FindAndCount(&topics) diff --git a/models/user.go b/models/user.go index 92452e0767f..0e27dae7302 100644 --- a/models/user.go +++ b/models/user.go @@ -332,7 +332,7 @@ func (u *User) GetFollowers(listOptions ListOptions) ([]*User, error) { Join("LEFT", "follow", "`user`.id=follow.user_id") if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = setSessionPagination(sess, &listOptions) users := make([]*User, 0, listOptions.PageSize) return users, sess.Find(&users) @@ -354,7 +354,7 @@ func (u *User) GetFollowing(listOptions ListOptions) ([]*User, error) { Join("LEFT", "follow", "`user`.id=follow.follow_id") if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = setSessionPagination(sess, &listOptions) users := make([]*User, 0, listOptions.PageSize) return users, sess.Find(&users) @@ -1670,7 +1670,7 @@ func SearchUsers(opts *SearchUserOptions) (users []*User, _ int64, _ error) { sess := x.Where(cond).OrderBy(opts.OrderBy.String()) if opts.Page != 0 { - sess = opts.setSessionPagination(sess) + sess = setSessionPagination(sess, opts) } users = make([]*User, 0, opts.PageSize) @@ -1686,7 +1686,7 @@ func GetStarredRepos(userID int64, private bool, listOptions ListOptions) ([]*Re } if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = setSessionPagination(sess, &listOptions) repos := make([]*Repository, 0, listOptions.PageSize) return repos, sess.Find(&repos) @@ -1706,7 +1706,7 @@ func GetWatchedRepos(userID int64, private bool, listOptions ListOptions) ([]*Re } if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + sess = setSessionPagination(sess, &listOptions) repos := make([]*Repository, 0, listOptions.PageSize) total, err := sess.FindAndCount(&repos) diff --git a/models/webhook.go b/models/webhook.go index 79ce70a0de0..e19d0203c7d 100644 --- a/models/webhook.go +++ b/models/webhook.go @@ -415,7 +415,7 @@ func listWebhooksByOpts(e Engine, opts *ListWebhookOptions) ([]*Webhook, error) sess := e.Where(opts.toCond()) if opts.Page != 0 { - sess = opts.setSessionPagination(sess) + sess = setSessionPagination(sess, opts) webhooks := make([]*Webhook, 0, opts.PageSize) err := sess.Find(&webhooks) return webhooks, err