Skip to content
quarry Artifact [838934026d]
Login

Artifact [838934026d]

Artifact 838934026d5bd385ef0679d99f2d8cf5145a41e80844972f968b4a7d3d046af9:


package index

import (
	"fmt"
	"slices"
	"strings"
	"testing"
)

// A chip that stands for several kinds ("l" is a landing and its
// comments, "code" is check-ins, landings and technotes) must walk the
// kind index in date order and stop at the page, as a single kind does.
// As one IN list it read every matching row and sorted them in a temp
// b-tree: 83ms on tcl for a page of sixty, over a second across the
// dashboard's repositories.
func TestCompositeKindTimelineDoesNotSort(t *testing.T) {
	ix := openIndex(t)
	for _, f := range []TimelineFilter{
		{Limit: 60, Kind: "code"},
		{Limit: 60, Kind: "l"},
		{Limit: 60, Kind: "code", Before: "2026-01-01T00:00:00~abc"},
		{Limit: 60, Kind: "code", User: "dan"},
	} {
		q, args, ok, err := ix.timelineQuery(f)
		if err != nil || !ok {
			t.Fatalf("%+v: ok=%v err=%v", f, ok, err)
		}
		plan := queryPlan(t, ix, q, args...)
		if strings.Contains(plan, "TEMP B-TREE") {
			t.Errorf("%+v sorts every matching event in a temp b-tree:\n%s", f, plan)
		}
		if !strings.Contains(plan, "event_by_kind") {
			t.Errorf("%+v does not walk event_by_kind:\n%s", f, plan)
		}
	}
}

// Whatever shape the query takes, a composite chip's pages are its
// kinds merged in the timeline's one total order, (date, hash)
// descending, and paging by cursor visits each event once.
func TestCompositeKindTimelineIsItsKindsMerged(t *testing.T) {
	ix := openIndex(t)
	kinds := []string{"ci", "l", "lc", "e", "t", "w", "f"}
	type row struct{ hash, kind, date, user string }
	var rows []row
	for i := 0; i < 70; i++ {
		// Every third pair shares a timestamp, so the hash tiebreak is
		// exercised across kinds.
		r := row{
			hash: fmt.Sprintf("h%03d", i),
			kind: kinds[i%len(kinds)],
			date: fmt.Sprintf("2026-01-%02dT00:00:%02d", 1+i/6, (i/2)%60),
			user: []string{"dan", "ann"}[i%2],
		}
		rows = append(rows, r)
		if _, err := ix.w.Exec(`INSERT INTO event (hash, kind, ref, date, user, comment) VALUES (?, ?, ?, ?, ?, '')`,
			r.hash, r.kind, r.hash, r.date, r.user); err != nil {
			t.Fatal(err)
		}
	}
	want := func(set []string, user string) []string {
		var got []row
		for _, r := range rows {
			if slices.Contains(set, r.kind) && (user == "" || r.user == user) {
				got = append(got, r)
			}
		}
		slices.SortFunc(got, func(a, b row) int {
			if c := strings.Compare(b.date, a.date); c != 0 {
				return c
			}
			return strings.Compare(b.hash, a.hash)
		})
		var out []string
		for _, r := range got {
			out = append(out, r.hash)
		}
		return out
	}
	for _, c := range []struct {
		kind, user string
		set        []string
	}{
		{"code", "", []string{"ci", "l", "lc", "e"}},
		{"code", "dan", []string{"ci", "l", "lc", "e"}},
		{"l", "", []string{"l", "lc"}},
	} {
		var got []string
		before := ""
		for page := 0; page < 20; page++ {
			entries, more, err := ix.Timeline(TimelineFilter{Limit: 4, Kind: c.kind, User: c.user, Before: before})
			if err != nil {
				t.Fatal(err)
			}
			for _, e := range entries {
				got = append(got, e.Hash)
			}
			if !more {
				break
			}
			last := entries[len(entries)-1]
			before = last.Date + "~" + last.Hash
		}
		if exp := want(c.set, c.user); !slices.Equal(got, exp) {
			t.Errorf("kind=%s user=%q paged\n got %v\nwant %v", c.kind, c.user, got, exp)
		}
	}
}