coverage~bigbes/sr-ht-doltede3b0bbgraph/paging.go

Coverage
84.1% 37/44 statements
Δ
Blob
f93e74d
1 package graph
2
3 import (
4 "context"
5 "strconv"
6
7 coremodel "sourcecraft.dev/bigbes/sr-ht-core/model"
8
9 "sourcecraft.dev/bigbes/sr-ht-dolt/browse"
10 "sourcecraft.dev/bigbes/sr-ht-dolt/core"
11 "sourcecraft.dev/bigbes/sr-ht-dolt/graph/model"
12 )
13
14 // Page sizes for the database listings. The count travels in the cursor, as it
15 // does on every other sr.ht service, so a client that asked for a page size
16 // keeps it across pages without repeating itself.
17 const (
18 defaultPageSize = 25
19 maxPageSize = 100
20 )
21
22 // page slices one page out of a listing the store has already filtered to what
23 // the caller may see, and mints the cursor for the next one.
24 //
25 // The slicing is in memory rather than in SQL, which is a real limit and not an
26 // oversight: ListReposForViewer's rule spans three tables and the listings it
27 // answers are instance-sized (tens of rows), so the whole set is fetched and cut
28 // here. A listing that outgrows that wants a keyset query in db/, and this is
29 // the function that would then become a thin wrapper over it.
30 //
31 // The cursor carries the id of the first row of the next page and the page size.
32 // Resuming starts at the first row whose id is at or below it, so a cursor whose
33 // row was deleted — or made invisible to this caller — between two pages resumes
34 // at the next row instead of failing or, worse, silently starting over.
35 13 func page(repos []*core.Repo, cursor *coremodel.Cursor, filter *coremodel.Filter) *model.DatabaseCursor {
36 13 count := defaultPageSize
37 13 start := 0
38 13 if cursor != nil {
39 3 if cursor.Count > 0 {
40 3 count = cursor.Count
41 3 }
42 3 if after, err := strconv.Atoi(cursor.Next); err == nil {
43 3 start = len(repos)
44 11 for i, repo := range repos {
45 11 if repo.ID <= after {
46 3 start = i
47 3 break
48 }
49 }
50 }
51 }
52 // A filter given on this call wins over what the cursor remembers, so a
53 // client can change the page size mid-walk without minting a new cursor.
54 13 if filter != nil && filter.Count != nil && *filter.Count > 0 {
55 3 count = *filter.Count
56 3 }
57 13 if count > maxPageSize {
58 1 count = maxPageSize
59 1 }
60
61 13 end := start + count
62 13 if end > len(repos) {
63 9 end = len(repos)
64 9 }
65
66 13 out := make([]*model.Database, 0, end-start)
67 129 for _, repo := range repos[start:end] {
68 129 out = append(out, model.NewDatabase(repo))
69 129 }
70
71 13 page := &model.DatabaseCursor{Results: out}
72 13 if end < len(repos) {
73 4 page.Cursor = &coremodel.Cursor{
74 4 Next: strconv.Itoa(repos[end].ID),
75 4 Count: count,
76 4 }
77 4 }
78 13 return page
79 }
80
81 // logLimit bounds how many commits one Database.log answers. The cap is the
82 // surface's, not the client's: a log page is a walk of the on-disk store.
83 1 func logLimit(limit *int) int {
84 1 if limit == nil || *limit <= 0 {
85 1 return defaultLogLimit
86 1 }
87 0 if *limit > maxLogLimit {
88 0 return maxLogLimit
89 0 }
90 0 return *limit
91 }
92
93 // cursorNext is the commit hash a log page resumes from, or "" to start at the
94 // ref's head.
95 1 func cursorNext(from *coremodel.Cursor) string {
96 1 if from == nil {
97 1 return ""
98 1 }
99 0 return from.Next
100 }
101
102 // nextCursor wraps browse's "next hash" in the schema's cursor, or nil when the
103 // walk reached the root commit.
104 1 func nextCursor(next string) *coremodel.Cursor {
105 1 if next == "" {
106 0 return nil
107 0 }
108 1 return &coremodel.Cursor{Next: next}
109 }
110
111 // resolveRef answers the ref a field should read: the one the query named, or
112 // the database's default branch. It returns "" — and no error — for a store
113 // with no branches at all, which is a database nothing has been pushed to yet;
114 // the caller turns that into the empty answer for its own field.
115 4 func resolveRef(ctx context.Context, sess BrowseSession, ref *string) (string, error) {
116 4 if ref != nil && *ref != "" {
117 0 return *ref, nil
118 0 }
119 4 branches, err := sess.Branches(ctx)
120 4 if err != nil {
121 0 return "", internalError("Database.ref", err)
122 0 }
123 4 if len(branches) == 0 {
124 2 return "", nil
125 2 }
126 2 return browse.DefaultBranch(branches), nil
127 }