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  }