code.gitea.io/gitea@v1.22.3/models/db/list.go (about) 1 // Copyright 2020 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package db 5 6 import ( 7 "context" 8 9 "code.gitea.io/gitea/modules/setting" 10 11 "xorm.io/builder" 12 "xorm.io/xorm" 13 ) 14 15 const ( 16 // DefaultMaxInSize represents default variables number on IN () in SQL 17 DefaultMaxInSize = 50 18 defaultFindSliceSize = 10 19 ) 20 21 // Paginator is the base for different ListOptions types 22 type Paginator interface { 23 GetSkipTake() (skip, take int) 24 IsListAll() bool 25 } 26 27 // SetSessionPagination sets pagination for a database session 28 func SetSessionPagination(sess Engine, p Paginator) *xorm.Session { 29 skip, take := p.GetSkipTake() 30 31 return sess.Limit(take, skip) 32 } 33 34 // ListOptions options to paginate results 35 type ListOptions struct { 36 PageSize int 37 Page int // start from 1 38 ListAll bool // if true, then PageSize and Page will not be taken 39 } 40 41 var ListOptionsAll = ListOptions{ListAll: true} 42 43 var ( 44 _ Paginator = &ListOptions{} 45 _ FindOptions = ListOptions{} 46 ) 47 48 // GetSkipTake returns the skip and take values 49 func (opts *ListOptions) GetSkipTake() (skip, take int) { 50 opts.SetDefaultValues() 51 return (opts.Page - 1) * opts.PageSize, opts.PageSize 52 } 53 54 func (opts ListOptions) GetPage() int { 55 return opts.Page 56 } 57 58 func (opts ListOptions) GetPageSize() int { 59 return opts.PageSize 60 } 61 62 // IsListAll indicates PageSize and Page will be ignored 63 func (opts ListOptions) IsListAll() bool { 64 return opts.ListAll 65 } 66 67 // SetDefaultValues sets default values 68 func (opts *ListOptions) SetDefaultValues() { 69 if opts.PageSize <= 0 { 70 opts.PageSize = setting.API.DefaultPagingNum 71 } 72 if opts.PageSize > setting.API.MaxResponseItems { 73 opts.PageSize = setting.API.MaxResponseItems 74 } 75 if opts.Page <= 0 { 76 opts.Page = 1 77 } 78 } 79 80 func (opts ListOptions) ToConds() builder.Cond { 81 return builder.NewCond() 82 } 83 84 // AbsoluteListOptions absolute options to paginate results 85 type AbsoluteListOptions struct { 86 skip int 87 take int 88 } 89 90 var _ Paginator = &AbsoluteListOptions{} 91 92 // NewAbsoluteListOptions creates a list option with applied limits 93 func NewAbsoluteListOptions(skip, take int) *AbsoluteListOptions { 94 if skip < 0 { 95 skip = 0 96 } 97 if take <= 0 { 98 take = setting.API.DefaultPagingNum 99 } 100 if take > setting.API.MaxResponseItems { 101 take = setting.API.MaxResponseItems 102 } 103 return &AbsoluteListOptions{skip, take} 104 } 105 106 // IsListAll will always return false 107 func (opts *AbsoluteListOptions) IsListAll() bool { 108 return false 109 } 110 111 // GetSkipTake returns the skip and take values 112 func (opts *AbsoluteListOptions) GetSkipTake() (skip, take int) { 113 return opts.skip, opts.take 114 } 115 116 // FindOptions represents a find options 117 type FindOptions interface { 118 GetPage() int 119 GetPageSize() int 120 IsListAll() bool 121 ToConds() builder.Cond 122 } 123 124 type JoinFunc func(sess Engine) error 125 126 type FindOptionsJoin interface { 127 ToJoins() []JoinFunc 128 } 129 130 type FindOptionsOrder interface { 131 ToOrders() string 132 } 133 134 // Find represents a common find function which accept an options interface 135 func Find[T any](ctx context.Context, opts FindOptions) ([]*T, error) { 136 sess := GetEngine(ctx).Where(opts.ToConds()) 137 138 if joinOpt, ok := opts.(FindOptionsJoin); ok { 139 for _, joinFunc := range joinOpt.ToJoins() { 140 if err := joinFunc(sess); err != nil { 141 return nil, err 142 } 143 } 144 } 145 if orderOpt, ok := opts.(FindOptionsOrder); ok { 146 if order := orderOpt.ToOrders(); order != "" { 147 sess.OrderBy(order) 148 } 149 } 150 151 page, pageSize := opts.GetPage(), opts.GetPageSize() 152 if !opts.IsListAll() && pageSize > 0 { 153 if page == 0 { 154 page = 1 155 } 156 sess.Limit(pageSize, (page-1)*pageSize) 157 } 158 159 findPageSize := defaultFindSliceSize 160 if pageSize > 0 { 161 findPageSize = pageSize 162 } 163 objects := make([]*T, 0, findPageSize) 164 if err := sess.Find(&objects); err != nil { 165 return nil, err 166 } 167 return objects, nil 168 } 169 170 // Count represents a common count function which accept an options interface 171 func Count[T any](ctx context.Context, opts FindOptions) (int64, error) { 172 sess := GetEngine(ctx).Where(opts.ToConds()) 173 if joinOpt, ok := opts.(FindOptionsJoin); ok { 174 for _, joinFunc := range joinOpt.ToJoins() { 175 if err := joinFunc(sess); err != nil { 176 return 0, err 177 } 178 } 179 } 180 181 var object T 182 return sess.Count(&object) 183 } 184 185 // FindAndCount represents a common findandcount function which accept an options interface 186 func FindAndCount[T any](ctx context.Context, opts FindOptions) ([]*T, int64, error) { 187 sess := GetEngine(ctx).Where(opts.ToConds()) 188 page, pageSize := opts.GetPage(), opts.GetPageSize() 189 if !opts.IsListAll() && pageSize > 0 && page >= 1 { 190 sess.Limit(pageSize, (page-1)*pageSize) 191 } 192 if joinOpt, ok := opts.(FindOptionsJoin); ok { 193 for _, joinFunc := range joinOpt.ToJoins() { 194 if err := joinFunc(sess); err != nil { 195 return nil, 0, err 196 } 197 } 198 } 199 if orderOpt, ok := opts.(FindOptionsOrder); ok { 200 if order := orderOpt.ToOrders(); order != "" { 201 sess.OrderBy(order) 202 } 203 } 204 205 findPageSize := defaultFindSliceSize 206 if pageSize > 0 { 207 findPageSize = pageSize 208 } 209 objects := make([]*T, 0, findPageSize) 210 cnt, err := sess.FindAndCount(&objects) 211 if err != nil { 212 return nil, 0, err 213 } 214 return objects, cnt, nil 215 }