summaryrefslogtreecommitdiffstats
path: root/models/db/list.go
blob: 5c005a0350172dd9ad40a91a1ccd40fa46b78218 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package db

import (
	"context"

	"code.gitea.io/gitea/modules/setting"

	"xorm.io/builder"
	"xorm.io/xorm"
)

const (
	// DefaultMaxInSize represents default variables number on IN () in SQL
	DefaultMaxInSize     = 50
	defaultFindSliceSize = 10
)

// Paginator is the base for different ListOptions types
type Paginator interface {
	GetSkipTake() (skip, take int)
	IsListAll() bool
}

// SetSessionPagination sets pagination for a database session
func SetSessionPagination(sess Engine, p Paginator) *xorm.Session {
	skip, take := p.GetSkipTake()

	return sess.Limit(take, skip)
}

// ListOptions options to paginate results
type ListOptions struct {
	PageSize int
	Page     int  // start from 1
	ListAll  bool // if true, then PageSize and Page will not be taken
}

var ListOptionsAll = ListOptions{ListAll: true}

var (
	_ Paginator   = &ListOptions{}
	_ FindOptions = ListOptions{}
)

// GetSkipTake returns the skip and take values
func (opts *ListOptions) GetSkipTake() (skip, take int) {
	opts.SetDefaultValues()
	return (opts.Page - 1) * opts.PageSize, opts.PageSize
}

func (opts ListOptions) GetPage() int {
	return opts.Page
}

func (opts ListOptions) GetPageSize() int {
	return opts.PageSize
}

// IsListAll indicates PageSize and Page will be ignored
func (opts ListOptions) IsListAll() bool {
	return opts.ListAll
}

// SetDefaultValues sets default values
func (opts *ListOptions) SetDefaultValues() {
	if opts.PageSize <= 0 {
		opts.PageSize = setting.API.DefaultPagingNum
	}
	if opts.PageSize > setting.API.MaxResponseItems {
		opts.PageSize = setting.API.MaxResponseItems
	}
	if opts.Page <= 0 {
		opts.Page = 1
	}
}

func (opts ListOptions) ToConds() builder.Cond {
	return builder.NewCond()
}

// AbsoluteListOptions absolute options to paginate results
type AbsoluteListOptions struct {
	skip int
	take int
}

var _ Paginator = &AbsoluteListOptions{}

// 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}
}

// IsListAll will always return false
func (opts *AbsoluteListOptions) IsListAll() bool {
	return false
}

// GetSkipTake returns the skip and take values
func (opts *AbsoluteListOptions) GetSkipTake() (skip, take int) {
	return opts.skip, opts.take
}

// FindOptions represents a find options
type FindOptions interface {
	GetPage() int
	GetPageSize() int
	IsListAll() bool
	ToConds() builder.Cond
}

type JoinFunc func(sess Engine) error

type FindOptionsJoin interface {
	ToJoins() []JoinFunc
}

type FindOptionsOrder interface {
	ToOrders() string
}

// Find represents a common find function which accept an options interface
func Find[T any](ctx context.Context, opts FindOptions) ([]*T, error) {
	sess := GetEngine(ctx).Where(opts.ToConds())

	if joinOpt, ok := opts.(FindOptionsJoin); ok {
		for _, joinFunc := range joinOpt.ToJoins() {
			if err := joinFunc(sess); err != nil {
				return nil, err
			}
		}
	}
	if orderOpt, ok := opts.(FindOptionsOrder); ok {
		if order := orderOpt.ToOrders(); order != "" {
			sess.OrderBy(order)
		}
	}

	page, pageSize := opts.GetPage(), opts.GetPageSize()
	if !opts.IsListAll() && pageSize > 0 {
		if page == 0 {
			page = 1
		}
		sess.Limit(pageSize, (page-1)*pageSize)
	}

	findPageSize := defaultFindSliceSize
	if pageSize > 0 {
		findPageSize = pageSize
	}
	objects := make([]*T, 0, findPageSize)
	if err := sess.Find(&objects); err != nil {
		return nil, err
	}
	return objects, nil
}

// Count represents a common count function which accept an options interface
func Count[T any](ctx context.Context, opts FindOptions) (int64, error) {
	sess := GetEngine(ctx).Where(opts.ToConds())
	if joinOpt, ok := opts.(FindOptionsJoin); ok {
		for _, joinFunc := range joinOpt.ToJoins() {
			if err := joinFunc(sess); err != nil {
				return 0, err
			}
		}
	}

	var object T
	return sess.Count(&object)
}

// FindAndCount represents a common findandcount function which accept an options interface
func FindAndCount[T any](ctx context.Context, opts FindOptions) ([]*T, int64, error) {
	sess := GetEngine(ctx).Where(opts.ToConds())
	page, pageSize := opts.GetPage(), opts.GetPageSize()
	if !opts.IsListAll() && pageSize > 0 && page >= 1 {
		sess.Limit(pageSize, (page-1)*pageSize)
	}
	if joinOpt, ok := opts.(FindOptionsJoin); ok {
		for _, joinFunc := range joinOpt.ToJoins() {
			if err := joinFunc(sess); err != nil {
				return nil, 0, err
			}
		}
	}
	if orderOpt, ok := opts.(FindOptionsOrder); ok {
		if order := orderOpt.ToOrders(); order != "" {
			sess.OrderBy(order)
		}
	}

	findPageSize := defaultFindSliceSize
	if pageSize > 0 {
		findPageSize = pageSize
	}
	objects := make([]*T, 0, findPageSize)
	cnt, err := sess.FindAndCount(&objects)
	if err != nil {
		return nil, 0, err
	}
	return objects, cnt, nil
}