Skip to content

Commit 87cbb20

Browse files
committed
tapdb: add pagination and ordering to QueryAddrEvents
1 parent 300b489 commit 87cbb20

File tree

6 files changed

+157
-7
lines changed

6 files changed

+157
-7
lines changed

address/event.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,28 @@ const (
4141
StatusCompleted Status = 3
4242
)
4343

44+
// SortDirection is an enum used to specify the order of returned events.
45+
type SortDirection uint8
46+
47+
const (
48+
// SortAscending is a sentinel value that indicates that the sort
49+
// should be in ascending order.
50+
SortAscending SortDirection = iota
51+
52+
// SortDescending is a sentinel value that indicates that the sort
53+
// should be in descending order.
54+
SortDescending
55+
)
56+
const (
57+
// DefaultEventQueryLimit is the number of events returned
58+
// when no limit is provided.
59+
DefaultEventQueryLimit = 512
60+
61+
// MaxEventQueryLimit is the maximum number of events that can be
62+
// returned in a single query.
63+
MaxEventQueryLimit = 16384
64+
)
65+
4466
// EventQueryParams holds the set of query params for address events.
4567
type EventQueryParams struct {
4668
// AddrTaprootOutputKey is the optional 32-byte x-only serialized
@@ -60,6 +82,17 @@ type EventQueryParams struct {
6082
// (inclusive). Can be set to nil to return events of all creation
6183
// times.
6284
CreationTimeFrom *time.Time
85+
86+
// Offset is the offset into the result set to start returning events.
87+
Offset int32
88+
89+
// Limit is the max number of events that should be returned. If zero,
90+
// then DefaultEventQueryLimit will be used.
91+
Limit int32
92+
93+
// SortDirection is the sort direction to use when returning the
94+
// events. The default zero value sorts the events in ascending order.
95+
SortDirection SortDirection
6396
}
6497

6598
// AssetOutput holds the information about a single asset output that was sent

rpcserver.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2230,6 +2230,10 @@ func (r *rpcServer) AddrReceives(ctx context.Context,
22302230
if req.Limit < 0 {
22312231
return nil, fmt.Errorf("limit must be non-negative")
22322232
}
2233+
if req.Limit > address.MaxEventQueryLimit {
2234+
return nil, fmt.Errorf("limit must be less than %d",
2235+
address.MaxEventQueryLimit)
2236+
}
22332237

22342238
sqlQuery.Offset = req.Offset
22352239
sqlQuery.Limit = req.Limit

tapdb/addrs.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -990,9 +990,12 @@ func (t *TapAddressBook) QueryAddrEvents(
990990
error) {
991991

992992
sqlQuery := AddrEventQuery{
993-
StatusFrom: int16(address.StatusTransactionDetected),
994-
StatusTo: int16(address.StatusCompleted),
995-
CreatedAfter: time.Unix(0, 0).UTC(),
993+
StatusFrom: int16(address.StatusTransactionDetected),
994+
StatusTo: int16(address.StatusCompleted),
995+
CreatedAfter: time.Unix(0, 0).UTC(),
996+
NumLimit: params.Limit,
997+
NumOffset: params.Offset,
998+
SortDirection: sqlInt16(params.SortDirection),
996999
}
9971000
if len(params.AddrTaprootOutputKey) > 0 {
9981001
sqlQuery.AddrTaprootKey = params.AddrTaprootOutputKey
@@ -1006,6 +1009,9 @@ func (t *TapAddressBook) QueryAddrEvents(
10061009
if params.CreationTimeFrom != nil && !params.CreationTimeFrom.IsZero() {
10071010
sqlQuery.CreatedAfter = params.CreationTimeFrom.UTC()
10081011
}
1012+
if params.Limit == 0 {
1013+
sqlQuery.NumLimit = int32(address.DefaultEventQueryLimit)
1014+
}
10091015

10101016
var (
10111017
readTxOpts = NewAssetStoreReadTx()

tapdb/addrs_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,97 @@ func TestQueryAddrEvents(t *testing.T) {
935935
require.Equal(t, event.Outpoint, events[0].Outpoint)
936936
}
937937

938+
// TestQueryAddrEventsPagination tests that query parameters for pagination are
939+
// applied when listing address events.
940+
func TestQueryAddrEventsPagination(t *testing.T) {
941+
t.Parallel()
942+
943+
// First, make a new addr book instance we'll use in the test below.
944+
testClock := clock.NewTestClock(time.Now())
945+
addrBook, _ := newAddrBook(t, testClock)
946+
947+
ctx := context.Background()
948+
949+
// Insert a test address.
950+
addrVersion := test.RandFlip(address.V0, address.V1)
951+
proofCourierAddr := address.RandProofCourierAddrForVersion(
952+
t, addrVersion,
953+
)
954+
addr, assetGen, assetGroup := address.RandAddrWithVersion(
955+
t, chainParams, proofCourierAddr, addrVersion,
956+
)
957+
err := addrBook.db.ExecTx(
958+
ctx, WriteTxOption(),
959+
insertFullAssetGen(ctx, assetGen, assetGroup),
960+
)
961+
require.NoError(t, err)
962+
require.NoError(t, addrBook.InsertAddrs(ctx, *addr))
963+
964+
// Insert events.
965+
const numEvents = 5
966+
events := make([]*address.Event, numEvents)
967+
baseTime := testClock.Now()
968+
for i := range numEvents {
969+
testClock.SetTime(baseTime.Add(time.Duration(i) * time.Minute))
970+
tx := randWalletTx()
971+
event, err := addrBook.GetOrCreateEvent(
972+
ctx, address.StatusTransactionDetected,
973+
newTransfer(t, addr, tx, 0),
974+
)
975+
require.NoError(t, err)
976+
events[i] = event
977+
}
978+
979+
// Test all events.
980+
allEvents, err := addrBook.QueryAddrEvents(
981+
ctx,
982+
address.EventQueryParams{},
983+
)
984+
require.NoError(t, err)
985+
require.Len(t, allEvents, numEvents)
986+
987+
// Test limit.
988+
firstPage, err := addrBook.QueryAddrEvents(
989+
ctx,
990+
address.EventQueryParams{
991+
Limit: 2,
992+
SortDirection: address.SortAscending,
993+
},
994+
)
995+
require.NoError(t, err)
996+
require.Len(t, firstPage, 2)
997+
require.Equal(t, events[0].ID, firstPage[0].ID)
998+
require.Equal(t, events[1].ID, firstPage[1].ID)
999+
1000+
// Test offset and limit.
1001+
secondPage, err := addrBook.QueryAddrEvents(
1002+
ctx,
1003+
address.EventQueryParams{
1004+
Limit: 2,
1005+
Offset: 2,
1006+
SortDirection: address.SortAscending,
1007+
},
1008+
)
1009+
require.NoError(t, err)
1010+
require.Len(t, secondPage, 2)
1011+
require.Equal(t, events[2].ID, secondPage[0].ID)
1012+
require.Equal(t, events[3].ID, secondPage[1].ID)
1013+
1014+
// Test ordering.
1015+
descPage, err := addrBook.QueryAddrEvents(
1016+
ctx,
1017+
address.EventQueryParams{
1018+
Limit: 2,
1019+
Offset: 1,
1020+
SortDirection: address.SortDescending,
1021+
},
1022+
)
1023+
require.NoError(t, err)
1024+
require.Len(t, descPage, 2)
1025+
require.Equal(t, events[3].ID, descPage[0].ID)
1026+
require.Equal(t, events[2].ID, descPage[1].ID)
1027+
}
1028+
9381029
// TestAddrByScriptKeyAndVersion tests that we can retrieve an address by its
9391030
// script key and version.
9401031
func TestAddrByScriptKeyAndVersion(t *testing.T) {

tapdb/sqlc/addrs.sql.go

Lines changed: 13 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tapdb/sqlc/queries/addrs.sql

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,11 +222,16 @@ SELECT
222222
FROM addr_events
223223
JOIN addrs
224224
ON addr_events.addr_id = addrs.id
225-
WHERE addr_events.status >= @status_from
225+
WHERE addr_events.status >= @status_from
226226
AND addr_events.status <= @status_to
227227
AND COALESCE(@addr_taproot_key, addrs.taproot_output_key) = addrs.taproot_output_key
228228
AND addr_events.creation_time >= @created_after
229-
ORDER by addr_events.creation_time;
229+
ORDER BY
230+
CASE WHEN sqlc.narg('sort_direction') = 0 THEN addr_events.creation_time END ASC,
231+
CASE WHEN sqlc.narg('sort_direction') = 1 THEN addr_events.creation_time END DESC,
232+
CASE WHEN sqlc.narg('sort_direction') = 0 THEN addr_events.id END ASC,
233+
CASE WHEN sqlc.narg('sort_direction') = 1 THEN addr_events.id END DESC
234+
LIMIT @num_limit OFFSET @num_offset;
230235

231236
-- name: QueryLastEventHeight :one
232237
SELECT cast(coalesce(max(chain_txns.block_height), 0) AS BIGINT) AS last_height

0 commit comments

Comments
 (0)