Skip to content
quarry Artifact [a1e0dd7909]
Login

Artifact [a1e0dd7909]

Artifact a1e0dd790980a5594a792fd8a2b63079c131a3a9d5269ae3f207422282f393ed:


package index

import (
	"sort"
	"strings"
	"testing"
)

// #263: a tip move rewrites only the files that changed. Whatever it
// skips, the rows and the full-text index read exactly as if the tree
// had been written from scratch: an unchanged file is found at the new
// tip, a changed one by its new words and not its old, a new one is
// found, a removed one is not, and the FTS index is consistent.
func TestATreeMoveRewritesOnlyWhatChanged(t *testing.T) {
	ix := openIndex(t)
	if err := ix.ReplaceTree("v1", []TreeText{
		{Path: "keep.go", Body: "steady alpaca"},
		{Path: "edit.go", Body: "old wombat"},
		{Path: "gone.go", Body: "doomed narwhal"},
		{Path: "README.md", Body: "intro", Doc: true},
	}); err != nil {
		t.Fatal(err)
	}
	if err := ix.ReplaceTree("v2", []TreeText{
		{Path: "keep.go", Body: "steady alpaca"},
		{Path: "edit.go", Body: "new quokka"},
		{Path: "added.go", Body: "fresh okapi"},
		{Path: "README.md", Body: "intro", Doc: true},
	}); err != nil {
		t.Fatal(err)
	}
	refs := func(q string) []string {
		t.Helper()
		rs, err := ix.Search(q, "", 20)
		if err != nil {
			t.Fatal(err)
		}
		var out []string
		for _, r := range rs {
			out = append(out, r.Ref)
		}
		sort.Strings(out)
		return out
	}
	for q, want := range map[string]string{
		"alpaca": "v2/keep.go", "quokka": "v2/edit.go", "okapi": "v2/added.go", "intro": "v2/README.md",
		"wombat": "", "narwhal": "",
	} {
		got := strings.Join(refs(q), " ")
		if got != want {
			t.Errorf("FAIL: %q finds %q, want %q", q, got, want)
		}
	}
	if _, err := ix.db.Exec(`INSERT INTO search_fts (search_fts) VALUES ('integrity-check')`); err != nil {
		t.Errorf("FAIL: the full-text index disagrees with its rows: %v", err)
	}
	var trig string
	ix.db.QueryRow(`SELECT sql FROM sqlite_master WHERE name = 'search_doc_au'`).Scan(&trig)
	if !strings.Contains(trig, "UPDATE OF text") {
		t.Errorf("FAIL: the update trigger still fires on any column: %s", trig)
	}
}

// An index made before the narrowed trigger has it replaced on open.
func TestAnOldSearchUpdateTriggerIsReplacedOnOpen(t *testing.T) {
	path := t.TempDir() + "/index.db"
	ix, err := Open(path)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := ix.w.Exec(`DROP TRIGGER search_doc_au;
		CREATE TRIGGER search_doc_au AFTER UPDATE ON search_doc BEGIN
			INSERT INTO search_fts (search_fts, rowid, text) VALUES ('delete', old.rowid, old.text);
			INSERT INTO search_fts (rowid, text) VALUES (new.rowid, new.text);
		END;`); err != nil {
		t.Fatal(err)
	}
	ix.Close()
	ix, err = Open(path)
	if err != nil {
		t.Fatal(err)
	}
	defer ix.Close()
	var trig string
	ix.db.QueryRow(`SELECT sql FROM sqlite_master WHERE name = 'search_doc_au'`).Scan(&trig)
	if !strings.Contains(trig, "UPDATE OF text") {
		t.Errorf("FAIL: reopening kept the old trigger: %s", trig)
	}
}