Commit84d8a3dcRecorded24 Oct 2026Repositoryfolio
Cover metadata-only search and literal FTS replacement compatibility
Changed
test/test-fts-replacement.sgl | 47 +++++++++++++++++++++++++++++++++++++++++++++++
test/test-search-projection.sgl | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 95 insertions(+)Diff
test/test-fts-replacement.sgladded
@@ -0,0 +1,47 @@
+1
;;; FTS replacement must remove every literal-name row, including legacy rows.+2
(import (sigil test)+3
(sigil fs)+4
(sigil path)+5
(sigil sqlite)+6
(folio store)+7
(folio index))+8
+9
(test-group "literal FTS replacement"+10
(test "updates and removal preserve unrelated names, arbitrary rowids and tokenless names"+11
(call-with-temp-directory+12
(lambda (root)+13
(let* ((store (make-folio-store root))+14
(idx (make-folio-index store))+15
(db (folio-index-db idx)))+16
(for-each+17
(lambda (name)+18
(let* ((path (path-join (store-notes-dir store)+19
(string-append name ".md")))+20
(sibling (string-append "guard-" name)))+21
(write-file-string path "# Before\nbeforeword\n")+22
(index-note! idx path)+23
;; Standalone FTS rowids are unrelated to notes_meta rowids.+24
;; Old/concurrent versions can leave more than one FTS row.+25
(assert-true+26
(sqlite-run db+27
"INSERT INTO notes_fts (name, title, tags, content) VALUES (?, ?, '', 'staleword')"+28
name name))+29
(assert-true+30
(sqlite-run db+31
"INSERT INTO notes_fts (name, title, tags, content) VALUES (?, ?, '', 'guardword')"+32
sibling name))+33
(write-file-string path "# After\nafterword\n")+34
(index-note! idx path)+35
(let ((rows (sqlite-query db+36
"SELECT content FROM notes_fts WHERE name = ?" name)))+37
(assert-equal 1 (length rows))+38
(assert-equal "# After\nafterword\n" (cdr (assoc 'content (car rows)))))+39
(assert-equal 1+40
(length (sqlite-query db "SELECT rowid FROM notes_fts WHERE name = ?" sibling)))+41
(remove-note-from-index! idx name)+42
(assert-equal '()+43
(sqlite-query db "SELECT rowid FROM notes_fts WHERE name = ?" name))+44
(assert-equal 1+45
(length (sqlite-query db "SELECT rowid FROM notes_fts WHERE name = ?" sibling)))))+46
'("normal" "a\"quoted" "中文" "---" "😀" "Case-Fold" "a.b" "with space"))+47
(sqlite-close db))))))test/test-search-projection.sgladded
@@ -0,0 +1,48 @@
+1
;;; Metadata-only searches retain ranking and filters without computing snippets.+2
(import (sigil test)+3
(sigil fs)+4
(sigil path)+5
(sigil sqlite)+6
(folio store)+7
(folio index))+8
+9
(define (without-snippets rows)+10
(map (lambda (row)+11
(filter (lambda (field) (not (eq? (car field) 'snippet))) row))+12
rows))+13
+14
(test-group "search projections"+15
(test "the default retains snippets; opting out preserves ranked metadata and filters"+16
(call-with-temp-directory+17
(lambda (root)+18
(let* ((store (make-folio-store root))+19
(dir (store-notes-dir store)))+20
(write-file-string (path-join dir "one.md")+21
"---\ntags: [alpha]\ncreated: 2025-01-01\nlast-updated: 2026-10-24\n---\n# One\nneedle needle needle λ\n")+22
(write-file-string (path-join dir "two.md")+23
"---\ntags: [beta]\ncreated: 2024-01-01\n---\n# Two\nneedle λ\n")+24
(let* ((idx (make-folio-index store))+25
(db (folio-index-db idx)))+26
(index-reconcile! idx)+27
(let ((ordinary (search-index idx "needle" #f #f)))+28
(assert-equal 2 (length ordinary))+29
(assert-true+30
(every (lambda (row) (string? (cdr (assoc 'snippet row)))) ordinary))+31
(assert-equal (without-snippets ordinary)+32
(search-index idx "needle" #f #f snippets?: #f)))+33
(for-each+34
(lambda (options)+35
(let* ((query (car options))+36
(tag (cadr options))+37
(since (caddr options))+38
(ordinary (search-index idx query tag since)))+39
(assert-equal 1 (length ordinary))+40
(assert-equal (without-snippets ordinary)+41
(search-index idx query tag since snippets?: #f))))+42
'(("needle" "alpha" #f)+43
("needle" #f "2026-01-01")+44
("λ" "beta" #f)))+45
(assert-equal (search-index idx #f #f #f)+46
(search-index idx #f #f #f snippets?: #f))+47
(assert-equal '() (search-index idx "missingword" #f #f snippets?: #f))+48
(sqlite-close db)))))))