Page research without exact counts and bound candidate scans [skip ci]
This commit is contained in:
@@ -50,31 +50,28 @@ func TestPostgresPageSearchAndFilters(t *testing.T) {
|
||||
}
|
||||
for _, test := range []struct {
|
||||
filters Filters
|
||||
total int64
|
||||
ids []string
|
||||
}{
|
||||
{Filters{Page: 1, PageSize: 2}, 5, []string{"5", "4"}},
|
||||
{Filters{Page: 2, PageSize: 2}, 5, []string{"3", "2"}},
|
||||
{Filters{Page: 3, PageSize: 2}, 5, []string{"1"}},
|
||||
{Filters{Page: 1, PageSize: 2, Sort: "title"}, 5, []string{"2", "5"}},
|
||||
{Filters{Page: 1, PageSize: 8, Query: "graph"}, 2, []string{"3", "1"}},
|
||||
{Filters{Page: 1, PageSize: 8, Query: "graph OR zebra"}, 0, []string{}},
|
||||
{Filters{Page: 1, PageSize: 8, Query: "Ada Smith", SearchIn: "author"}, 1, []string{"1"}},
|
||||
{Filters{Page: 1, PageSize: 8, Topic: "cs.LG"}, 1, []string{"1"}},
|
||||
{Filters{Page: 1, PageSize: 8, Topic: "cs.DC", Query: "graph"}, 1, []string{"3"}},
|
||||
{Filters{Page: 1, PageSize: 8, Query: `x'); DROP TABLE papers;--`}, 0, []string{}},
|
||||
{Filters{PageSize: 2}, []string{"5", "4"}},
|
||||
{Filters{PageSize: 2, Sort: "title"}, []string{"2", "5"}},
|
||||
{Filters{PageSize: 8, Query: "graph"}, []string{"3", "1"}},
|
||||
{Filters{PageSize: 8, Query: "graph OR zebra"}, []string{}},
|
||||
{Filters{PageSize: 8, Query: "Ada Smith", SearchIn: "author"}, []string{"1"}},
|
||||
{Filters{PageSize: 8, Topic: "cs.LG"}, []string{"1"}},
|
||||
{Filters{PageSize: 8, Topic: "cs.DC", Query: "graph"}, []string{"3"}},
|
||||
{Filters{PageSize: 8, Query: `x'); DROP TABLE papers;--`}, []string{}},
|
||||
} {
|
||||
page, err := r.List(ctx, test.filters)
|
||||
if err != nil {
|
||||
t.Fatalf("%+v: %v", test.filters, err)
|
||||
}
|
||||
if page.Total != test.total || !reflect.DeepEqual(paperIDs(page.Items), test.ids) {
|
||||
t.Fatalf("%+v: total=%d ids=%v", test.filters, page.Total, paperIDs(page.Items))
|
||||
if !reflect.DeepEqual(paperIDs(page.Items), test.ids) {
|
||||
t.Fatalf("%+v: ids=%v", test.filters, paperIDs(page.Items))
|
||||
}
|
||||
if !reflect.DeepEqual(page.Topics, []string{"cs.AI", "cs.DC", "cs.LG"}) {
|
||||
t.Fatalf("mixed tag namespaces: %v", page.Topics)
|
||||
}
|
||||
if page.Page != test.filters.Page || page.PageSize != test.filters.PageSize {
|
||||
if page.PageSize != test.filters.PageSize {
|
||||
t.Fatal("lost paging metadata")
|
||||
}
|
||||
}
|
||||
@@ -143,7 +140,7 @@ func TestPostgresCorpusIsReadOnlyAndHTTPWorks(t *testing.T) {
|
||||
Register(mux, r, map[string]string{"thesisly": "http://localhost:17322/auth/callback"})
|
||||
w := request(mux, "GET", "/projects/thesisly/research/papers?q=graph")
|
||||
var page Page
|
||||
if w.Code != 200 || json.Unmarshal(w.Body.Bytes(), &page) != nil || page.Total != 2 {
|
||||
if w.Code != 200 || json.Unmarshal(w.Body.Bytes(), &page) != nil || len(page.Items) != 2 {
|
||||
t.Fatalf("SQL-backed HTTP list: %d %s", w.Code, w.Body.String())
|
||||
}
|
||||
w = request(mux, "GET", "/projects/thesisly/research/papers/1")
|
||||
@@ -179,8 +176,174 @@ func TestPostgresPublishedTextCodec(t *testing.T) {
|
||||
if w.Code != 200 || json.Unmarshal(w.Body.Bytes(), &decoded) != nil || decoded.Title != p.Title || decoded.Abstract == nil || *decoded.Abstract != *p.Abstract {
|
||||
t.Fatalf("HTTP lost original control characters: %d %s", w.Code, w.Body.String())
|
||||
}
|
||||
page, err := r.List(ctx, Filters{Page: 1, PageSize: 8, Query: "graph"})
|
||||
if err != nil || page.Total != 2 || len(page.Items) != 2 || page.Items[1].Title != p.Title {
|
||||
page, err := r.List(ctx, Filters{PageSize: 8, Query: "graph"})
|
||||
if err != nil || len(page.Items) != 2 || len(page.Items) != 2 || page.Items[1].Title != p.Title {
|
||||
t.Fatalf("encoded corpus search: %+v %v", page, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostgresCursorsTraverseBothDirections(t *testing.T) {
|
||||
r, ctx := integrationRepository(t)
|
||||
for _, sort := range []string{"recent", "title"} {
|
||||
f := Filters{Sort: sort, PageSize: 1}
|
||||
seen := []string{}
|
||||
cursors := []string{""}
|
||||
for {
|
||||
page, err := r.List(ctx, f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(page.Items) != 1 {
|
||||
t.Fatalf("unexpected page %+v", page)
|
||||
}
|
||||
seen = append(seen, page.Items[0].ID)
|
||||
if len(seen) > 5 {
|
||||
t.Fatal("cursor cycle")
|
||||
}
|
||||
if len(seen) > 1 {
|
||||
back := f
|
||||
back.Cursor = page.PreviousCursor
|
||||
prior, err := r.List(ctx, back)
|
||||
if err != nil || len(prior.Items) != 1 || prior.Items[0].ID != seen[len(seen)-2] {
|
||||
t.Fatalf("reverse cursor: %+v %v", prior, err)
|
||||
}
|
||||
} else if page.PreviousCursor != "" {
|
||||
t.Fatal("first page has previous")
|
||||
}
|
||||
if page.NextCursor == "" {
|
||||
break
|
||||
}
|
||||
f.Cursor = page.NextCursor
|
||||
cursors = append(cursors, f.Cursor)
|
||||
}
|
||||
want := []string{"5", "4", "3", "2", "1"}
|
||||
if sort == "title" {
|
||||
want = []string{"2", "5", "1", "3", "4"}
|
||||
}
|
||||
if !reflect.DeepEqual(seen, want) {
|
||||
t.Fatalf("%s: %v", sort, seen)
|
||||
}
|
||||
f.Cursor = cursors[1]
|
||||
f.Query = "different"
|
||||
if _, err := r.List(ctx, f); err == nil {
|
||||
t.Fatal("cursor reused across filters")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostgresEncodedTitleCursors(t *testing.T) {
|
||||
legacy, ctx := integrationRepository(t)
|
||||
r, err := NewPostgresRepository(legacy.db, "research_encoded_fixture")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = r.Check(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f := Filters{Sort: "title", PageSize: 1}
|
||||
seen := []string{}
|
||||
for {
|
||||
page, err := r.List(ctx, f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(page.Items) != 1 {
|
||||
t.Fatalf("invalid encoded title page %+v", page)
|
||||
}
|
||||
seen = append(seen, page.Items[0].ID)
|
||||
if len(seen) > 5 {
|
||||
t.Fatal("cursor cycle")
|
||||
}
|
||||
if page.NextCursor == "" {
|
||||
break
|
||||
}
|
||||
f.Cursor = page.NextCursor
|
||||
}
|
||||
if !reflect.DeepEqual(seen, []string{"2", "5", "1", "3", "4"}) {
|
||||
t.Fatalf("encoded key paging %v", seen)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostgresBoundedProbeDoesNotTruncateSparseMatches(t *testing.T) {
|
||||
legacy, ctx := integrationRepository(t)
|
||||
r, err := NewPostgresRepository(legacy.db, "research_sparse_fixture")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = r.Check(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, size := range []int{1, 2, 3} {
|
||||
f := Filters{Query: "graph", PageSize: size}
|
||||
seen := []string{}
|
||||
var previous []string
|
||||
for {
|
||||
page, err := r.List(ctx, f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ids := paperIDs(page.Items)
|
||||
if len(ids) == 0 || len(ids) > size {
|
||||
t.Fatalf("invalid sparse page %+v", page)
|
||||
}
|
||||
if previous != nil {
|
||||
back := f
|
||||
back.Cursor = page.PreviousCursor
|
||||
prior, err := r.List(ctx, back)
|
||||
if err != nil || !reflect.DeepEqual(paperIDs(prior.Items), previous) {
|
||||
t.Fatalf("reverse gap %+v %v", prior, err)
|
||||
}
|
||||
}
|
||||
seen = append(seen, ids...)
|
||||
if len(seen) > 5 {
|
||||
t.Fatal("cursor cycle")
|
||||
}
|
||||
previous = ids
|
||||
if page.NextCursor == "" {
|
||||
break
|
||||
}
|
||||
f.Cursor = page.NextCursor
|
||||
}
|
||||
if !reflect.DeepEqual(seen, []string{"10001", "10000", "8000", "3", "1"}) {
|
||||
t.Fatalf("size=%d lost sparse matches %v", size, seen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostgresTopicCursorSkipsDuplicateEvidence(t *testing.T) {
|
||||
legacy, ctx := integrationRepository(t)
|
||||
r, err := NewPostgresRepository(legacy.db, "research_sparse_fixture")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f := Filters{Topic: "cs.LG", PageSize: 1}
|
||||
seen := []string{}
|
||||
for {
|
||||
page, err := r.List(ctx, f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(page.Items) != 1 {
|
||||
t.Fatalf("invalid topic page %+v", page)
|
||||
}
|
||||
seen = append(seen, page.Items[0].ID)
|
||||
if len(seen) > 3 {
|
||||
t.Fatal("cursor cycle")
|
||||
}
|
||||
if len(seen) > 1 {
|
||||
back := f
|
||||
back.Cursor = page.PreviousCursor
|
||||
prior, err := r.List(ctx, back)
|
||||
if err != nil || len(prior.Items) != 1 || prior.Items[0].ID != seen[len(seen)-2] {
|
||||
t.Fatalf("reverse topic %+v %v", prior, err)
|
||||
}
|
||||
}
|
||||
if page.NextCursor == "" {
|
||||
break
|
||||
}
|
||||
f.Cursor = page.NextCursor
|
||||
}
|
||||
if !reflect.DeepEqual(seen, []string{"10001", "10000", "1"}) {
|
||||
t.Fatalf("duplicate evidence or lost topic matches %v", seen)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user