|
3 | 3 | //! Per-collection analyzer configuration stored in backend metadata. |
4 | 4 | //! |
5 | 5 | //! Uses structural `(tid, collection, subkey)` meta blobs: |
6 | | -//! - `subkey = "analyzer"` → analyzer name (e.g. "german", "cjk_bigram") |
| 6 | +//! - `subkey = "analyzer"` → analyzer name (e.g. "german", "standard") |
7 | 7 | //! - `subkey = "language"` → lang code (e.g. "de", "ja") |
| 8 | +//! - `subkey = "fuzzy"` → `"1"` when the collection defaults to fuzzy matching |
8 | 9 | //! |
9 | 10 | //! Applied automatically at both index time and query time. |
10 | 11 |
|
@@ -49,6 +50,37 @@ impl<B: FtsBackend> FtsIndex<B> { |
49 | 50 | ) |
50 | 51 | } |
51 | 52 |
|
| 53 | + /// Set whether searches over this collection fall back to fuzzy |
| 54 | + /// (Levenshtein) matching by default. Persists to backend metadata. |
| 55 | + pub fn set_collection_fuzzy( |
| 56 | + &self, |
| 57 | + database_id: u64, |
| 58 | + tid: u64, |
| 59 | + collection: &str, |
| 60 | + fuzzy: bool, |
| 61 | + ) -> Result<(), B::Error> { |
| 62 | + self.backend.write_meta( |
| 63 | + database_id, |
| 64 | + tid, |
| 65 | + collection, |
| 66 | + "fuzzy", |
| 67 | + if fuzzy { b"1" } else { b"0" }, |
| 68 | + ) |
| 69 | + } |
| 70 | + |
| 71 | + /// Whether this collection defaults to fuzzy matching. `false` when unset. |
| 72 | + pub fn get_collection_fuzzy( |
| 73 | + &self, |
| 74 | + database_id: u64, |
| 75 | + tid: u64, |
| 76 | + collection: &str, |
| 77 | + ) -> Result<bool, B::Error> { |
| 78 | + Ok(self |
| 79 | + .backend |
| 80 | + .read_meta(database_id, tid, collection, "fuzzy")? |
| 81 | + .is_some_and(|bytes| bytes.as_slice() == b"1")) |
| 82 | + } |
| 83 | + |
52 | 84 | /// Get the configured analyzer name for a collection. |
53 | 85 | pub fn get_collection_analyzer( |
54 | 86 | &self, |
@@ -115,6 +147,20 @@ impl<B: FtsBackend> FtsIndex<B> { |
115 | 147 | } |
116 | 148 | } |
117 | 149 |
|
| 150 | +/// Whether `name` resolves to a real analyzer. |
| 151 | +/// |
| 152 | +/// [`resolve_analyzer`] falls back to the standard analyzer for anything it |
| 153 | +/// does not know, which silently turns a misspelled analyzer name into a |
| 154 | +/// different analyzer. Callers that accept a name from a user — DDL, config — |
| 155 | +/// gate on this first so the fallback is only ever reached for a name that |
| 156 | +/// was already checked. The two must stay in step: every arm below has a |
| 157 | +/// matching arm there. |
| 158 | +pub fn analyzer_exists(name: &str) -> bool { |
| 159 | + name == "standard" |
| 160 | + || LanguageAnalyzer::new(name).is_some() |
| 161 | + || NoStemAnalyzer::new(name).is_some() |
| 162 | +} |
| 163 | + |
118 | 164 | /// Resolve an analyzer name to a `Box<dyn TextAnalyzer>`. |
119 | 165 | fn resolve_analyzer(name: &str) -> Box<dyn TextAnalyzer> { |
120 | 166 | match name { |
|
0 commit comments