Skip to content

Commit 1b8ea34

Browse files
Enable FTS5 Porter stemmer for skill search (#49)
* Add lightweight database migration system (#44) Replace inline schema constant in DatabaseInitializer with numbered SQL migration files and a SchemaMigrator that tracks applied versions in a schema_version table. Existing databases are handled safely via IF NOT EXISTS clauses in migration 001. * Enable FTS5 Porter stemmer for skill search (#43) Add migration 002 to rebuild the FTS5 virtual table with tokenize='porter unicode61' so inflected search terms like "closed deals" and "closing" match skills containing "close deal".
1 parent aa691fe commit 1b8ea34

2 files changed

Lines changed: 78 additions & 0 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
-- 002_fts5_porter_stemmer.sql: Enable Porter stemmer for better search matching
2+
3+
-- Drop existing triggers before dropping the table
4+
DROP TRIGGER IF EXISTS trg_skills_fts_insert;
5+
DROP TRIGGER IF EXISTS trg_skills_fts_delete;
6+
7+
-- Recreate FTS table with porter stemmer tokenizer
8+
DROP TABLE IF EXISTS skills_fts;
9+
10+
CREATE VIRTUAL TABLE skills_fts USING fts5(
11+
name,
12+
description,
13+
category,
14+
tokenize='porter unicode61'
15+
);
16+
17+
-- Recreate triggers to keep FTS in sync
18+
CREATE TRIGGER trg_skills_fts_insert
19+
AFTER INSERT ON skill_versions
20+
WHEN NEW.is_latest = 1
21+
BEGIN
22+
DELETE FROM skills_fts WHERE rowid = NEW.skill_id;
23+
INSERT INTO skills_fts(rowid, name, description, category)
24+
SELECT NEW.skill_id, s.name, NEW.description, COALESCE(NEW.category, '')
25+
FROM skills s WHERE s.id = NEW.skill_id;
26+
END;
27+
28+
CREATE TRIGGER trg_skills_fts_delete
29+
AFTER DELETE ON skill_versions
30+
BEGIN
31+
DELETE FROM skills_fts WHERE rowid = OLD.skill_id;
32+
INSERT INTO skills_fts(rowid, name, description, category)
33+
SELECT s.id, s.name, sv.description, COALESCE(sv.category, '')
34+
FROM skills s
35+
JOIN skill_versions sv ON sv.skill_id = s.id AND sv.is_latest = 1
36+
WHERE s.id = OLD.skill_id;
37+
END;
38+
39+
-- Repopulate FTS index from existing data
40+
INSERT INTO skills_fts(rowid, name, description, category)
41+
SELECT s.id, s.name, sv.description, COALESCE(sv.category, '')
42+
FROM skills s
43+
JOIN skill_versions sv ON sv.skill_id = s.id AND sv.is_latest = 1;

tests/SkillServer.Integration.Tests/SkillServerIntegrationTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,4 +571,39 @@ public async Task UploadSkill_DifferentVersions_AreAllowed()
571571
var versions = await _fixture.Client.GetSkillVersionsAsync(skillName, ct);
572572
Assert.Equal(2, versions.Count);
573573
}
574+
575+
[Fact]
576+
public async Task SearchSkills_WithPorterStemming_MatchesStemmedTerms()
577+
{
578+
var ct = TestContext.Current.CancellationToken;
579+
var prefix = $"stem-{Guid.NewGuid():N}"[..10];
580+
var skillName = $"{prefix}-closer";
581+
582+
var skillContent = $"""
583+
---
584+
name: {skillName}
585+
description: Helps sales reps close deal opportunities faster
586+
---
587+
588+
# Stemming Test
589+
""";
590+
591+
using var content = new MultipartFormDataContent();
592+
content.Add(new StringContent(skillName), "name");
593+
content.Add(new StringContent("1.0.0"), "version");
594+
595+
var fileContent = new ByteArrayContent(Encoding.UTF8.GetBytes(skillContent));
596+
fileContent.Headers.ContentType = new MediaTypeHeaderValue("text/markdown");
597+
content.Add(fileContent, "file", "SKILL.md");
598+
599+
await _fixture.AuthenticatedHttpClient.PostAsync("/skills", content, ct);
600+
601+
// "closed deals" should match "close deal" via porter stemming
602+
var results = await _fixture.Client.SearchSkillsAsync("closed deals", ct: ct);
603+
Assert.Contains(results, s => s.Name == skillName);
604+
605+
// "closing" should match "close" via stemming
606+
results = await _fixture.Client.SearchSkillsAsync("closing", ct: ct);
607+
Assert.Contains(results, s => s.Name == skillName);
608+
}
574609
}

0 commit comments

Comments
 (0)