Skip to content
This repository has been archived by the owner on Nov 8, 2023. It is now read-only.

Commit

Permalink
Wrapper for MockStateRangeQueryIterator with pagination (#66)
Browse files Browse the repository at this point in the history
Wrapper for MockStateRangeQueryIterator with pagination
  • Loading branch information
uraldav authored Aug 10, 2021
1 parent f8b4ed4 commit bfba2a6
Show file tree
Hide file tree
Showing 9 changed files with 713 additions and 25 deletions.
11 changes: 10 additions & 1 deletion router/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,12 @@ type (
// ParamBytes returns parameter value as bytes.
ParamBytes(name string) []byte

// ParamInt returns parameter value as bytes.
// ParamInt returns parameter value as int.
ParamInt(name string) int

// ParamInt32 returns parameter value as int32.
ParamInt32(name string) int32

// SetParam sets parameter value.
SetParam(name string, value interface{})

Expand Down Expand Up @@ -256,6 +259,12 @@ func (c *context) ParamInt(name string) int {
return out
}

// ParamInt32 returns parameter value as int32.
func (c *context) ParamInt32(name string) int32 {
out, _ := c.Param(name).(int32)
return out
}

func (c *context) Set(key string, val interface{}) {
if c.store == nil {
c.store = make(InterfaceMap)
Expand Down
12 changes: 12 additions & 0 deletions state/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ func (k Key) String() string {
return strings.Join(k, ` | `)
}

// Parts returns object type and attributes slice
func (k Key) Parts() (objectType string, attrs []string) {
if len(k) > 0 {
objectType = k[0]

if len(k) > 1 {
attrs = k[1:]
}
}
return
}

func NormalizeKey(stub shim.ChaincodeStubInterface, key interface{}) (Key, error) {
switch k := key.(type) {
case Key:
Expand Down
43 changes: 43 additions & 0 deletions state/mapping/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (

"go.uber.org/zap"

pb "github.com/hyperledger/fabric-protos-go/peer"
"github.com/pkg/errors"

"github.com/s7techlab/cckit/state"
"github.com/s7techlab/cckit/state/schema"
)
Expand All @@ -17,6 +19,10 @@ type (
// ListWith allows to refine search criteria by adding to namespace key parts
ListWith(schema interface{}, key state.Key) (result interface{}, err error)

// ListPaginatedWith allows to refine search criteria by adding to namespace key parts with pagination
ListPaginatedWith(schema interface{}, key state.Key, pageSize int32, bookmark string) (
result interface{}, metadata *pb.QueryResponseMetadata, err error)

// GetByUniqKey return one entry
// Deprecated: use GetByKey
GetByUniqKey(schema interface{}, idx string, idxVal []string, target ...interface{}) (result interface{}, err error)
Expand Down Expand Up @@ -179,6 +185,24 @@ func (s *Impl) List(entry interface{}, target ...interface{}) (interface{}, erro
return s.State.List(namespace, m.Schema(), m.List())
}

func (s *Impl) ListPaginated(entry interface{}, pageSize int32, bookmark string, target ...interface{}) (
interface{}, *pb.QueryResponseMetadata, error) {
if !s.mappings.Exists(entry) {
return s.State.ListPaginated(entry, pageSize, bookmark, target...)
}

m, err := s.mappings.Get(entry)
if err != nil {
return nil, nil, errors.Wrap(err, `mapping`)
}

namespace := m.Namespace()
s.Logger().Debug(`state mapped LIST`, zap.String(`namespace`, namespace.String()),
zap.Int32("pageSize", pageSize), zap.String("bookmark", bookmark))

return s.State.ListPaginated(namespace, pageSize, bookmark, m.Schema(), m.List())
}

func (s *Impl) ListWith(entry interface{}, key state.Key) (result interface{}, err error) {
if !s.mappings.Exists(entry) {
return nil, ErrStateMappingNotFound
Expand All @@ -194,6 +218,25 @@ func (s *Impl) ListWith(entry interface{}, key state.Key) (result interface{}, e
return s.State.List(namespace.Append(key), m.Schema(), m.List())
}

func (s *Impl) ListPaginatedWith(
schema interface{}, key state.Key, pageSize int32, bookmark string) (
result interface{}, metadata *pb.QueryResponseMetadata, err error) {
if !s.mappings.Exists(schema) {
return nil, nil, ErrStateMappingNotFound
}
m, err := s.mappings.Get(schema)
if err != nil {
return nil, nil, errors.Wrap(err, `mapping`)
}

namespace := m.Namespace()
s.Logger().Debug(`state mapped LIST`,
zap.String(`namespace`, namespace.String()), zap.String(`list`, namespace.Append(key).String()),
zap.Int32("pageSize", pageSize), zap.String("bookmark", bookmark))

return s.State.ListPaginated(namespace.Append(key), pageSize, bookmark, m.Schema(), m.List())
}

func (s *Impl) GetByUniqKey(
entry interface{}, idx string, idxVal []string, target ...interface{}) (result interface{}, err error) {
return s.GetByKey(entry, idx, idxVal, target...)
Expand Down
101 changes: 78 additions & 23 deletions state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/pkg/errors"
"go.uber.org/zap"

pb "github.com/hyperledger/fabric-protos-go/peer"

"github.com/s7techlab/cckit/convert"
)

Expand Down Expand Up @@ -56,6 +58,10 @@ type State interface {
// namespace can be part of key (string or []string) or entity with defined mapping
List(namespace interface{}, target ...interface{}) (interface{}, error)

// ListPaginated returns slice of target type with pagination
// namespace can be part of key (string or []string) or entity with defined mapping
ListPaginated(namespace interface{}, pageSize int32, bookmark string, target ...interface{}) (interface{}, *pb.QueryResponseMetadata, error)

// Keys returns slice of keys
// namespace can be part of key (string or []string) or entity with defined mapping
Keys(namespace interface{}) ([]string, error)
Expand Down Expand Up @@ -111,10 +117,11 @@ type Impl struct {
logger *zap.Logger

// wrappers for state access methods
PutState func(string, []byte) error
GetState func(string) ([]byte, error)
DelState func(string) error
GetStateByPartialCompositeKey func(objectType string, keys []string) (shim.StateQueryIteratorInterface, error)
PutState func(string, []byte) error
GetState func(string) ([]byte, error)
DelState func(string) error
GetStateByPartialCompositeKey func(objectType string, keys []string) (shim.StateQueryIteratorInterface, error)
GetStateByPartialCompositeKeyWithPagination func(objectType string, keys []string, pageSize int32, bookmark string) (shim.StateQueryIteratorInterface, *pb.QueryResponseMetadata, error)

StateKeyTransformer KeyTransformer
StateKeyReverseTransformer KeyTransformer
Expand Down Expand Up @@ -156,6 +163,12 @@ func NewState(stub shim.ChaincodeStubInterface, logger *zap.Logger) *Impl {
return stub.GetStateByPartialCompositeKey(objectType, keys)
}

i.GetStateByPartialCompositeKeyWithPagination = func(
objectType string, keys []string, pageSize int32, bookmark string) (
shim.StateQueryIteratorInterface, *pb.QueryResponseMetadata, error) {
return stub.GetStateByPartialCompositeKeyWithPagination(objectType, keys, pageSize, bookmark)
}

return i
}

Expand All @@ -167,10 +180,11 @@ func (s *Impl) Clone() State {
GetState: s.GetState,
DelState: s.DelState,
GetStateByPartialCompositeKey: s.GetStateByPartialCompositeKey,
StateKeyTransformer: s.StateKeyTransformer,
StateKeyReverseTransformer: s.StateKeyReverseTransformer,
StateGetTransformer: s.StateGetTransformer,
StatePutTransformer: s.StatePutTransformer,
GetStateByPartialCompositeKeyWithPagination: s.GetStateByPartialCompositeKeyWithPagination,
StateKeyTransformer: s.StateKeyTransformer,
StateKeyReverseTransformer: s.StateKeyReverseTransformer,
StateGetTransformer: s.StateGetTransformer,
StatePutTransformer: s.StatePutTransformer,
}
}

Expand Down Expand Up @@ -308,31 +322,72 @@ func (s *Impl) List(namespace interface{}, target ...interface{}) (interface{},
}

func (s *Impl) createStateQueryIterator(namespace interface{}) (shim.StateQueryIteratorInterface, error) {
key, err := NormalizeKey(s.stub, namespace)
if err != nil {
return nil, fmt.Errorf(`list prefix: %w`, err)
}

keyTransformed, err := s.StateKeyTransformer(key)
n, t, err := s.normalizeAndTransformKey(namespace)
if err != nil {
return nil, err
}
s.logger.Debug(`state KEYS with composite key`,
zap.String(`key`, key.String()), zap.String(`transformed`, keyTransformed.String()))
zap.String(`key`, n.String()), zap.String(`transformed`, t.String()))

if len(keyTransformed) == 0 || keyTransformed[0] == `` {
objectType, attrs := t.Parts()
if objectType == `` {
return s.stub.GetStateByRange(``, ``) // all state entries
}
var (
objectType = keyTransformed[0]
attrs []string
)

if len(keyTransformed) > 1 {
attrs = keyTransformed[1:]
return s.GetStateByPartialCompositeKey(objectType, attrs)
}

// normalizeAndTransformKey returns normalized and transformed key or error if occur
func (s *Impl) normalizeAndTransformKey(namespace interface{}) (Key, Key, error) {
normal, err := NormalizeKey(s.stub, namespace)
if err != nil {
return nil, nil, fmt.Errorf(`list prefix: %w`, err)
}

transformed, err := s.StateKeyTransformer(normal)
if err != nil {
return nil, nil, err
}

return s.GetStateByPartialCompositeKey(objectType, attrs)
return normal, transformed, nil
}

func (s *Impl) ListPaginated(
namespace interface{}, pageSize int32, bookmark string, target ...interface{}) (
interface{}, *pb.QueryResponseMetadata, error) {
stateList, err := NewStateList(target...)
if err != nil {
return nil, nil, err
}

iter, md, err := s.createStateQueryPagedIterator(namespace, pageSize, bookmark)
if err != nil {
return nil, nil, errors.Wrap(err, `state iterator`)
}

defer func() { _ = iter.Close() }()
list, err := stateList.Fill(iter, s.StateGetTransformer)

return list, md, err
}

func (s *Impl) createStateQueryPagedIterator(namespace interface{}, pageSize int32, bookmark string) (
shim.StateQueryIteratorInterface, *pb.QueryResponseMetadata, error) {
n, t, err := s.normalizeAndTransformKey(namespace)
if err != nil {
return nil, nil, err
}

s.logger.Debug(`state KEYS with composite key`,
zap.String(`key`, n.String()), zap.String(`transformed`, t.String()),
zap.Int32("pageSize", pageSize), zap.String("bookmark", bookmark))

objectType, attrs := t.Parts()
if objectType == `` {
return s.stub.GetStateByRangeWithPagination(``, ``, pageSize, bookmark)
}

return s.GetStateByPartialCompositeKeyWithPagination(objectType, attrs, pageSize, bookmark)
}

func (s *Impl) Keys(namespace interface{}) ([]string, error) {
Expand Down
26 changes: 26 additions & 0 deletions state/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,32 @@ var _ = Describe(`State`, func() {
}
})

It("allow to get list with pagination", func() {
req := &schema.BookListRequest{ // query first page
PageSize: 2,
}
pr := booksCC.Invoke(`bookListPaginated`, req)
res := expectcc.PayloadIs(pr, &schema.BookList{}).(schema.BookList)

Expect(len(res.Items)).To(Equal(2))
Expect(res.Next == ``).To(Equal(false))
for i := range testdata.Books[0:2] {
Expect(res.Items[i].Id).To(Equal(testdata.Books[i].Id))
}

req2 := &schema.BookListRequest{ // query second page
PageSize: 2,
Bookmark: res.Next,
}
pr2 := booksCC.Invoke(`bookListPaginated`, req2)
res2 := expectcc.PayloadIs(pr2, &schema.BookList{}).(schema.BookList)
Expect(len(res2.Items)).To(Equal(1))
Expect(res2.Next == ``).To(Equal(true))
for i := range testdata.Books[2:3] {
Expect(res.Items[i].Id).To(Equal(testdata.Books[i].Id))
}
})

It("Allow to get entry ids", func() {
ids := expectcc.PayloadIs(booksCC.Invoke(`bookIds`), &[]string{}).([]string)
Expect(len(ids)).To(Equal(3))
Expand Down
26 changes: 25 additions & 1 deletion state/testdata/cc_books.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package testdata

import (
"errors"

"fmt"
"github.com/s7techlab/cckit/extensions/debug"
"github.com/s7techlab/cckit/extensions/owner"
"github.com/s7techlab/cckit/router"
Expand All @@ -19,6 +19,7 @@ func NewBooksCC() *router.Chaincode {

r.Init(owner.InvokeSetFromCreator).
Invoke(`bookList`, bookList).
Invoke(`bookListPaginated`, bookListPaginated, p.Struct(`in`, &schema.BookListRequest{})).
Invoke(`bookIds`, bookIds).
Invoke(`bookGet`, bookGet, p.String(`id`)).
Invoke(`bookInsert`, bookInsert, p.Struct(`book`, &schema.Book{})).
Expand All @@ -38,6 +39,29 @@ func bookList(c router.Context) (interface{}, error) {
return c.State().List(schema.BookEntity, &schema.Book{})
}

func bookListPaginated(c router.Context) (interface{}, error) {
in, ok := c.Param(`in`).(schema.BookListRequest)
if !ok {
return nil, fmt.Errorf("unexpected argument type")
}

list, md, err := c.State().ListPaginated(schema.BookEntity, in.PageSize, in.Bookmark, &schema.Book{})
if err != nil {
return nil, err
}

var books []*schema.Book
for _, item := range list.([]interface{}) {
var b = item.(schema.Book)
books = append(books, &b)
}

return schema.BookList{
Items: books,
Next: md.Bookmark,
}, nil
}

func bookIds(c router.Context) (interface{}, error) {
return c.State().Keys(schema.BookEntity)
}
Expand Down
10 changes: 10 additions & 0 deletions state/testdata/schema/book.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,13 @@ type PrivateBook struct {
func (pb PrivateBook) Key() ([]string, error) {
return []string{PrivateBookEntity, pb.Id}, nil
}

type BookListRequest struct {
PageSize int32
Bookmark string
}

type BookList struct {
Items []*Book
Next string
}
Loading

0 comments on commit bfba2a6

Please sign in to comment.