test(store): doc-side expansion Task 2 리뷰 보강 (M1/M2/N1)
- M1: chunk_aliases trigger 가드에 AND aliases <> '' (빈 문자열 미색인) - M2: 재색인 멱등 테스트 (재-put 후 별칭 행 1개) - N1: 본문 격리 음성 단언 (별칭 term 이 chunks_fts 로 누출 안 됨) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -161,3 +161,60 @@ fn none_aliases_not_indexed() {
|
|||||||
"aliases=None 이면 chunk_aliases_fts 에 행이 없어야 한다"
|
"aliases=None 이면 chunk_aliases_fts 에 행이 없어야 한다"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Task 2 리뷰 M2: 같은 doc 을 두 번 `put_chunks` 해도 `chunk_aliases_fts`
|
||||||
|
/// 행이 중복되지 않아야 한다. put_chunks 의 DELETE-then-INSERT 가
|
||||||
|
/// chunk_aliases_ad → chunk_aliases_ai 를 발화해 멱등 재동기화하는지 검증.
|
||||||
|
#[test]
|
||||||
|
fn reput_keeps_single_alias_row() {
|
||||||
|
let env = common::TestEnv::new();
|
||||||
|
let store = open_store_with_document(&env);
|
||||||
|
let doc = DocumentId("d".repeat(32));
|
||||||
|
let mk = || base_chunk(&"e".repeat(32), &doc, Some("메모리 안전성".into()));
|
||||||
|
|
||||||
|
store.put_chunks(&doc, &[mk()]).unwrap();
|
||||||
|
store.put_chunks(&doc, &[mk()]).unwrap(); // 같은 doc 재-put
|
||||||
|
|
||||||
|
let n: i64 = env.with_conn(|c| {
|
||||||
|
c.query_row("SELECT count(*) FROM chunk_aliases_fts", [], |r| r.get(0))
|
||||||
|
});
|
||||||
|
assert_eq!(n, 1, "재색인 후에도 별칭 행은 1개여야 한다 (중복/누락 없음)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Task 2 리뷰 N1: 별칭 term 이 본문 `chunks_fts` 로 새지 않아야 한다(§3.3 격리).
|
||||||
|
/// 본문엔 없고 별칭에만 있는 한국어 term 으로 chunks_fts 를 MATCH 하면 0행.
|
||||||
|
#[test]
|
||||||
|
fn aliases_dont_leak_into_body_fts() {
|
||||||
|
let env = common::TestEnv::new();
|
||||||
|
let store = open_store_with_document(&env);
|
||||||
|
let doc = DocumentId("d".repeat(32));
|
||||||
|
// 본문 "Rust ownership and borrowing" 에 "메모리" 없음, 별칭에만 있음.
|
||||||
|
let chunk = base_chunk(&"e".repeat(32), &doc, Some("메모리 안전성".into()));
|
||||||
|
store.put_chunks(&doc, &[chunk]).unwrap();
|
||||||
|
|
||||||
|
let body_hits: i64 = env.with_conn(|c| {
|
||||||
|
c.query_row(
|
||||||
|
"SELECT count(*) FROM chunks_fts WHERE chunks_fts MATCH 'text : (\"메모리\")'",
|
||||||
|
[],
|
||||||
|
|r| r.get(0),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
assert_eq!(body_hits, 0, "별칭 term 이 본문 chunks_fts 로 누출되면 안 된다");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Task 2 리뷰 M1: 빈 문자열 별칭은 색인하지 않는다(trigger 가드
|
||||||
|
/// `AND new.aliases <> ''`). producer 가 Some("") 를 넘겨도 무용한 행이
|
||||||
|
/// chunk_aliases_fts 에 쌓이지 않아야 한다.
|
||||||
|
#[test]
|
||||||
|
fn empty_string_alias_not_indexed() {
|
||||||
|
let env = common::TestEnv::new();
|
||||||
|
let store = open_store_with_document(&env);
|
||||||
|
let doc = DocumentId("d".repeat(32));
|
||||||
|
let chunk = base_chunk(&"e".repeat(32), &doc, Some(String::new()));
|
||||||
|
store.put_chunks(&doc, &[chunk]).unwrap();
|
||||||
|
|
||||||
|
let n: i64 = env.with_conn(|c| {
|
||||||
|
c.query_row("SELECT count(*) FROM chunk_aliases_fts", [], |r| r.get(0))
|
||||||
|
});
|
||||||
|
assert_eq!(n, 0, "빈 문자열 별칭은 chunk_aliases_fts 에 색인되면 안 된다");
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ CREATE VIRTUAL TABLE chunk_aliases_fts USING fts5(
|
|||||||
tokenize = 'unicode61'
|
tokenize = 'unicode61'
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TRIGGER chunk_aliases_ai AFTER INSERT ON chunks WHEN new.aliases IS NOT NULL BEGIN
|
-- 가드 `IS NOT NULL AND <> ''`: producer 가 Some("") 를 넘겨도 내용 없는
|
||||||
|
-- 행이 chunk_aliases_fts 에 쌓이지 않게 한다(Task 2 리뷰 M1).
|
||||||
|
CREATE TRIGGER chunk_aliases_ai AFTER INSERT ON chunks
|
||||||
|
WHEN new.aliases IS NOT NULL AND new.aliases <> '' BEGIN
|
||||||
INSERT INTO chunk_aliases_fts(chunk_id, doc_id, aliases)
|
INSERT INTO chunk_aliases_fts(chunk_id, doc_id, aliases)
|
||||||
VALUES (new.chunk_id, new.doc_id, new.aliases);
|
VALUES (new.chunk_id, new.doc_id, new.aliases);
|
||||||
END;
|
END;
|
||||||
@@ -26,7 +29,8 @@ END;
|
|||||||
CREATE TRIGGER chunk_aliases_au AFTER UPDATE ON chunks BEGIN
|
CREATE TRIGGER chunk_aliases_au AFTER UPDATE ON chunks BEGIN
|
||||||
DELETE FROM chunk_aliases_fts WHERE chunk_id = old.chunk_id;
|
DELETE FROM chunk_aliases_fts WHERE chunk_id = old.chunk_id;
|
||||||
INSERT INTO chunk_aliases_fts(chunk_id, doc_id, aliases)
|
INSERT INTO chunk_aliases_fts(chunk_id, doc_id, aliases)
|
||||||
SELECT new.chunk_id, new.doc_id, new.aliases WHERE new.aliases IS NOT NULL;
|
SELECT new.chunk_id, new.doc_id, new.aliases
|
||||||
|
WHERE new.aliases IS NOT NULL AND new.aliases <> '';
|
||||||
END;
|
END;
|
||||||
|
|
||||||
-- in-process LRU search cache 무효화 (V009 와 동일 패턴).
|
-- in-process LRU search cache 무효화 (V009 와 동일 패턴).
|
||||||
|
|||||||
Reference in New Issue
Block a user