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

Commit bfba2a6

Browse files
authored
Wrapper for MockStateRangeQueryIterator with pagination (#66)
Wrapper for MockStateRangeQueryIterator with pagination
1 parent f8b4ed4 commit bfba2a6

9 files changed

Lines changed: 713 additions & 25 deletions

File tree

router/context.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,12 @@ type (
6868
// ParamBytes returns parameter value as bytes.
6969
ParamBytes(name string) []byte
7070

71-
// ParamInt returns parameter value as bytes.
71+
// ParamInt returns parameter value as int.
7272
ParamInt(name string) int
7373

74+
// ParamInt32 returns parameter value as int32.
75+
ParamInt32(name string) int32
76+
7477
// SetParam sets parameter value.
7578
SetParam(name string, value interface{})
7679

@@ -256,6 +259,12 @@ func (c *context) ParamInt(name string) int {
256259
return out
257260
}
258261

262+
// ParamInt32 returns parameter value as int32.
263+
func (c *context) ParamInt32(name string) int32 {
264+
out, _ := c.Param(name).(int32)
265+
return out
266+
}
267+
259268
func (c *context) Set(key string, val interface{}) {
260269
if c.store == nil {
261270
c.store = make(InterfaceMap)

state/key.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,18 @@ func (k Key) String() string {
6767
return strings.Join(k, ` | `)
6868
}
6969

70+
// Parts returns object type and attributes slice
71+
func (k Key) Parts() (objectType string, attrs []string) {
72+
if len(k) > 0 {
73+
objectType = k[0]
74+
75+
if len(k) > 1 {
76+
attrs = k[1:]
77+
}
78+
}
79+
return
80+
}
81+
7082
func NormalizeKey(stub shim.ChaincodeStubInterface, key interface{}) (Key, error) {
7183
switch k := key.(type) {
7284
case Key:

state/mapping/state.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import (
55

66
"go.uber.org/zap"
77

8+
pb "github.com/hyperledger/fabric-protos-go/peer"
89
"github.com/pkg/errors"
10+
911
"github.com/s7techlab/cckit/state"
1012
"github.com/s7techlab/cckit/state/schema"
1113
)
@@ -17,6 +19,10 @@ type (
1719
// ListWith allows to refine search criteria by adding to namespace key parts
1820
ListWith(schema interface{}, key state.Key) (result interface{}, err error)
1921

22+
// ListPaginatedWith allows to refine search criteria by adding to namespace key parts with pagination
23+
ListPaginatedWith(schema interface{}, key state.Key, pageSize int32, bookmark string) (
24+
result interface{}, metadata *pb.QueryResponseMetadata, err error)
25+
2026
// GetByUniqKey return one entry
2127
// Deprecated: use GetByKey
2228
GetByUniqKey(schema interface{}, idx string, idxVal []string, target ...interface{}) (result interface{}, err error)
@@ -179,6 +185,24 @@ func (s *Impl) List(entry interface{}, target ...interface{}) (interface{}, erro
179185
return s.State.List(namespace, m.Schema(), m.List())
180186
}
181187

188+
func (s *Impl) ListPaginated(entry interface{}, pageSize int32, bookmark string, target ...interface{}) (
189+
interface{}, *pb.QueryResponseMetadata, error) {
190+
if !s.mappings.Exists(entry) {
191+
return s.State.ListPaginated(entry, pageSize, bookmark, target...)
192+
}
193+
194+
m, err := s.mappings.Get(entry)
195+
if err != nil {
196+
return nil, nil, errors.Wrap(err, `mapping`)
197+
}
198+
199+
namespace := m.Namespace()
200+
s.Logger().Debug(`state mapped LIST`, zap.String(`namespace`, namespace.String()),
201+
zap.Int32("pageSize", pageSize), zap.String("bookmark", bookmark))
202+
203+
return s.State.ListPaginated(namespace, pageSize, bookmark, m.Schema(), m.List())
204+
}
205+
182206
func (s *Impl) ListWith(entry interface{}, key state.Key) (result interface{}, err error) {
183207
if !s.mappings.Exists(entry) {
184208
return nil, ErrStateMappingNotFound
@@ -194,6 +218,25 @@ func (s *Impl) ListWith(entry interface{}, key state.Key) (result interface{}, e
194218
return s.State.List(namespace.Append(key), m.Schema(), m.List())
195219
}
196220

221+
func (s *Impl) ListPaginatedWith(
222+
schema interface{}, key state.Key, pageSize int32, bookmark string) (
223+
result interface{}, metadata *pb.QueryResponseMetadata, err error) {
224+
if !s.mappings.Exists(schema) {
225+
return nil, nil, ErrStateMappingNotFound
226+
}
227+
m, err := s.mappings.Get(schema)
228+
if err != nil {
229+
return nil, nil, errors.Wrap(err, `mapping`)
230+
}
231+
232+
namespace := m.Namespace()
233+
s.Logger().Debug(`state mapped LIST`,
234+
zap.String(`namespace`, namespace.String()), zap.String(`list`, namespace.Append(key).String()),
235+
zap.Int32("pageSize", pageSize), zap.String("bookmark", bookmark))
236+
237+
return s.State.ListPaginated(namespace.Append(key), pageSize, bookmark, m.Schema(), m.List())
238+
}
239+
197240
func (s *Impl) GetByUniqKey(
198241
entry interface{}, idx string, idxVal []string, target ...interface{}) (result interface{}, err error) {
199242
return s.GetByKey(entry, idx, idxVal, target...)

state/state.go

Lines changed: 78 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"github.com/pkg/errors"
99
"go.uber.org/zap"
1010

11+
pb "github.com/hyperledger/fabric-protos-go/peer"
12+
1113
"github.com/s7techlab/cckit/convert"
1214
)
1315

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

61+
// ListPaginated returns slice of target type with pagination
62+
// namespace can be part of key (string or []string) or entity with defined mapping
63+
ListPaginated(namespace interface{}, pageSize int32, bookmark string, target ...interface{}) (interface{}, *pb.QueryResponseMetadata, error)
64+
5965
// Keys returns slice of keys
6066
// namespace can be part of key (string or []string) or entity with defined mapping
6167
Keys(namespace interface{}) ([]string, error)
@@ -111,10 +117,11 @@ type Impl struct {
111117
logger *zap.Logger
112118

113119
// wrappers for state access methods
114-
PutState func(string, []byte) error
115-
GetState func(string) ([]byte, error)
116-
DelState func(string) error
117-
GetStateByPartialCompositeKey func(objectType string, keys []string) (shim.StateQueryIteratorInterface, error)
120+
PutState func(string, []byte) error
121+
GetState func(string) ([]byte, error)
122+
DelState func(string) error
123+
GetStateByPartialCompositeKey func(objectType string, keys []string) (shim.StateQueryIteratorInterface, error)
124+
GetStateByPartialCompositeKeyWithPagination func(objectType string, keys []string, pageSize int32, bookmark string) (shim.StateQueryIteratorInterface, *pb.QueryResponseMetadata, error)
118125

119126
StateKeyTransformer KeyTransformer
120127
StateKeyReverseTransformer KeyTransformer
@@ -156,6 +163,12 @@ func NewState(stub shim.ChaincodeStubInterface, logger *zap.Logger) *Impl {
156163
return stub.GetStateByPartialCompositeKey(objectType, keys)
157164
}
158165

166+
i.GetStateByPartialCompositeKeyWithPagination = func(
167+
objectType string, keys []string, pageSize int32, bookmark string) (
168+
shim.StateQueryIteratorInterface, *pb.QueryResponseMetadata, error) {
169+
return stub.GetStateByPartialCompositeKeyWithPagination(objectType, keys, pageSize, bookmark)
170+
}
171+
159172
return i
160173
}
161174

@@ -167,10 +180,11 @@ func (s *Impl) Clone() State {
167180
GetState: s.GetState,
168181
DelState: s.DelState,
169182
GetStateByPartialCompositeKey: s.GetStateByPartialCompositeKey,
170-
StateKeyTransformer: s.StateKeyTransformer,
171-
StateKeyReverseTransformer: s.StateKeyReverseTransformer,
172-
StateGetTransformer: s.StateGetTransformer,
173-
StatePutTransformer: s.StatePutTransformer,
183+
GetStateByPartialCompositeKeyWithPagination: s.GetStateByPartialCompositeKeyWithPagination,
184+
StateKeyTransformer: s.StateKeyTransformer,
185+
StateKeyReverseTransformer: s.StateKeyReverseTransformer,
186+
StateGetTransformer: s.StateGetTransformer,
187+
StatePutTransformer: s.StatePutTransformer,
174188
}
175189
}
176190

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

310324
func (s *Impl) createStateQueryIterator(namespace interface{}) (shim.StateQueryIteratorInterface, error) {
311-
key, err := NormalizeKey(s.stub, namespace)
312-
if err != nil {
313-
return nil, fmt.Errorf(`list prefix: %w`, err)
314-
}
315-
316-
keyTransformed, err := s.StateKeyTransformer(key)
325+
n, t, err := s.normalizeAndTransformKey(namespace)
317326
if err != nil {
318327
return nil, err
319328
}
320329
s.logger.Debug(`state KEYS with composite key`,
321-
zap.String(`key`, key.String()), zap.String(`transformed`, keyTransformed.String()))
330+
zap.String(`key`, n.String()), zap.String(`transformed`, t.String()))
322331

323-
if len(keyTransformed) == 0 || keyTransformed[0] == `` {
332+
objectType, attrs := t.Parts()
333+
if objectType == `` {
324334
return s.stub.GetStateByRange(``, ``) // all state entries
325335
}
326-
var (
327-
objectType = keyTransformed[0]
328-
attrs []string
329-
)
330336

331-
if len(keyTransformed) > 1 {
332-
attrs = keyTransformed[1:]
337+
return s.GetStateByPartialCompositeKey(objectType, attrs)
338+
}
339+
340+
// normalizeAndTransformKey returns normalized and transformed key or error if occur
341+
func (s *Impl) normalizeAndTransformKey(namespace interface{}) (Key, Key, error) {
342+
normal, err := NormalizeKey(s.stub, namespace)
343+
if err != nil {
344+
return nil, nil, fmt.Errorf(`list prefix: %w`, err)
345+
}
346+
347+
transformed, err := s.StateKeyTransformer(normal)
348+
if err != nil {
349+
return nil, nil, err
333350
}
334351

335-
return s.GetStateByPartialCompositeKey(objectType, attrs)
352+
return normal, transformed, nil
353+
}
354+
355+
func (s *Impl) ListPaginated(
356+
namespace interface{}, pageSize int32, bookmark string, target ...interface{}) (
357+
interface{}, *pb.QueryResponseMetadata, error) {
358+
stateList, err := NewStateList(target...)
359+
if err != nil {
360+
return nil, nil, err
361+
}
362+
363+
iter, md, err := s.createStateQueryPagedIterator(namespace, pageSize, bookmark)
364+
if err != nil {
365+
return nil, nil, errors.Wrap(err, `state iterator`)
366+
}
367+
368+
defer func() { _ = iter.Close() }()
369+
list, err := stateList.Fill(iter, s.StateGetTransformer)
370+
371+
return list, md, err
372+
}
373+
374+
func (s *Impl) createStateQueryPagedIterator(namespace interface{}, pageSize int32, bookmark string) (
375+
shim.StateQueryIteratorInterface, *pb.QueryResponseMetadata, error) {
376+
n, t, err := s.normalizeAndTransformKey(namespace)
377+
if err != nil {
378+
return nil, nil, err
379+
}
380+
381+
s.logger.Debug(`state KEYS with composite key`,
382+
zap.String(`key`, n.String()), zap.String(`transformed`, t.String()),
383+
zap.Int32("pageSize", pageSize), zap.String("bookmark", bookmark))
384+
385+
objectType, attrs := t.Parts()
386+
if objectType == `` {
387+
return s.stub.GetStateByRangeWithPagination(``, ``, pageSize, bookmark)
388+
}
389+
390+
return s.GetStateByPartialCompositeKeyWithPagination(objectType, attrs, pageSize, bookmark)
336391
}
337392

338393
func (s *Impl) Keys(namespace interface{}) ([]string, error) {

state/state_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,32 @@ var _ = Describe(`State`, func() {
5454
}
5555
})
5656

57+
It("allow to get list with pagination", func() {
58+
req := &schema.BookListRequest{ // query first page
59+
PageSize: 2,
60+
}
61+
pr := booksCC.Invoke(`bookListPaginated`, req)
62+
res := expectcc.PayloadIs(pr, &schema.BookList{}).(schema.BookList)
63+
64+
Expect(len(res.Items)).To(Equal(2))
65+
Expect(res.Next == ``).To(Equal(false))
66+
for i := range testdata.Books[0:2] {
67+
Expect(res.Items[i].Id).To(Equal(testdata.Books[i].Id))
68+
}
69+
70+
req2 := &schema.BookListRequest{ // query second page
71+
PageSize: 2,
72+
Bookmark: res.Next,
73+
}
74+
pr2 := booksCC.Invoke(`bookListPaginated`, req2)
75+
res2 := expectcc.PayloadIs(pr2, &schema.BookList{}).(schema.BookList)
76+
Expect(len(res2.Items)).To(Equal(1))
77+
Expect(res2.Next == ``).To(Equal(true))
78+
for i := range testdata.Books[2:3] {
79+
Expect(res.Items[i].Id).To(Equal(testdata.Books[i].Id))
80+
}
81+
})
82+
5783
It("Allow to get entry ids", func() {
5884
ids := expectcc.PayloadIs(booksCC.Invoke(`bookIds`), &[]string{}).([]string)
5985
Expect(len(ids)).To(Equal(3))

state/testdata/cc_books.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package testdata
22

33
import (
44
"errors"
5-
5+
"fmt"
66
"github.com/s7techlab/cckit/extensions/debug"
77
"github.com/s7techlab/cckit/extensions/owner"
88
"github.com/s7techlab/cckit/router"
@@ -19,6 +19,7 @@ func NewBooksCC() *router.Chaincode {
1919

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

42+
func bookListPaginated(c router.Context) (interface{}, error) {
43+
in, ok := c.Param(`in`).(schema.BookListRequest)
44+
if !ok {
45+
return nil, fmt.Errorf("unexpected argument type")
46+
}
47+
48+
list, md, err := c.State().ListPaginated(schema.BookEntity, in.PageSize, in.Bookmark, &schema.Book{})
49+
if err != nil {
50+
return nil, err
51+
}
52+
53+
var books []*schema.Book
54+
for _, item := range list.([]interface{}) {
55+
var b = item.(schema.Book)
56+
books = append(books, &b)
57+
}
58+
59+
return schema.BookList{
60+
Items: books,
61+
Next: md.Bookmark,
62+
}, nil
63+
}
64+
4165
func bookIds(c router.Context) (interface{}, error) {
4266
return c.State().Keys(schema.BookEntity)
4367
}

state/testdata/schema/book.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,13 @@ type PrivateBook struct {
2828
func (pb PrivateBook) Key() ([]string, error) {
2929
return []string{PrivateBookEntity, pb.Id}, nil
3030
}
31+
32+
type BookListRequest struct {
33+
PageSize int32
34+
Bookmark string
35+
}
36+
37+
type BookList struct {
38+
Items []*Book
39+
Next string
40+
}

0 commit comments

Comments
 (0)