test(A4): korean + english trigram matching at FTS level
3개 신규 unit tests in tests/fts.rs §7: 1. fts_trigram_korean_3char_substring_hits — Codex sqlite 3.45.1 검증 동작 5개 assert pin: raw 3자 substring hit (충돌은/발생한), quoted phrase hit (\"해시 충돌\"/\"시 충\"), raw 해시충 0-hit (원문 미존재). 2. fts_trigram_korean_short_query_zero_hit_pinned — 2자 한국어 query (충돌·키) 0-hit 회귀 감지. trigram 구조 변경 시 먼저 fail. 3. fts_trigram_english_substring_hits — substring recall 동작 변경 pin (token→tokenizer, to 0-hit). 검증: cargo test -p kebab-store-sqlite --test fts → 13/13 PASS (신규 3 + 기존 10). Step 1c (multi-token 한국어 query e.g. \"해시 충돌\") 와 Step 5 (lexical BM25 snapshot 갱신) 는 Task A5 의 build_match_string() 재설계 후 진행. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -481,3 +481,115 @@ fn fts_store_drop_releases_wal_files() {
|
||||
.expect("main DB file should be removable after store drop");
|
||||
}
|
||||
}
|
||||
|
||||
// ── 7. Trigram tokenizer behavior (V007) — Korean + English ──────────
|
||||
|
||||
/// V007 의 trigram tokenizer 가 한국어 3자 이상 연속 substring 을
|
||||
/// 매칭하는지. Codex round 1/2 가 sqlite 3.45.1 로 검증한 동작을 pin:
|
||||
/// - raw query 가 3자 이상 공백 없는 substring 인 경우 hit.
|
||||
/// - raw query 가 공백을 포함하면 FTS5 가 토큰 경계로 분리 →
|
||||
/// 양 토큰이 3자 미만이면 0-hit.
|
||||
/// - quoted phrase ("..." 안에 공백 포함) 는 통째로 substring 매칭.
|
||||
#[test]
|
||||
fn fts_trigram_korean_3char_substring_hits() {
|
||||
let env = common::TestEnv::new();
|
||||
let store = SqliteStore::open(&env.config()).unwrap();
|
||||
store.run_migrations().unwrap();
|
||||
|
||||
let conn = raw_conn_no_fk(&env);
|
||||
insert_chunk(
|
||||
&conn,
|
||||
&"k".repeat(32),
|
||||
&"d".repeat(32),
|
||||
"[]",
|
||||
"해시 충돌은 키와 값을 매핑할 때 발생한다",
|
||||
);
|
||||
|
||||
// raw 3+ chars 공백 없는 연속 substring → hit.
|
||||
assert_eq!(
|
||||
count_match(&conn, "충돌은"),
|
||||
1,
|
||||
"raw 3-char 공백 없는 substring '충돌은' must hit"
|
||||
);
|
||||
assert_eq!(
|
||||
count_match(&conn, "발생한"),
|
||||
1,
|
||||
"raw 3-char 공백 없는 substring '발생한' must hit"
|
||||
);
|
||||
|
||||
// quoted phrase (공백 포함) → substring 매칭으로 hit.
|
||||
assert_eq!(
|
||||
count_match(&conn, "\"해시 충돌\""),
|
||||
1,
|
||||
"quoted whole phrase '해시 충돌' (5 chars including space)"
|
||||
);
|
||||
assert_eq!(
|
||||
count_match(&conn, "\"시 충\""),
|
||||
1,
|
||||
"quoted phrase '시 충' across the space boundary"
|
||||
);
|
||||
|
||||
// raw with no whitespace but substring not present in source → 0-hit.
|
||||
assert_eq!(
|
||||
count_match(&conn, "해시충"),
|
||||
0,
|
||||
"원문에 공백 없는 '해시충' trigram 이 없으므로 0-hit"
|
||||
);
|
||||
}
|
||||
|
||||
/// V007 trigram 의 핵심 제약: 3 Unicode chars 미만 query 는 색인 단위가
|
||||
/// 없어 항상 0-hit. design §3.4 + 사용자 결정 (lexical core 정상 0-hit,
|
||||
/// CLI/TUI wrapper 가 안내 메시지 출력). 회귀 감지 — trigram 구조 변경
|
||||
/// 또는 다른 tokenizer 도입 시 이 test 가 먼저 fail 한다.
|
||||
#[test]
|
||||
fn fts_trigram_korean_short_query_zero_hit_pinned() {
|
||||
let env = common::TestEnv::new();
|
||||
let store = SqliteStore::open(&env.config()).unwrap();
|
||||
store.run_migrations().unwrap();
|
||||
|
||||
let conn = raw_conn_no_fk(&env);
|
||||
insert_chunk(
|
||||
&conn,
|
||||
&"k".repeat(32),
|
||||
&"d".repeat(32),
|
||||
"[]",
|
||||
"해시 충돌은 키와 값을 매핑할 때 발생한다",
|
||||
);
|
||||
|
||||
// 2자 한국어 query — 도그푸딩에서 보고된 핵심 케이스 ('충돌'/'값').
|
||||
assert_eq!(count_match(&conn, "충돌"), 0, "2-char Korean query");
|
||||
// 1자 한국어 query.
|
||||
assert_eq!(count_match(&conn, "키"), 0, "1-char Korean query");
|
||||
}
|
||||
|
||||
/// V007 trigram 은 영어에도 substring 매칭으로 동작 — recall ↑, 단어
|
||||
/// 경계 정밀도 ↓. design §3.4 의 동작 변경을 명시적으로 핀.
|
||||
#[test]
|
||||
fn fts_trigram_english_substring_hits() {
|
||||
let env = common::TestEnv::new();
|
||||
let store = SqliteStore::open(&env.config()).unwrap();
|
||||
store.run_migrations().unwrap();
|
||||
|
||||
let conn = raw_conn_no_fk(&env);
|
||||
insert_chunk(
|
||||
&conn,
|
||||
&"e".repeat(32),
|
||||
&"d".repeat(32),
|
||||
"[]",
|
||||
"the tokenizer normalizes whitespace before matching",
|
||||
);
|
||||
|
||||
// trigram substring — 'token' hits inside 'tokenizer'.
|
||||
assert_eq!(
|
||||
count_match(&conn, "token"),
|
||||
1,
|
||||
"substring of 'tokenizer' — trigram recall"
|
||||
);
|
||||
assert_eq!(
|
||||
count_match(&conn, "izer"),
|
||||
1,
|
||||
"substring of 'tokenizer'"
|
||||
);
|
||||
// 3-char-minimum applies to English too.
|
||||
assert_eq!(count_match(&conn, "to"), 0, "2-char English query");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user