linkrs_sync/manager/
accessors.rs1use super::*;
3#[cfg(feature = "fulltext")]
4use crate::coordinator::SyncCoordinator;
5#[cfg(any(feature = "fulltext", feature = "vector"))]
6use crate::sqlite_outbox::SqliteOutbox;
7#[cfg(feature = "vector")]
8use crate::vector_sync::VectorSyncCoordinator;
9#[cfg(any(feature = "fulltext", feature = "vector"))]
10use linkrs_metrics::StatsManager;
11impl super::SyncManager {
12 #[cfg(any(feature = "fulltext", feature = "vector"))]
17 pub(crate) fn rebuild_lock_for(
18 &self,
19 target: &str,
20 space_id: u64,
21 tag_name: &str,
22 field_name: &str,
23 ) -> Arc<tokio::sync::Mutex<()>> {
24 let key = format!("{target}:{space_id}:{tag_name}:{field_name}");
25 self.rebuild_locks
26 .entry(key)
27 .or_insert_with(|| Arc::new(tokio::sync::Mutex::new(())))
28 .clone()
29 }
30
31 #[cfg(feature = "fulltext")]
32 pub fn sync_coordinator(&self) -> &Arc<SyncCoordinator> {
33 self.sync_coordinator
34 .as_ref()
35 .expect("SyncCoordinator not available without fulltext feature")
36 }
37
38 #[cfg(feature = "fulltext")]
39 pub(crate) fn sync_coordinator_opt(&self) -> Option<&Arc<SyncCoordinator>> {
40 self.sync_coordinator.as_ref()
41 }
42
43 #[cfg(any(feature = "fulltext", feature = "vector"))]
44 pub(crate) fn sqlite_outbox_opt(&self) -> Option<&SqliteOutbox> {
45 self.sqlite_outbox.as_deref()
46 }
47
48 #[cfg(feature = "vector")]
49 pub fn vector_coordinator(&self) -> Option<&Arc<VectorSyncCoordinator>> {
50 self.vector_coordinator.as_ref()
51 }
52
53 #[cfg(feature = "fulltext")]
54 pub fn fulltext_manager(&self) -> Arc<linkrs_fulltext::manager::FulltextIndexManager> {
55 self.sync_coordinator
56 .as_ref()
57 .expect("SyncCoordinator not available without fulltext feature")
58 .fulltext_manager()
59 .clone()
60 }
61
62 #[cfg(feature = "fulltext")]
66 pub fn fulltext_manager_opt(
67 &self,
68 ) -> Option<Arc<linkrs_fulltext::manager::FulltextIndexManager>> {
69 self.sync_coordinator
70 .as_ref()
71 .map(|coordinator| coordinator.fulltext_manager().clone())
72 }
73
74 #[cfg(any(feature = "fulltext", feature = "vector"))]
75 pub(crate) fn stats_manager_opt(&self) -> Option<Arc<StatsManager>> {
76 self.stats_manager.clone()
77 }
78
79 #[cfg(feature = "fulltext")]
83 pub fn inconsistent_fulltext_indexes(&self) -> Vec<linkrs_fulltext::IndexMetadata> {
84 self.fulltext_manager_opt()
85 .map(|manager| manager.get_inconsistent_indexes())
86 .unwrap_or_default()
87 }
88
89 #[cfg(feature = "vector")]
93 pub fn inconsistent_vector_indexes(&self) -> Vec<linkrs_core::index_events::RebuildProgress> {
94 self.vector_coordinator
95 .as_ref()
96 .map(|coordinator| coordinator.inconsistent_vector_indexes())
97 .unwrap_or_default()
98 }
99
100 #[cfg(any(feature = "fulltext", feature = "vector"))]
102 pub fn has_inconsistent_indexes(&self) -> bool {
103 #[cfg(feature = "fulltext")]
104 if !self.inconsistent_fulltext_indexes().is_empty() {
105 return true;
106 }
107 #[cfg(feature = "vector")]
108 if !self.inconsistent_vector_indexes().is_empty() {
109 return true;
110 }
111 false
112 }
113
114 #[cfg(any(feature = "fulltext", feature = "vector"))]
122 pub fn rebuild_progress(
123 &self,
124 space_id: u64,
125 tag_name: &str,
126 field_name: &str,
127 ) -> Option<linkrs_core::index_events::RebuildProgress> {
128 #[cfg(feature = "fulltext")]
129 if let Some(manager) = self.fulltext_manager_opt() {
130 if let Some(progress) = manager.rebuild_progress(space_id, tag_name, field_name) {
131 return Some(progress);
132 }
133 }
134 #[cfg(feature = "vector")]
135 if let Some(coordinator) = self.vector_coordinator.as_ref() {
136 if let Some(progress) = coordinator.rebuild_progress(space_id, tag_name, field_name) {
137 return Some(progress);
138 }
139 }
140 None
141 }
142
143 #[cfg(not(any(feature = "fulltext", feature = "vector")))]
145 pub fn rebuild_progress(
146 &self,
147 _space_id: u64,
148 _tag_name: &str,
149 _field_name: &str,
150 ) -> Option<linkrs_core::index_events::RebuildProgress> {
151 None
152 }
153
154 pub async fn recover_stale_rebuilds(&self) -> Result<usize, SyncError> {
159 let fulltext_failed = self.recover_stale_fulltext_rebuilds_tolerant().await?;
160 let vector_failed = self.recover_stale_vector_rebuilds_tolerant().await?;
161 Ok(fulltext_failed + vector_failed)
162 }
163
164 #[cfg(feature = "fulltext")]
165 async fn recover_stale_fulltext_rebuilds_tolerant(&self) -> Result<usize, SyncError> {
166 match self.recover_stale_fulltext_rebuilds().await {
167 Ok(count) => Ok(count),
168 Err(SyncError::PersistenceError(_)) => Ok(0),
169 Err(error) => Err(error),
170 }
171 }
172
173 #[cfg(not(feature = "fulltext"))]
174 async fn recover_stale_fulltext_rebuilds_tolerant(&self) -> Result<usize, SyncError> {
175 Ok(0)
176 }
177
178 #[cfg(feature = "vector")]
179 async fn recover_stale_vector_rebuilds_tolerant(&self) -> Result<usize, SyncError> {
180 match self.recover_stale_vector_rebuilds().await {
181 Ok(count) => Ok(count),
182 Err(SyncError::PersistenceError(_)) => Ok(0),
183 Err(error) => Err(error),
184 }
185 }
186
187 #[cfg(not(feature = "vector"))]
188 async fn recover_stale_vector_rebuilds_tolerant(&self) -> Result<usize, SyncError> {
189 Ok(0)
190 }
191
192 pub fn recover_stale_rebuilds_sync(&self) -> Result<usize, SyncError> {
195 self.execute_sync(|| async { self.recover_stale_rebuilds().await })
196 }
197
198 pub fn get_dead_letter_entries(&self) -> Vec<crate::DeadLetterEntry> {
199 if let Some(ref dlq) = self.dead_letter_queue {
200 dlq.get_all()
201 } else {
202 vec![]
203 }
204 }
205
206 pub fn get_unrecovered_entries(&self) -> Vec<crate::DeadLetterEntry> {
207 if let Some(ref dlq) = self.dead_letter_queue {
208 dlq.get_unrecovered()
209 } else {
210 vec![]
211 }
212 }
213
214 pub fn get_old_dead_letter_entries(
215 &self,
216 age: std::time::Duration,
217 ) -> Vec<crate::DeadLetterEntry> {
218 if let Some(ref dlq) = self.dead_letter_queue {
219 dlq.get_old_entries(age)
220 } else {
221 vec![]
222 }
223 }
224
225 pub fn remove_dead_letter_entry(&self, index: usize) -> Option<crate::DeadLetterEntry> {
226 if let Some(ref dlq) = self.dead_letter_queue {
227 dlq.remove(index)
228 } else {
229 None
230 }
231 }
232
233 pub fn get_dlq_size(&self) -> usize {
234 if let Some(ref dlq) = self.dead_letter_queue {
235 dlq.get_all().len()
236 } else {
237 0
238 }
239 }
240
241 pub fn get_unrecovered_dlq_size(&self) -> usize {
242 if let Some(ref dlq) = self.dead_letter_queue {
243 dlq.get_unrecovered().len()
244 } else {
245 0
246 }
247 }
248
249 #[cfg(feature = "vector")]
250 pub fn vector_index_exists(&self, space_id: u64, tag_name: &str, field_name: &str) -> bool {
251 if let Some(ref vector_coord) = self.vector_coordinator {
252 vector_coord.index_exists(space_id, tag_name, field_name)
253 } else {
254 false
255 }
256 }
257
258 #[cfg(feature = "vector")]
259 pub async fn create_vector_index(
260 &self,
261 space_id: u64,
262 tag_name: &str,
263 field_name: &str,
264 vector_size: usize,
265 distance: simvec::DistanceMetric,
266 ) -> Result<String, SyncError> {
267 if let Some(ref vector_coord) = self.vector_coordinator {
268 vector_coord
269 .create_vector_index(space_id, tag_name, field_name, vector_size, distance)
270 .await
271 .map_err(|e| SyncError::VectorError(e.to_string()))
272 } else {
273 Err(SyncError::Internal(
274 "Vector coordinator not available".to_string(),
275 ))
276 }
277 }
278
279 #[cfg(feature = "vector")]
280 pub async fn drop_vector_index(
281 &self,
282 space_id: u64,
283 tag_name: &str,
284 field_name: &str,
285 ) -> Result<(), SyncError> {
286 if let Some(ref vector_coord) = self.vector_coordinator {
287 vector_coord
288 .drop_vector_index(space_id, tag_name, field_name)
289 .await
290 .map_err(|e| SyncError::VectorError(e.to_string()))
291 } else {
292 Err(SyncError::Internal(
293 "Vector coordinator not available".to_string(),
294 ))
295 }
296 }
297
298 #[cfg(feature = "vector")]
299 pub async fn search_vector(
300 &self,
301 space_id: u64,
302 tag_name: &str,
303 field_name: &str,
304 vector: &[f32],
305 top_k: usize,
306 ) -> Result<Vec<SearchResult>, SyncError> {
307 if let Some(ref vector_coord) = self.vector_coordinator {
308 let options = crate::vector_sync::SearchOptions::new(
309 space_id,
310 tag_name,
311 field_name,
312 vector.to_vec(),
313 top_k,
314 );
315 vector_coord
316 .search_with_options(options)
317 .await
318 .map_err(|e| SyncError::VectorError(e.to_string()))
319 } else {
320 Err(SyncError::Internal(
321 "Vector coordinator not available".to_string(),
322 ))
323 }
324 }
325}