Skip to content
quarry Artifact [d5dd8ccde2]
Login

Artifact [d5dd8ccde2]

Artifact d5dd8ccde29235b73b2952ee0796829a167d73b670cc5d0e5dd16a070f25bc8f:


package index

import "testing"

// A prefix lookup must be an index SEEK, not a scan. `col LIKE ? || '%'`
// cannot use an index — the pattern is not a literal when the statement
// is prepared — so finding one check-in by its full hash read all 36,853
// rows of sqlite's tree, comments included. That was ten seconds on the
// first timeline render and forty milliseconds on every later one, which
// is exactly the shape that reads as cold I/O and is not.
func TestPrefixLookupUsesAnIndex(t *testing.T) {
	ix := openIndex(t)
	ci(t, ix, "abcdef0123456789", "trunk", "2026-01-01T10:00:00", "x")

	var plan string
	rows, err := ix.db.Query(`EXPLAIN QUERY PLAN
		SELECT hash FROM checkin WHERE hash >= ? AND hash < ?`, "abc", "abcg")
	if err != nil {
		t.Fatal(err)
	}
	defer rows.Close()
	for rows.Next() {
		var a, b, c int
		var detail string
		if err := rows.Scan(&a, &b, &c, &detail); err != nil {
			t.Fatal(err)
		}
		plan += detail + " "
	}
	if plan == "" {
		t.Fatal("no query plan")
	}
	if !contains(plan, "SEARCH") || contains(plan, "SCAN checkin") {
		t.Errorf("prefix lookup is not an index seek: %s", plan)
	}
}

func TestHexBoundRejectsNonHex(t *testing.T) {
	for _, ok := range []string{"abc", "0", "deadbeef", "0123456789abcdef"} {
		if _, good := hexBound(ok); !good {
			t.Errorf("hexBound(%q) refused a hex prefix", ok)
		}
	}
	// Anything that is not lowercase hex keeps the LIKE path, because
	// the 'g' terminator only bounds the hex alphabet.
	for _, bad := range []string{"", "ABC", "xyz", "abc-", "12 3"} {
		if _, good := hexBound(bad); good {
			t.Errorf("hexBound(%q) claimed a bound it cannot honour", bad)
		}
	}
	if hi, _ := hexBound("ff"); hi != "ffg" {
		t.Errorf("hexBound(ff) = %q, want ffg", hi)
	}
}

// The lookup still has to find things, by full hash and by prefix.
func TestCheckinByPrefixStillResolves(t *testing.T) {
	ix := openIndex(t)
	ci(t, ix, "abcdef0123456789", "trunk", "2026-01-01T10:00:00", "the one")
	ci(t, ix, "bbbbbb9999999999", "trunk", "2026-01-02T10:00:00", "another")
	for _, p := range []string{"abcdef0123456789", "abcdef", "abc"} {
		got, _, ok, err := ix.CheckinByPrefix(p)
		if err != nil || !ok {
			t.Fatalf("prefix %q: ok=%v err=%v", p, ok, err)
		}
		if got.Comment != "the one" {
			t.Errorf("prefix %q resolved to %q", p, got.Comment)
		}
	}
	if _, _, ok, _ := ix.CheckinByPrefix("ffff"); ok {
		t.Error("a prefix matching nothing resolved anyway")
	}
}

func contains(h, n string) bool {
	for i := 0; i+len(n) <= len(h); i++ {
		if h[i:i+len(n)] == n {
			return true
		}
	}
	return false
}