Skip to content

Commit

Permalink
Add concept of driver & customize cursor
Browse files Browse the repository at this point in the history
  • Loading branch information
raphaelvigee committed Nov 21, 2020
1 parent cecfe98 commit a3428b6
Show file tree
Hide file tree
Showing 12 changed files with 498 additions and 269 deletions.
5 changes: 1 addition & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,5 @@ jobs:
- name: Check out code into the Go module directory
uses: actions/checkout@v2

- name: Build
run: go build -v .

- name: Test
run: go test -v .
run: go test -v ./...
53 changes: 27 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
![Test](https://github.com/raphaelvigee/go-paginate/workflows/Test/badge.svg)

An efficient go data cursor-based paginator.
For now only supports [gorm](https://gorm.io), support for any data source to be added later™️, PRs welcome.

- Supports multiple columns with multiple orderings
- Plug and play
- Easy to use
- Efficient

## Drivers

- [gorm](https://gorm.io):
- Supports multiple columns with multiple orderings directions

- Missing driver? Make a PR!

## Usage

```go
Expand All @@ -18,9 +23,11 @@ package main
import (
"fmt"
paginator "github.com/raphaelvigee/go-paginate"
"github.com/raphaelvigee/go-paginate/cursor"
"github.com/raphaelvigee/go-paginate/driver/gorm"
uuid "github.com/satori/go.uuid"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
gormdb "gorm.io/gorm"
"time"
)

Expand All @@ -34,7 +41,7 @@ func main() {
// Errors omitted for brevity

// Open the DB
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{NowFunc: func() time.Time { return time.Now().Local() }})
db, err := gormdb.Open(sqlite.Open("file::memory:?cache=shared"), &gormdb.Config{NowFunc: func() time.Time { return time.Now().Local() }})
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -68,25 +75,27 @@ func main() {
})

// Define the pagination criterias
pg := paginator.New(&paginator.Paginator{
Columns: []*paginator.Column{
{
Name: "created_at",
// For SQLite the placeholder must be wrapped with `datetime()`
Placeholder: func(*paginator.Column) string {
return "datetime(?)"
},
// For SQLite the column name must be wrapped with `datetime()`
Reference: func(c *paginator.Column) string {
return fmt.Sprintf("datetime(%v)", c.Name)
pg := paginator.New(paginator.Options{
Driver: gorm.Driver{
Columns: []*gorm.Column{
{
Name: "created_at",
// For SQLite the placeholder must be wrapped with `datetime()`
Placeholder: func(*gorm.Column) string {
return "datetime(?)"
},
// For SQLite the column name must be wrapped with `datetime()`
Reference: func(c *gorm.Column) string {
return fmt.Sprintf("datetime(%v)", c.Name)
},
},
},
},
})

// This would typically come from the request
cursorString := "" // must be empty for the first request
cursorType := paginator.CursorAfter
cursorType := cursor.After
cursorLimit := 2

c, err := pg.Cursor(cursorString, cursorType, cursorLimit)
Expand All @@ -108,22 +117,14 @@ func main() {

// Retrieve the results for the provided cursor/limit
var users []User
// res.Tx is nil when no results are available
if res.Tx != nil {
if err := res.Tx.Find(&users).Error; err != nil {
panic(err)
}
if err := res.Query(&users); err != nil {
panic(err)
}

fmt.Println(len(users)) // Should print 2
}
```

## Roadmap

- Support different datasources (not only gorm)
- Custom cursors encode/decode

# Release

TAG=v0.0.1 make tag
59 changes: 0 additions & 59 deletions cursor.go

This file was deleted.

65 changes: 65 additions & 0 deletions cursor/cursor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package cursor

import (
"encoding/base64"
"github.com/vmihailenco/msgpack/v5"
)

type Type int

const (
Before Type = 1 << iota
After
)

type Cursor struct {
Limit int
Type Type
Value interface{}
}

type EncoderDecoder interface {
// When input is nil, must return an empty string
Encode(input interface{}) (string, error)

// When encoded is an empty string, return value must be nil
Decode(encoded string) (interface{}, error)
}

func MsgPackBase64EncoderDecoder() EncoderDecoder {
return msgpackBase64{}
}

type msgpackBase64 struct {
}

func (m msgpackBase64) Encode(input interface{}) (string, error) {
if input == nil {
return "", nil
}

data, err := msgpack.Marshal(input)
if err != nil {
return "", err
}

return base64.StdEncoding.EncodeToString(data), nil
}

func (m msgpackBase64) Decode(encoded string) (interface{}, error) {
if len(encoded) == 0 {
return nil, nil
}

decoded, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
return nil, err
}

var data interface{}
if err = msgpack.Unmarshal(decoded, &data); err != nil {
return nil, err
}

return data, nil
}
36 changes: 36 additions & 0 deletions driver/driver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package driver

import "github.com/raphaelvigee/go-paginate/cursor"

type Driver interface {
Init()

// Used to convert data from the driver layer to the cursor layer
// This should ideally be the smallest representation of the data
// (ex: prefer literal, over array, over map...)
CursorEncode(input interface{}) (interface{}, error)

// Used to convert data from the cursor layer to the driver layer
// input can be nil
CursorDecode(input interface{}) (interface{}, error)

Paginate(c cursor.Cursor, input interface{}) (Page, error)
}

type Executor interface {
Query(dst interface{}) error
Count() (uint64, error)
}

type PageInfo struct {
HasNextPage bool
HasPreviousPage bool
StartCursor interface{}
EndCursor interface{}
}

type Page interface {
Executor
Cursor(i int64) (interface{}, error)
Info() PageInfo
}
10 changes: 6 additions & 4 deletions column.go → driver/gorm/column.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
package go_paginate
package gorm

import "github.com/raphaelvigee/go-paginate/cursor"

type Order string

Expand All @@ -15,7 +17,7 @@ func (o Order) Invert() Order {
return OrderAsc
}

panic("invalid order")
panic("invalid order: " + string(o))
}

type Column struct {
Expand All @@ -27,13 +29,13 @@ type Column struct {
Placeholder func(column *Column) string
}

func (c Column) Order(t CursorType) Order {
func (c Column) Order(t cursor.Type) Order {
order := OrderAsc
if c.Desc {
order = OrderDesc
}

if t == CursorBefore {
if t == cursor.Before {
order = order.Invert()
}

Expand Down
Loading

0 comments on commit a3428b6

Please sign in to comment.