Skip to content

Commit 90592f8

Browse files
committed
api: add pagination support
A user could fetch a position of a last tuple using a new method of the SelectRequest type: selectRequest = selectRequest.FetchPos(true) The position will be stored in a new field of the Response type: Response.Pos A user could specify a tuple from which selection must continue or its position with a new method of the SelectRequest type: selectRequest = selectRequest.After(resp.Pos) selectRequest = selectRequest.After([]interface{}{23}) In action it looks like: req := NewSelectRequest(space).Key(key).Limit(10) for condition { resp, _ := conn.Do(req).Get() // Process resp.Data. req.After(resp.Pos) } 1. tarantool/tarantool#7639 Closes #246
1 parent 788327e commit 90592f8

10 files changed

+335
-57
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
1010

1111
### Added
1212

13+
- Support pagination (#246)
14+
1315
### Changed
1416

1517
### Fixed

const.go

+4
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,20 @@ const (
3030
KeyLimit = 0x12
3131
KeyOffset = 0x13
3232
KeyIterator = 0x14
33+
KeyFetchPos = 0x1f
3334
KeyKey = 0x20
3435
KeyTuple = 0x21
3536
KeyFunctionName = 0x22
3637
KeyUserName = 0x23
3738
KeyExpression = 0x27
39+
KeyAfterPos = 0x2e
40+
KeyAfterTuple = 0x2f
3841
KeyDefTuple = 0x28
3942
KeyData = 0x30
4043
KeyError24 = 0x31 /* Error in pre-2.4 format. */
4144
KeyMetaData = 0x32
4245
KeyBindCount = 0x34
46+
KeyPos = 0x35
4347
KeySQLText = 0x40
4448
KeySQLBind = 0x41
4549
KeySQLInfo = 0x42

example_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ func ExampleProtocolVersion() {
329329
fmt.Println("Connector client protocol features:", clientProtocolInfo.Features)
330330
// Output:
331331
// Connector client protocol version: 4
332-
// Connector client protocol features: [StreamsFeature TransactionsFeature ErrorExtensionFeature WatchersFeature]
332+
// Connector client protocol features: [StreamsFeature TransactionsFeature ErrorExtensionFeature WatchersFeature PaginationFeature]
333333
}
334334

335335
func getTestTxnOpts() tarantool.Opts {

export_test.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ func RefImplPingBody(enc *encoder) error {
2323

2424
// RefImplSelectBody is reference implementation for filling of a select
2525
// request's body.
26-
func RefImplSelectBody(enc *encoder, space, index, offset, limit, iterator uint32, key interface{}) error {
27-
return fillSelect(enc, space, index, offset, limit, iterator, key)
26+
func RefImplSelectBody(enc *encoder, space, index, offset, limit, iterator uint32,
27+
key, after interface{}, fetchPos bool) error {
28+
return fillSelect(enc, space, index, offset, limit, iterator, key, after, fetchPos)
2829
}
2930

3031
// RefImplInsertBody is reference implementation for filling of an insert

protocol.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const (
4747
// (supported by connector).
4848
WatchersFeature ProtocolFeature = 3
4949
// PaginationFeature represents support of pagination
50-
// (unsupported by connector).
50+
// (supported by connector).
5151
PaginationFeature ProtocolFeature = 4
5252
)
5353

@@ -83,11 +83,14 @@ var clientProtocolInfo ProtocolInfo = ProtocolInfo{
8383
// version 2 (Tarantool 2.10.0), in connector since 1.10.0.
8484
// Watchers were introduced in protocol version 3 (Tarantool 2.10.0), in
8585
// connector since 1.10.0.
86+
// Pagination were introduced in protocol version 4 (Tarantool 2.11.0), in
87+
// connector since 1.11.0.
8688
Features: []ProtocolFeature{
8789
StreamsFeature,
8890
TransactionsFeature,
8991
ErrorExtensionFeature,
9092
WatchersFeature,
93+
PaginationFeature,
9194
},
9295
}
9396

request.go

+74-9
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ func fillSearch(enc *encoder, spaceNo, indexNo uint32, key interface{}) error {
1818
return enc.Encode(key)
1919
}
2020

21-
func fillIterator(enc *encoder, offset, limit, iterator uint32) {
21+
func fillIterator(enc *encoder, offset, limit, iterator uint32) error {
2222
encodeUint(enc, KeyIterator)
2323
encodeUint(enc, uint64(iterator))
2424
encodeUint(enc, KeyOffset)
2525
encodeUint(enc, uint64(offset))
2626
encodeUint(enc, KeyLimit)
27-
encodeUint(enc, uint64(limit))
27+
return encodeUint(enc, uint64(limit))
2828
}
2929

3030
func fillInsert(enc *encoder, spaceNo uint32, tuple interface{}) error {
@@ -35,10 +35,50 @@ func fillInsert(enc *encoder, spaceNo uint32, tuple interface{}) error {
3535
return enc.Encode(tuple)
3636
}
3737

38-
func fillSelect(enc *encoder, spaceNo, indexNo, offset, limit, iterator uint32, key interface{}) error {
39-
enc.EncodeMapLen(6)
40-
fillIterator(enc, offset, limit, iterator)
41-
return fillSearch(enc, spaceNo, indexNo, key)
38+
func fillSelect(enc *encoder, spaceNo, indexNo, offset, limit, iterator uint32,
39+
key, after interface{}, fetchPos bool) error {
40+
mapLen := 6
41+
if fetchPos {
42+
mapLen += 1
43+
}
44+
if after != nil {
45+
mapLen += 1
46+
}
47+
if err := enc.EncodeMapLen(mapLen); err != nil {
48+
return err
49+
}
50+
if err := fillIterator(enc, offset, limit, iterator); err != nil {
51+
return err
52+
}
53+
if err := fillSearch(enc, spaceNo, indexNo, key); err != nil {
54+
return err
55+
}
56+
if fetchPos {
57+
if err := encodeUint(enc, KeyFetchPos); err != nil {
58+
return err
59+
}
60+
if err := enc.EncodeBool(fetchPos); err != nil {
61+
return err
62+
}
63+
}
64+
if after != nil {
65+
if pos, ok := after.([]byte); ok {
66+
if err := encodeUint(enc, KeyAfterPos); err != nil {
67+
return err
68+
}
69+
if err := enc.EncodeString(string(pos)); err != nil {
70+
return err
71+
}
72+
} else {
73+
if err := encodeUint(enc, KeyAfterTuple); err != nil {
74+
return err
75+
}
76+
if err := enc.Encode(after); err != nil {
77+
return err
78+
}
79+
}
80+
}
81+
return nil
4282
}
4383

4484
func fillUpdate(enc *encoder, spaceNo, indexNo uint32, key, ops interface{}) error {
@@ -660,9 +700,9 @@ func (req *PingRequest) Context(ctx context.Context) *PingRequest {
660700
// by a Connection.
661701
type SelectRequest struct {
662702
spaceIndexRequest
663-
isIteratorSet bool
703+
isIteratorSet, fetchPos bool
664704
offset, limit, iterator uint32
665-
key interface{}
705+
key, after interface{}
666706
}
667707

668708
// NewSelectRequest returns a new empty SelectRequest.
@@ -671,8 +711,10 @@ func NewSelectRequest(space interface{}) *SelectRequest {
671711
req.requestCode = SelectRequestCode
672712
req.setSpace(space)
673713
req.isIteratorSet = false
714+
req.fetchPos = false
674715
req.iterator = IterAll
675716
req.key = []interface{}{}
717+
req.after = nil
676718
req.limit = 0xFFFFFFFF
677719
return req
678720
}
@@ -716,14 +758,37 @@ func (req *SelectRequest) Key(key interface{}) *SelectRequest {
716758
return req
717759
}
718760

761+
// FetchPos determines whether to fetch positions of tuples. A position
762+
// descriptor will be saved in Response.Pos value.
763+
//
764+
// Requires Tarantool >= 2.11.
765+
//
766+
// Note: default value is false.
767+
func (req *SelectRequest) FetchPos(fetch bool) *SelectRequest {
768+
req.fetchPos = fetch
769+
return req
770+
}
771+
772+
// After must contain a tuple from which selection must continue or its
773+
// position (a value from Response.Pos).
774+
//
775+
// Requires Tarantool >= 2.11.
776+
//
777+
// Note: default value in nil.
778+
func (req *SelectRequest) After(after interface{}) *SelectRequest {
779+
req.after = after
780+
return req
781+
}
782+
719783
// Body fills an encoder with the select request body.
720784
func (req *SelectRequest) Body(res SchemaResolver, enc *encoder) error {
721785
spaceNo, indexNo, err := res.ResolveSpaceIndex(req.space, req.index)
722786
if err != nil {
723787
return err
724788
}
725789

726-
return fillSelect(enc, spaceNo, indexNo, req.offset, req.limit, req.iterator, req.key)
790+
return fillSelect(enc, spaceNo, indexNo, req.offset, req.limit, req.iterator,
791+
req.key, req.after, req.fetchPos)
727792
}
728793

729794
// Context sets a passed context to the request.

request_test.go

+36-10
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,8 @@ func TestSelectRequestDefaultValues(t *testing.T) {
319319
var refBuf bytes.Buffer
320320

321321
refEnc := NewEncoder(&refBuf)
322-
err := RefImplSelectBody(refEnc, validSpace, defaultIndex, 0, 0xFFFFFFFF, IterAll, []interface{}{})
322+
err := RefImplSelectBody(refEnc, validSpace, defaultIndex, 0, 0xFFFFFFFF,
323+
IterAll, []interface{}{}, nil, false)
323324
if err != nil {
324325
t.Errorf("An unexpected RefImplSelectBody() error %q", err.Error())
325326
return
@@ -334,7 +335,8 @@ func TestSelectRequestDefaultIteratorEqIfKey(t *testing.T) {
334335
key := []interface{}{uint(18)}
335336

336337
refEnc := NewEncoder(&refBuf)
337-
err := RefImplSelectBody(refEnc, validSpace, defaultIndex, 0, 0xFFFFFFFF, IterEq, key)
338+
err := RefImplSelectBody(refEnc, validSpace, defaultIndex, 0, 0xFFFFFFFF,
339+
IterEq, key, nil, false)
338340
if err != nil {
339341
t.Errorf("An unexpected RefImplSelectBody() error %q", err.Error())
340342
return
@@ -351,7 +353,8 @@ func TestSelectRequestIteratorNotChangedIfKey(t *testing.T) {
351353
const iter = IterGe
352354

353355
refEnc := NewEncoder(&refBuf)
354-
err := RefImplSelectBody(refEnc, validSpace, defaultIndex, 0, 0xFFFFFFFF, iter, key)
356+
err := RefImplSelectBody(refEnc, validSpace, defaultIndex, 0, 0xFFFFFFFF,
357+
iter, key, nil, false)
355358
if err != nil {
356359
t.Errorf("An unexpected RefImplSelectBody() error %q", err.Error())
357360
return
@@ -368,22 +371,45 @@ func TestSelectRequestSetters(t *testing.T) {
368371
const limit = 5
369372
const iter = IterLt
370373
key := []interface{}{uint(36)}
371-
var refBuf bytes.Buffer
374+
afterBytes := []byte{0x1, 0x2, 0x3}
375+
afterKey := []interface{}{uint(13)}
376+
var refBufAfterBytes, refBufAfterKey bytes.Buffer
372377

373-
refEnc := NewEncoder(&refBuf)
374-
err := RefImplSelectBody(refEnc, validSpace, validIndex, offset, limit, iter, key)
378+
refEncAfterBytes := NewEncoder(&refBufAfterBytes)
379+
err := RefImplSelectBody(refEncAfterBytes, validSpace, validIndex, offset,
380+
limit, iter, key, afterBytes, true)
375381
if err != nil {
376-
t.Errorf("An unexpected RefImplSelectBody() error %q", err.Error())
382+
t.Errorf("An unexpected RefImplSelectBody() error %s", err)
377383
return
378384
}
379385

380-
req := NewSelectRequest(validSpace).
386+
refEncAfterKey := NewEncoder(&refBufAfterKey)
387+
err = RefImplSelectBody(refEncAfterKey, validSpace, validIndex, offset,
388+
limit, iter, key, afterKey, true)
389+
if err != nil {
390+
t.Errorf("An unexpected RefImplSelectBody() error %s", err)
391+
return
392+
}
393+
394+
reqAfterBytes := NewSelectRequest(validSpace).
381395
Index(validIndex).
382396
Offset(offset).
383397
Limit(limit).
384398
Iterator(iter).
385-
Key(key)
386-
assertBodyEqual(t, refBuf.Bytes(), req)
399+
Key(key).
400+
After(afterBytes).
401+
FetchPos(true)
402+
reqAfterKey := NewSelectRequest(validSpace).
403+
Index(validIndex).
404+
Offset(offset).
405+
Limit(limit).
406+
Iterator(iter).
407+
Key(key).
408+
After(afterKey).
409+
FetchPos(true)
410+
411+
assertBodyEqual(t, refBufAfterBytes.Bytes(), reqAfterBytes)
412+
assertBodyEqual(t, refBufAfterKey.Bytes(), reqAfterKey)
387413
}
388414

389415
func TestInsertRequestDefaultValues(t *testing.T) {

response.go

+14-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@ import (
77
type Response struct {
88
RequestId uint32
99
Code uint32
10-
Error string // error message
11-
// Data contains deserialized data for untyped requests
12-
Data []interface{}
10+
// Error message.
11+
Error string
12+
// Data contains deserialized data for untyped requests.
13+
Data []interface{}
14+
// Pos contains a position descriptor of last selected tuple.
15+
Pos []byte
1316
MetaData []ColumnMetaData
1417
SQLInfo SQLInfo
1518
buf smallBuf
@@ -228,6 +231,10 @@ func (resp *Response) decodeBody() (err error) {
228231
if !found {
229232
return fmt.Errorf("unknown auth type %s", auth)
230233
}
234+
case KeyPos:
235+
if resp.Pos, err = d.DecodeBytes(); err != nil {
236+
return fmt.Errorf("unable to decode a position: %w", err)
237+
}
231238
default:
232239
if err = d.Skip(); err != nil {
233240
return err
@@ -300,6 +307,10 @@ func (resp *Response) decodeBodyTyped(res interface{}) (err error) {
300307
if err = d.Decode(&resp.MetaData); err != nil {
301308
return err
302309
}
310+
case KeyPos:
311+
if resp.Pos, err = d.DecodeBytes(); err != nil {
312+
return fmt.Errorf("unable to decode a position: %w", err)
313+
}
303314
default:
304315
if err = d.Skip(); err != nil {
305316
return err

0 commit comments

Comments
 (0)