mirror of
https://github.com/luxfi/graph.git
synced 2026-09-24 00:26:07 +00:00
The page before a cursor is bounded in SQL, beside the LIMIT
A client pages a pool's history as "the N trades before this time". The bound was applied by FilterResults after the database had already taken the newest N, so every page after the first threw its rows away and came back empty. It sits in the WHERE now, on the same index the pool does. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -4,6 +4,7 @@ package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -59,3 +60,30 @@ func TestPoolHistoryReadsTheIndex(t *testing.T) {
|
||||
t.Fatalf("pool history still scans or sorts: %s", joined)
|
||||
}
|
||||
}
|
||||
|
||||
// The page before a cursor is the older trades, not the newest ones thrown away.
|
||||
func TestPageBeforeCursorReachesOlderTrades(t *testing.T) {
|
||||
s, err := New(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatalf("open store: %v", err)
|
||||
}
|
||||
defer s.Close()
|
||||
if err := s.Init(context.Background()); err != nil {
|
||||
t.Fatalf("init: %v", err)
|
||||
}
|
||||
for i := 1; i <= 6; i++ {
|
||||
s.SeedSwap(fmt.Sprintf("s%d", i), &SeedSwapData{Timestamp: int64(i * 100), Pool: "0xpool", AmountUSD: "1"})
|
||||
}
|
||||
got, err := s.GetSwaps(context.Background(), 2, "timestamp", "desc", map[string]interface{}{"pool": "0xPOOL", "timestamp_lt": float64(400)})
|
||||
if err != nil {
|
||||
t.Fatalf("GetSwaps: %v", err)
|
||||
}
|
||||
rows := got.([]interface{})
|
||||
if len(rows) != 2 {
|
||||
t.Fatalf("want the 2 trades before t=400, got %d", len(rows))
|
||||
}
|
||||
ids := []string{rows[0].(map[string]interface{})["id"].(string), rows[1].(map[string]interface{})["id"].(string)}
|
||||
if ids[0] != "s3" || ids[1] != "s2" {
|
||||
t.Fatalf("want s3, s2 (newest first, before the cursor); got %v", ids)
|
||||
}
|
||||
}
|
||||
|
||||
+27
-2
@@ -747,11 +747,22 @@ func (s *Store) GetSwaps(_ context.Context, limit int, orderBy, orderDirection s
|
||||
// 1000 newest chain-wide, kept the handful that matched, and usually
|
||||
// answered with none at all. The pool column is indexed for exactly this.
|
||||
args := []interface{}{}
|
||||
query := "SELECT id, data FROM swaps"
|
||||
conds := []string{}
|
||||
if pool, ok := whereString(where, "pool"); ok {
|
||||
query += " WHERE pool = ? COLLATE NOCASE"
|
||||
conds = append(conds, "pool = ? COLLATE NOCASE")
|
||||
args = append(args, pool)
|
||||
}
|
||||
// A page of history is asked for as "the N before this time", and that bound
|
||||
// has to sit beside the LIMIT for the same reason the pool does: applied
|
||||
// after it, the query returns the newest N and then throws them all away.
|
||||
if before, ok := whereNumber(where, "timestamp_lt"); ok {
|
||||
conds = append(conds, "timestamp < ?")
|
||||
args = append(args, before)
|
||||
}
|
||||
query := "SELECT id, data FROM swaps"
|
||||
if len(conds) > 0 {
|
||||
query += " WHERE " + strings.Join(conds, " AND ")
|
||||
}
|
||||
if orderBy == "timestamp" {
|
||||
if strings.EqualFold(orderDirection, "desc") {
|
||||
query += " ORDER BY timestamp DESC"
|
||||
@@ -813,6 +824,20 @@ func whereString(where map[string]interface{}, key string) (string, bool) {
|
||||
return s, true
|
||||
}
|
||||
|
||||
// whereNumber reads a numeric bound out of a `where` map, however the client
|
||||
// wrote it — GraphQL variables arrive as float64, literals may arrive as strings.
|
||||
func whereNumber(where map[string]interface{}, key string) (float64, bool) {
|
||||
v, ok := where[key]
|
||||
if !ok || v == nil {
|
||||
return 0, false
|
||||
}
|
||||
f, err := strconv.ParseFloat(fmt.Sprint(v), 64)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return f, true
|
||||
}
|
||||
|
||||
func (s *Store) swapToMap(id string, sw *SeedSwapData) map[string]interface{} {
|
||||
var pool interface{} = sw.Pool
|
||||
if p, _ := s.loadPool(sw.Pool); p != nil {
|
||||
|
||||
Reference in New Issue
Block a user