Skip to content

Commit

Permalink
pagination support sorting (#3)
Browse files Browse the repository at this point in the history
* pagination support sorting

* fixing lint issues

* removing coverage restrictions to errors.go file

---------

Co-authored-by: Manuel Doncel Martos <[email protected]>
  • Loading branch information
manuelarte and Manuel Doncel Martos authored Feb 19, 2025
1 parent d420128 commit 432939b
Show file tree
Hide file tree
Showing 10 changed files with 462 additions and 37 deletions.
1 change: 1 addition & 0 deletions .testcoverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ threshold:
exclude:
# Exclude files or packages matching their paths
paths:
- ^errors.go
- ^internal/model.go$
- ^pkg/bar # exclude package `pkg/bar`
25 changes: 18 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[![Go](https://github.com/manuelarte/pagorminator/actions/workflows/go.yml/badge.svg)](https://github.com/manuelarte/pagorminator/actions/workflows/go.yml)
![coverage](https://raw.githubusercontent.com/manuelarte/pagorminator/badges/.badges/main/coverage.svg)
[![Go Report Card](https://goreportcard.com/badge/github.com/manuelarte/pagorminator)](https://goreportcard.com/report/github.com/manuelarte/pagorminator)
# 📃 pagorminator
# 📃 PaGORMinator

Gorm plugin to add pagination to your select queries
Gorm plugin to add **Pagination** to your select queries

## ⬇️ How to install it

Expand All @@ -12,32 +12,43 @@ Gorm plugin to add pagination to your select queries
## 🎯 How to use it

```go
var DB *gorm.DB
DB.Use(pagorminator.PaGormMinator{})
var products []*Products
pageRequest, err := pagorminator.PageRequest(0, 10)
DB.Clauses(pageRequest).First(&products)
```

The plugin will calculate the total amount of elements so then the fields `total amounts` and `total pages` can be used too.
The pagination struct contains the following data:

+ `page`: page number, e.g. 0
+ `size`: page size, e.g. 10
+ `sort`: to apply sorting, e.g. id,asc

**The plugin will calculate the total amount of elements**, and then the pagination instance provides a `GetTotalElements()` and `GetTotalPages()` methods to be used.
The pagination starts at index 0. So if the total pages is 6, then the pagination index goes from 0 to 5.

## 🎓 Examples

- [Simple](./examples/simple/main.go)

Simple query with no filters (where clause)
Simple query with no filters (no WHERE clause)

- [Simple Sort](./examples/simple-sort/main.go)

Simple query with sorting and no filters (no WHERE clause)

- [Many Pages](./examples/many-pages/main.go)

Simple query with no filters (where clause), many pages
Simple query with no filters (no WHERE clause), many pages

- [Filter](./examples/filter/main.go)

Using where to filter
Using WHERE to filter

- [Unpaged](./examples/unpaged/main.go)

Unpaged query
Unpaged query (pagination with no pagination)

- [Many Pages With Preload](./examples/many-pages-preload/main.go)

Expand Down
21 changes: 21 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package pagorminator

import (
"errors"
"fmt"
)

var (
ErrPageCantBeNegative = errors.New("page number can't be negative")
ErrSizeCantBeNegative = errors.New("size can't be negative")
ErrSizeNotAllowed = errors.New("size is not allowed")
ErrOrderPropertyIsEmpty = errors.New("order property is empty")
)

type ErrOrderDirectionNotValid struct {
Direction Direction
}

func (e ErrOrderDirectionNotValid) Error() string {
return fmt.Sprintf("order direction is not valid: %s", e.Direction)
}
41 changes: 41 additions & 0 deletions examples/simple-sort/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package main

import (
"fmt"
"github.com/manuelarte/pagorminator"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)

type Product struct {
gorm.Model
Code string
Price uint
}

func (p Product) String() string {
return fmt.Sprintf("Product{Code: %s, Price: %d}", p.Code, p.Price)
}

func main() {
db, err := gorm.Open(sqlite.Open("file:mem?mode=memory&cache=shared"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}

_ = db.Use(pagorminator.PaGormMinator{})
_ = db.AutoMigrate(&Product{})
db.Create(&Product{Code: "D42", Price: 100})
db.Create(&Product{Code: "E42", Price: 200})
fmt.Printf("2 products created\n")

var products []*Product
pageRequest, _ := pagorminator.PageRequest(0, 1, pagorminator.MustNewOrder("price", pagorminator.DESC))
db.Clauses(pageRequest).Find(&products)

fmt.Printf("PageRequest result:(Page: %d, Size: %d, TotalElements: %d, TotalPages: %d)\n",
pageRequest.GetPage(), pageRequest.GetSize(), pageRequest.GetTotalElements(), pageRequest.GetTotalPages())
for _, product := range products {
fmt.Printf("%s\n", product)
}
}
33 changes: 6 additions & 27 deletions model.go → pagination.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,12 @@
package pagorminator

import (
"errors"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"math"
"sync"
)

const pagorminatorClause = "pagorminator:clause"

var (
ErrPageCantBeNegative = errors.New("page number can't be negative")
ErrSizeCantBeNegative = errors.New("size can't be negative")
ErrSizeNotAllowed = errors.New("size is not allowed")
)

var _ clause.Expression = new(Pagination)
var _ gorm.StatementModifier = new(Pagination)

// PageRequest Create page to query the database
func PageRequest(page, size int) (*Pagination, error) {
func PageRequest(page, size int, orders ...Order) (*Pagination, error) {
if page < 0 {
return nil, ErrPageCantBeNegative
}
Expand All @@ -30,7 +16,8 @@ func PageRequest(page, size int) (*Pagination, error) {
if page > 0 && size == 0 {
return nil, ErrSizeNotAllowed
}
return &Pagination{page: page, size: size}, nil
sort := NewSort(orders...)
return &Pagination{page: page, size: size, sort: sort}, nil
}

// UnPaged Create an unpaged request (no pagination is applied)
Expand All @@ -42,6 +29,7 @@ func UnPaged() *Pagination {
type Pagination struct {
page int
size int
sort Sort
teMutex sync.RWMutex
totalElementsSet bool
totalElements int64
Expand Down Expand Up @@ -90,17 +78,8 @@ func (p *Pagination) IsUnPaged() bool {
return p.page == 0 && p.size == 0
}

// ModifyStatement Modify the query clause to apply pagination
func (p *Pagination) ModifyStatement(stm *gorm.Statement) {
db := stm.DB
db.Set(pagorminatorClause, p)
if !p.IsUnPaged() {
stm.DB.Limit(p.size).Offset(p.GetOffset())
}
}

// Build N/A for pagination
func (p *Pagination) Build(_ clause.Builder) {
func (p *Pagination) IsSort() bool {
return p.sort != nil && len(p.sort) > 0
}

func calculateTotalPages(totalElements int64, size int) int {
Expand Down
28 changes: 28 additions & 0 deletions pagination_clause.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package pagorminator

import (
"gorm.io/gorm"
"gorm.io/gorm/clause"
)

const pagorminatorClause = "pagorminator:clause"

var _ clause.Expression = new(Pagination)
var _ gorm.StatementModifier = new(Pagination)

// ModifyStatement Modify the query clause to apply pagination
func (p *Pagination) ModifyStatement(stm *gorm.Statement) {
db := stm.DB
db.Set(pagorminatorClause, p)
tx := stm.DB
if !p.IsUnPaged() {
tx = tx.Limit(p.size).Offset(p.GetOffset())
}
if p.IsSort() {
tx.Order(p.sort.String())
}
}

// Build N/A for pagination
func (p *Pagination) Build(_ clause.Builder) {
}
File renamed without changes.
Loading

0 comments on commit 432939b

Please sign in to comment.