Q193: search re-indexes only what changed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6c0e3f4464
commit
3625ca4f4f
5 files changed
+186
-60
No files matched your search
@@ -35,7 +35,7 @@ describe("domain services", () => {
|
||||
const connection = createConnection();
|
||||
|
||||
const rows = connection.db.prepare("select version from schema_migrations order by version asc").all() as Array<{ version: number }>;
|
||||
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106]);
|
||||
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107]);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createHash } from "node:crypto";
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import { DatabaseConnection } from "../../src/main/database/connection";
|
||||
import { ChatService } from "../../src/main/services/chat-service";
|
||||
import { OrganizationService } from "../../src/main/services/organization-service";
|
||||
@@ -276,4 +276,58 @@ describe("search service", () => {
|
||||
expect(blocked[0]!.preview).not.toContain("PRIVATE KEY");
|
||||
connection.close();
|
||||
});
|
||||
|
||||
test("indexes a company once, then redoes only what changed", () => {
|
||||
const connection = createConnection();
|
||||
const db = connection.db;
|
||||
const organization = new OrganizationService(db);
|
||||
const search = new SearchService(db);
|
||||
let orgId = "";
|
||||
let taskId = "";
|
||||
connection.transaction(() => {
|
||||
orgId = organization.createOrganization({ organizationName: "Index Co", employeeName: "Dana Lee", mission: "m", goal: "g" });
|
||||
taskId = organization.createTask({
|
||||
orgId, title: "Osprey survey", description: "d", status: TASK_STATUS.BACKLOG, assigneeAgentId: null, risk: "low",
|
||||
projectId: null, goalId: null, workspaceId: null, priority: "normal", requiresFilesystem: false,
|
||||
});
|
||||
});
|
||||
const employeeId = (db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
|
||||
const titles = (query: string) => search.search({ orgId, employeeId, query }).results.map((hit) => hit.title);
|
||||
const indexRows = () => db.prepare("select rowid from search_documents where org_id = ? order by rowid").all(orgId);
|
||||
expect(titles("osprey")).toEqual(["Osprey survey"]);
|
||||
expect(db.prepare("select org_id from search_built").all()).toEqual([{ org_id: orgId }]);
|
||||
// Nothing changed, nothing is rewritten.
|
||||
const before = indexRows();
|
||||
expect(titles("osprey")).toEqual(["Osprey survey"]);
|
||||
expect(indexRows()).toEqual(before);
|
||||
// Edits, additions and removals are queued and applied on the next search.
|
||||
db.prepare("update tasks set title = 'Heron survey' where id = ?").run(taskId);
|
||||
connection.transaction(() => organization.createTask({
|
||||
orgId, title: "Kestrel count", description: "d", status: TASK_STATUS.BACKLOG, assigneeAgentId: null, risk: "low",
|
||||
projectId: null, goalId: null, workspaceId: null, priority: "normal", requiresFilesystem: false,
|
||||
}));
|
||||
expect(db.prepare("select count(*) as n from search_pending where org_id = ?").get(orgId)).toEqual({ n: 2 });
|
||||
expect(titles("osprey")).toEqual([]);
|
||||
expect(titles("heron")).toEqual(["Heron survey"]);
|
||||
expect(titles("kestrel")).toEqual(["Kestrel count"]);
|
||||
expect(db.prepare("select count(*) as n from search_pending where org_id = ?").get(orgId)).toEqual({ n: 0 });
|
||||
// A knowledge entry's state is read against the clock, not frozen at indexing.
|
||||
new KnowledgeService(db).create({
|
||||
orgId, topic: "Ferry timetable", category: "policy", content: "Ferries leave hourly.", importance: "normal",
|
||||
sourceType: "employee", sourceId: null, scopeType: "organization", scopeId: null, validUntil: "2030-01-01T00:00:00Z", supersedesId: null,
|
||||
});
|
||||
expect(search.search({ orgId, employeeId, query: "ferry", entityTypes: ["knowledge"] }).results[0]!.facets.state).toBe("active");
|
||||
vi.setSystemTime(new Date("2031-01-01T00:00:00Z"));
|
||||
try {
|
||||
expect(search.search({ orgId, employeeId, query: "ferry", entityTypes: ["knowledge"] }).results[0]!.facets.state).toBe("expired");
|
||||
expect(db.prepare("select count(*) as n from search_pending where org_id = ?").get(orgId)).toEqual({ n: 0 });
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
// A deleted company leaves no index behind.
|
||||
connection.transaction(() => organization.deleteOrganization(orgId));
|
||||
expect(db.prepare("select count(*) as n from search_documents where org_id = ?").get(orgId)).toEqual({ n: 0 });
|
||||
expect(db.prepare("select count(*) as n from search_built where org_id = ?").get(orgId)).toEqual({ n: 0 });
|
||||
connection.close();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user