Skip to main content

relay_knowledge/storage/sqlite/code/
mod.rs

1use std::path::Path;
2
3use rusqlite::{Connection, OptionalExtension};
4
5use super::{business, scope_filters as code_query_scope, software};
6
7mod batch;
8mod checkpoint_receipt;
9mod documents;
10mod feature_flags;
11mod frameworks;
12mod generated;
13mod impact;
14pub(in crate::storage) mod lifecycle;
15pub(in crate::storage::sqlite) mod publication;
16mod query;
17mod repository_set_store;
18mod routes;
19pub(in crate::storage::sqlite) mod schema;
20mod search;
21mod session_finalization;
22mod set;
23mod snapshot;
24mod software_projection_store;
25mod symbols;
26mod tasks;
27mod views;
28mod workspace;
29
30#[cfg(test)]
31pub(in crate::storage) use schema::ensure_code_query_indexes;
32
33#[cfg(test)]
34#[path = "tests/mod_tests.rs"]
35mod code_tests;
36
37#[cfg(test)]
38#[path = "tests/scope_status.rs"]
39mod code_scope_status_tests;
40
41#[cfg(test)]
42#[path = "tests/incremental_search.rs"]
43mod code_incremental_search_tests;
44
45#[cfg(test)]
46#[path = "tests/cross_language_calls.rs"]
47mod code_cross_language_call_tests;
48
49#[cfg(test)]
50#[path = "query/accuracy/mod.rs"]
51mod code_query_accuracy_tests;
52
53#[cfg(test)]
54#[path = "tests/metadata.rs"]
55mod code_metadata_tests;
56
57#[cfg(test)]
58#[path = "tests/unfenced_authority.rs"]
59mod code_unfenced_authority_tests;
60
61use crate::{
62    domain::{
63        BusinessKnowledgeProjection, BusinessKnowledgeProjectionInput,
64        BusinessKnowledgeQueryRequest, BusinessKnowledgeStatus, CodeFeatureFlagGraph,
65        CodeFeatureFlagRequest, CodeFileFingerprint, CodeImpactRequest, CodeIndexBatch,
66        CodeIndexCheckpoint, CodeIndexPublicationFence, CodeIndexSession, CodeIndexSnapshot,
67        CodeIndexSummary, CodeRepositoryRegistration, CodeRepositoryReport, CodeRepositoryStatus,
68        CodeRepositoryTotals, CodeRetrievalHit, CodeRetrievalRequest, CodeSymbolGenerationCounts,
69        CodebaseViewRequest, CodebaseViewSnapshot, IndexedRepositoryDocument,
70    },
71    storage::{
72        BusinessKnowledgeStore, CodeImpactChanges, CodeIndexPublicationStore, CodeIndexSourceStore,
73        CodeIndexTaskStore, CodeQueryReadStore, CodeScopeRetentionStore, RepositoryCatalogStore,
74        StorageError, StorageFuture,
75    },
76};
77
78use super::SqliteGraphStore;
79pub(in crate::storage) use lifecycle::commit_scope::{
80    preserve_existing_scope_commit, record as record_commit_scope,
81};
82use lifecycle::{cleanup, removal, report, status};
83pub(in crate::storage) use publication::record_receipt_from_active_fence;
84pub(super) use search::SearchDocumentInserter;
85#[cfg(test)]
86pub(in crate::storage) use tasks::MAX_SCOPE_SLOTS_PER_REPOSITORY;
87
88impl SqliteGraphStore {
89    pub(in crate::storage) fn code_query_indexes_ready_for_publication(
90        &self,
91    ) -> StorageFuture<'_, bool> {
92        self.run_read(|connection| schema::query_indexes_ready_for_fact_publication(connection))
93    }
94
95    pub(in crate::storage) fn materialize_partitioned_completed_checkpoint(
96        &self,
97        expected: CodeIndexCheckpoint,
98        fence: Option<CodeIndexPublicationFence>,
99    ) -> StorageFuture<'_, CodeIndexCheckpoint> {
100        let authority_path = self.publication_authority_path.clone();
101        self.run(move |connection| {
102            let guard = fence
103                .map(|fence| {
104                    lifecycle::publication_fence::prepare_guard(
105                        connection,
106                        fence,
107                        authority_path.as_deref(),
108                    )
109                })
110                .transpose()?;
111            batch::materialize_partitioned_completed_checkpoint(
112                connection,
113                expected,
114                guard.as_ref(),
115            )
116        })
117    }
118
119    pub(in crate::storage) fn reopen_completed_checkpoint_for_partitioned_repair(
120        &self,
121        expected: CodeIndexCheckpoint,
122        fence: CodeIndexPublicationFence,
123    ) -> StorageFuture<'_, CodeIndexCheckpoint> {
124        let authority_path = self.publication_authority_path.clone();
125        self.run(move |connection| {
126            let guard = lifecycle::publication_fence::prepare_guard(
127                connection,
128                fence,
129                authority_path.as_deref(),
130            )?;
131            batch::reopen_completed_checkpoint_for_partitioned_repair(connection, expected, &guard)
132        })
133    }
134}
135
136pub(super) fn initialize_code_schema(connection: &Connection) -> Result<(), StorageError> {
137    schema::initialize_code_schema(connection)?;
138    software::initialize_schema(connection)?;
139    business::initialize_schema(connection)
140}
141
142pub(super) fn import_repository_from_database(
143    connection: &mut Connection,
144    source_path: &Path,
145    repository_id: &str,
146    source_scope: Option<&str>,
147) -> Result<(), StorageError> {
148    snapshot::import_repository_from_database(connection, source_path, repository_id, source_scope)
149}
150
151pub(super) fn repository_totals_excluding(
152    connection: &mut Connection,
153    excluded_repository_ids: &[String],
154) -> Result<CodeRepositoryTotals, StorageError> {
155    report::repository_totals_excluding(connection, excluded_repository_ids)
156}
157
158pub(super) fn prune_scopes_with_retained(
159    connection: &mut Connection,
160    request: crate::storage::CodeScopeRetentionRequest,
161    extra_retained_scopes: Vec<String>,
162) -> Result<crate::domain::CodeScopeRetentionSummary, StorageError> {
163    tasks::prune_scopes_with_retained(connection, request, extra_retained_scopes)
164}
165
166pub(super) fn complete_repository_retention(
167    connection: &mut Connection,
168    repository_id: &str,
169    cutoff_ms: u64,
170) -> Result<bool, StorageError> {
171    tasks::complete_repository_retention(connection, repository_id, cutoff_ms)
172}
173
174pub(super) fn repository_retention_republished_initial_scope(
175    connection: &Connection,
176    repository_id: &str,
177    initial_scope: &str,
178    cutoff_ms: u64,
179    cutoff_publication_generation: u64,
180) -> Result<Option<String>, StorageError> {
181    tasks::repository_retention_republished_initial_scope(
182        connection,
183        repository_id,
184        initial_scope,
185        cutoff_ms,
186        cutoff_publication_generation,
187    )
188}
189
190fn ensure_queryable_code_scope(
191    connection: &Connection,
192    source_scope: &str,
193) -> Result<(), StorageError> {
194    tasks::retention_gc::reject_retiring_scope(connection, source_scope)?;
195    let stale = connection
196        .query_row(
197            "SELECT stale FROM code_repository_scopes WHERE source_scope = ?1",
198            [source_scope],
199            |row| row.get::<_, bool>(0),
200        )
201        .optional()?
202        .ok_or_else(|| {
203            StorageError::InvalidInput(format!(
204                "code repository scope '{source_scope}' is unavailable"
205            ))
206        })?;
207    if stale {
208        return Err(StorageError::InvalidInput(format!(
209            "code repository scope '{source_scope}' is not published"
210        )));
211    }
212    #[cfg(test)]
213    read_snapshot_test_hook::after_retiring_check();
214    Ok(())
215}
216
217impl RepositoryCatalogStore for SqliteGraphStore {
218    fn upsert_code_repository(
219        &self,
220        registration: CodeRepositoryRegistration,
221    ) -> StorageFuture<'_, CodeRepositoryStatus> {
222        self.run(move |connection| status::upsert_repository(connection, registration))
223    }
224
225    fn code_repository_status(
226        &self,
227        repository: String,
228    ) -> StorageFuture<'_, Option<CodeRepositoryStatus>> {
229        self.run_read_snapshot(move |connection| status::repository_status(connection, &repository))
230    }
231
232    fn list_code_repositories(&self) -> StorageFuture<'_, Vec<CodeRepositoryStatus>> {
233        self.run_read_snapshot(status::repository_statuses)
234    }
235
236    fn remove_code_repository(
237        &self,
238        repository: String,
239        now_ms: u64,
240    ) -> StorageFuture<'_, Option<crate::domain::CodeRepositoryRemovalSummary>> {
241        self.run(move |connection| removal::remove_repository(connection, &repository, now_ms))
242    }
243
244    fn code_repository_scope_status(
245        &self,
246        repository: String,
247        resolved_commit_sha: String,
248        path_filters: Vec<String>,
249        language_filters: Vec<String>,
250    ) -> StorageFuture<'_, Option<CodeRepositoryStatus>> {
251        self.run_read_snapshot(move |connection| {
252            status::repository_scope_status(
253                connection,
254                &repository,
255                &resolved_commit_sha,
256                &path_filters,
257                &language_filters,
258            )
259        })
260    }
261
262    fn latest_code_repository_scope_status(
263        &self,
264        repository: String,
265        path_filters: Vec<String>,
266        language_filters: Vec<String>,
267    ) -> StorageFuture<'_, Option<CodeRepositoryStatus>> {
268        self.run_read_snapshot(move |connection| {
269            status::latest_repository_scope_status(
270                connection,
271                &repository,
272                &path_filters,
273                &language_filters,
274            )
275        })
276    }
277}
278
279impl CodeIndexTaskStore for SqliteGraphStore {
280    fn queue_code_index_task(
281        &self,
282        task: crate::storage::CodeIndexTaskSeed,
283    ) -> StorageFuture<'_, crate::domain::CodeIndexTaskRecord> {
284        self.run(move |connection| tasks::queue_task(connection, task))
285    }
286
287    fn claim_code_index_task(
288        &self,
289        request: crate::storage::CodeIndexTaskClaimRequest,
290    ) -> StorageFuture<'_, Option<crate::domain::CodeIndexTaskRecord>> {
291        self.run(move |connection| tasks::claim_task(connection, request))
292    }
293
294    fn recover_code_index_task_leases(
295        &self,
296        now_ms: u64,
297        max_attempts: u32,
298    ) -> StorageFuture<'_, ()> {
299        self.run(move |connection| {
300            tasks::recover_expired_task_leases(connection, now_ms, max_attempts)
301        })
302    }
303
304    fn running_code_index_task_leases(
305        &self,
306    ) -> StorageFuture<'_, Vec<crate::storage::CodeIndexTaskLeaseRecord>> {
307        self.run_read(tasks::running_task_leases)
308    }
309
310    fn recover_code_index_task_leases_by_task(
311        &self,
312        request: crate::storage::CodeIndexTaskLeaseRecovery,
313    ) -> StorageFuture<'_, usize> {
314        self.run(move |connection| tasks::recover_task_leases_by_task(connection, request))
315    }
316
317    fn reset_code_index_tasks(
318        &self,
319        repository_id: String,
320        now_ms: u64,
321    ) -> StorageFuture<'_, Vec<crate::domain::CodeIndexTaskRecord>> {
322        self.run(move |connection| tasks::reset_tasks(connection, &repository_id, now_ms))
323    }
324
325    fn renew_code_index_task_lease(
326        &self,
327        request: crate::storage::CodeIndexTaskLeaseRenewal,
328    ) -> StorageFuture<'_, crate::domain::CodeIndexTaskRecord> {
329        self.run(move |connection| tasks::renew_task_lease(connection, request))
330    }
331
332    fn complete_code_index_task(
333        &self,
334        request: crate::storage::CodeIndexTaskCompletion,
335    ) -> StorageFuture<'_, crate::domain::CodeIndexTaskRecord> {
336        self.run(move |connection| tasks::complete_task(connection, request))
337    }
338
339    fn run_code_index_post_maintenance(
340        &self,
341        _repository_id: String,
342        _source_scope: String,
343    ) -> StorageFuture<'_, ()> {
344        let maintenance = self.maintenance.clone();
345        self.run(move |connection| {
346            super::connection_runtime::maintenance::run_post_index_maintenance(
347                connection,
348                &maintenance,
349            );
350            Ok(())
351        })
352    }
353
354    fn code_index_publication_receipt(
355        &self,
356        task_id: String,
357        repository_id: String,
358        source_scope: String,
359        now_ms: u64,
360    ) -> StorageFuture<'_, bool> {
361        self.run_read(move |connection| {
362            tasks::publication_receipt(connection, &task_id, &repository_id, &source_scope, now_ms)
363        })
364    }
365
366    fn reconcile_code_index_publication_with_fence(
367        &self,
368        target: crate::storage::CodeIndexPublicationTarget,
369        fence: CodeIndexPublicationFence,
370    ) -> StorageFuture<'_, bool> {
371        let authority_path = self.publication_authority_path.clone();
372        self.run(move |connection| {
373            let guard = lifecycle::publication_fence::prepare_guard(
374                connection,
375                fence,
376                authority_path.as_deref(),
377            )?;
378            publication::adopt_active_target(connection, &target, &guard)
379        })
380    }
381
382    fn fail_code_index_task(
383        &self,
384        request: crate::storage::CodeIndexTaskFailure,
385    ) -> StorageFuture<'_, crate::domain::CodeIndexTaskRecord> {
386        self.run(move |connection| tasks::fail_task(connection, request))
387    }
388
389    fn code_index_task(
390        &self,
391        task_id: String,
392    ) -> StorageFuture<'_, Option<crate::domain::CodeIndexTaskRecord>> {
393        self.run_read(move |connection| tasks::task_by_id(connection, &task_id))
394    }
395
396    fn active_code_index_task(
397        &self,
398        repository_id: String,
399    ) -> StorageFuture<'_, Option<crate::domain::CodeIndexTaskRecord>> {
400        self.run_read(move |connection| tasks::active_task(connection, &repository_id))
401    }
402
403    fn code_index_task_queue_status(
404        &self,
405    ) -> StorageFuture<'_, crate::domain::CodeIndexTaskQueueStatus> {
406        self.run_read(tasks::queue_status)
407    }
408}
409
410impl CodeScopeRetentionStore for SqliteGraphStore {
411    fn code_scope_retention(
412        &self,
413        repository_id: String,
414    ) -> StorageFuture<'_, crate::domain::CodeScopeRetentionSummary> {
415        self.run_read_snapshot(move |connection| {
416            tasks::retention_status(connection, &repository_id)
417        })
418    }
419
420    fn prune_code_repository_scopes(
421        &self,
422        request: crate::storage::CodeScopeRetentionRequest,
423    ) -> StorageFuture<'_, crate::domain::CodeScopeRetentionSummary> {
424        self.run(move |connection| tasks::prune_scopes(connection, request))
425    }
426
427    fn schedule_code_repository_retention(
428        &self,
429        max_indexed_repositories: usize,
430        now_ms: u64,
431    ) -> StorageFuture<'_, Option<String>> {
432        self.run(move |connection| {
433            tasks::schedule_repository_retention(connection, max_indexed_repositories, now_ms)
434        })
435    }
436
437    fn code_repository_retention_scan_pending(&self) -> StorageFuture<'_, bool> {
438        self.run_read(|connection| tasks::repository_retention_scan_pending(connection))
439    }
440}
441
442impl CodeIndexSourceStore for SqliteGraphStore {
443    fn code_file_fingerprints(
444        &self,
445        repository_id: String,
446    ) -> StorageFuture<'_, Vec<CodeFileFingerprint>> {
447        self.run_read(move |connection| snapshot::file_fingerprints(connection, &repository_id))
448    }
449
450    fn code_file_fingerprints_for_scope(
451        &self,
452        source_scope: String,
453    ) -> StorageFuture<'_, Vec<CodeFileFingerprint>> {
454        self.run_read_snapshot(move |connection| {
455            ensure_queryable_code_scope(connection, &source_scope)?;
456            snapshot::file_fingerprints_for_scope(connection, &source_scope)
457        })
458    }
459
460    fn code_file_fingerprints_for_paths(
461        &self,
462        source_scope: String,
463        paths: Vec<String>,
464    ) -> StorageFuture<'_, Vec<CodeFileFingerprint>> {
465        self.run_read_snapshot(move |connection| {
466            ensure_queryable_code_scope(connection, &source_scope)?;
467            snapshot::file_fingerprints_for_paths(connection, &source_scope, &paths)
468        })
469    }
470
471    fn code_file_candidate_paths_for_scope(
472        &self,
473        source_scope: String,
474        path_filters: Vec<String>,
475        language_filters: Vec<String>,
476        exclude_generated: bool,
477        limit: usize,
478    ) -> StorageFuture<'_, Vec<String>> {
479        self.run_read_snapshot(move |connection| {
480            ensure_queryable_code_scope(connection, &source_scope)?;
481            snapshot::file_candidate_paths_for_scope(
482                connection,
483                &source_scope,
484                &path_filters,
485                &language_filters,
486                exclude_generated,
487                limit,
488            )
489        })
490    }
491
492    fn code_file_candidate_paths_for_query_scope(
493        &self,
494        source_scope: String,
495        query: String,
496        path_filters: Vec<String>,
497        language_filters: Vec<String>,
498        exclude_generated: bool,
499        limit: usize,
500    ) -> StorageFuture<'_, Vec<String>> {
501        self.run_read_snapshot(move |connection| {
502            ensure_queryable_code_scope(connection, &source_scope)?;
503            snapshot::file_candidate_paths_for_query_scope(
504                connection,
505                &source_scope,
506                &query,
507                &path_filters,
508                &language_filters,
509                exclude_generated,
510                limit,
511            )
512        })
513    }
514
515    fn repository_documents_for_scope(
516        &self,
517        source_scope: String,
518        path_filters: Vec<String>,
519        max_files: usize,
520        max_bytes: usize,
521    ) -> StorageFuture<'_, Vec<IndexedRepositoryDocument>> {
522        self.run_read_snapshot(move |connection| {
523            ensure_queryable_code_scope(connection, &source_scope)?;
524            documents::read_indexed_markdown_in_snapshot(
525                connection,
526                &source_scope,
527                &path_filters,
528                max_files,
529                max_bytes,
530            )
531        })
532    }
533}
534
535impl CodeIndexPublicationStore for SqliteGraphStore {
536    fn code_index_checkpoint(
537        &self,
538        source_scope: String,
539    ) -> StorageFuture<'_, Option<CodeIndexCheckpoint>> {
540        self.run_read(move |connection| tasks::checkpoint(connection, &source_scope))
541    }
542
543    fn latest_code_index_checkpoint(
544        &self,
545        repository_id: String,
546    ) -> StorageFuture<'_, Option<CodeIndexCheckpoint>> {
547        self.run_read(move |connection| {
548            tasks::latest_checkpoint_for_repository(connection, &repository_id)
549        })
550    }
551
552    fn apply_code_index_snapshot(
553        &self,
554        snapshot: CodeIndexSnapshot,
555    ) -> StorageFuture<'_, CodeIndexSummary> {
556        let this = self.clone();
557        Box::pin(async move {
558            let summary = this
559                .run(move |connection| snapshot::apply_snapshot(connection, snapshot))
560                .await?;
561            session_finalization::run_best_effort_maintenance(&this).await;
562            Ok(summary)
563        })
564    }
565
566    fn apply_code_index_snapshot_with_fence(
567        &self,
568        snapshot: CodeIndexSnapshot,
569        fence: CodeIndexPublicationFence,
570    ) -> StorageFuture<'_, CodeIndexSummary> {
571        let authority_path = self.publication_authority_path.clone();
572        self.run(move |connection| {
573            let guard = lifecycle::publication_fence::prepare_guard(
574                connection,
575                fence,
576                authority_path.as_deref(),
577            )?;
578            snapshot::apply_snapshot_with_fence(connection, snapshot, Some(&guard))
579        })
580    }
581
582    fn clear_code_workspace_state(
583        &self,
584        repository_id: String,
585        source_scope: String,
586    ) -> StorageFuture<'_, ()> {
587        self.run(move |connection| {
588            workspace::clear_auto_workspace_state(connection, &repository_id, &source_scope)
589        })
590    }
591
592    fn code_repository_auto_workspace_state_exists(
593        &self,
594        repository_id: String,
595    ) -> StorageFuture<'_, bool> {
596        self.run_read(move |connection| {
597            workspace::has_auto_workspace_state(connection, &repository_id)
598        })
599    }
600
601    fn clear_code_workspace_state_with_fence(
602        &self,
603        repository_id: String,
604        source_scope: String,
605        fence: CodeIndexPublicationFence,
606    ) -> StorageFuture<'_, ()> {
607        let authority_path = self.publication_authority_path.clone();
608        self.run(move |connection| {
609            let guard = lifecycle::publication_fence::prepare_guard(
610                connection,
611                fence,
612                authority_path.as_deref(),
613            )?;
614            workspace::clear_auto_workspace_state_with_fence(
615                connection,
616                &repository_id,
617                &source_scope,
618                &guard,
619            )
620        })
621    }
622
623    fn begin_code_index_session(
624        &self,
625        session: CodeIndexSession,
626    ) -> StorageFuture<'_, CodeIndexCheckpoint> {
627        self.run(move |connection| batch::begin_session(connection, session))
628    }
629
630    fn begin_code_index_session_with_fence(
631        &self,
632        session: CodeIndexSession,
633        fence: CodeIndexPublicationFence,
634    ) -> StorageFuture<'_, CodeIndexCheckpoint> {
635        let authority_path = self.publication_authority_path.clone();
636        self.run(move |connection| {
637            let guard = lifecycle::publication_fence::prepare_guard(
638                connection,
639                fence,
640                authority_path.as_deref(),
641            )?;
642            batch::begin_session_with_fence(connection, session, Some(&guard))
643        })
644    }
645
646    fn begin_code_index_session_at_checkpoint(
647        &self,
648        session: CodeIndexSession,
649        expected_checkpoint: Option<CodeIndexCheckpoint>,
650    ) -> StorageFuture<'_, CodeIndexCheckpoint> {
651        self.run(move |connection| {
652            batch::begin_session_at_checkpoint(connection, session, expected_checkpoint)
653        })
654    }
655
656    fn begin_code_index_session_at_checkpoint_with_fence(
657        &self,
658        session: CodeIndexSession,
659        expected_checkpoint: Option<CodeIndexCheckpoint>,
660        fence: CodeIndexPublicationFence,
661    ) -> StorageFuture<'_, CodeIndexCheckpoint> {
662        let authority_path = self.publication_authority_path.clone();
663        self.run(move |connection| {
664            let guard = lifecycle::publication_fence::prepare_guard(
665                connection,
666                fence,
667                authority_path.as_deref(),
668            )?;
669            batch::begin_session_at_checkpoint_with_fence(
670                connection,
671                session,
672                expected_checkpoint,
673                Some(&guard),
674            )
675        })
676    }
677
678    fn apply_code_index_batch(
679        &self,
680        batch: CodeIndexBatch,
681    ) -> StorageFuture<'_, CodeIndexCheckpoint> {
682        self.run(move |connection| batch::apply_batch(connection, batch))
683    }
684
685    fn apply_code_index_batch_with_fence(
686        &self,
687        batch: CodeIndexBatch,
688        fence: CodeIndexPublicationFence,
689    ) -> StorageFuture<'_, CodeIndexCheckpoint> {
690        let authority_path = self.publication_authority_path.clone();
691        self.run(move |connection| {
692            let guard = lifecycle::publication_fence::prepare_guard(
693                connection,
694                fence,
695                authority_path.as_deref(),
696            )?;
697            batch::apply_batch_with_fence(connection, batch, Some(&guard))
698        })
699    }
700
701    fn finalize_code_index_session(
702        &self,
703        session: CodeIndexSession,
704    ) -> StorageFuture<'_, CodeIndexSummary> {
705        session_finalization::finalize_session(self, session)
706    }
707
708    fn finalize_code_index_session_with_fence(
709        &self,
710        session: CodeIndexSession,
711        fence: CodeIndexPublicationFence,
712    ) -> StorageFuture<'_, CodeIndexSummary> {
713        session_finalization::finalize_session_with_fence(self, session, fence)
714    }
715
716    fn advance_code_index_session_with_fence(
717        &self,
718        session: CodeIndexSession,
719        fence: CodeIndexPublicationFence,
720    ) -> StorageFuture<'_, crate::storage::CodeIndexFinalizationStep> {
721        session_finalization::advance_session_with_fence(self, session, fence)
722    }
723}
724
725impl CodeQueryReadStore for SqliteGraphStore {
726    fn search_code(
727        &self,
728        request: CodeRetrievalRequest,
729    ) -> StorageFuture<'_, Vec<CodeRetrievalHit>> {
730        self.run_read_snapshot(move |connection| query::search_code(connection, request))
731    }
732
733    fn search_code_feature_flags(
734        &self,
735        request: CodeFeatureFlagRequest,
736    ) -> StorageFuture<'_, Vec<CodeFeatureFlagGraph>> {
737        self.run_read_snapshot(move |connection| feature_flags::search(connection, request))
738    }
739
740    fn search_code_feature_flags_scope(
741        &self,
742        source_scope: String,
743        request: CodeFeatureFlagRequest,
744    ) -> StorageFuture<'_, Vec<CodeFeatureFlagGraph>> {
745        self.run_read_snapshot(move |connection| {
746            ensure_queryable_code_scope(connection, &source_scope)?;
747            feature_flags::search_scope(connection, &source_scope, request)
748        })
749    }
750
751    fn search_code_scope(
752        &self,
753        source_scope: String,
754        request: CodeRetrievalRequest,
755    ) -> StorageFuture<'_, Vec<CodeRetrievalHit>> {
756        self.run_read_snapshot(move |connection| {
757            ensure_queryable_code_scope(connection, &source_scope)?;
758            query::search_code_scope(connection, &source_scope, request)
759        })
760    }
761
762    fn analyze_code_impact(
763        &self,
764        request: CodeImpactRequest,
765        changes: CodeImpactChanges,
766    ) -> StorageFuture<'_, Vec<CodeRetrievalHit>> {
767        self.run_read_snapshot(move |connection| {
768            impact::analyze_impact(connection, request, changes)
769        })
770    }
771
772    fn analyze_code_impact_scope(
773        &self,
774        source_scope: String,
775        request: CodeImpactRequest,
776        changes: CodeImpactChanges,
777    ) -> StorageFuture<'_, Vec<CodeRetrievalHit>> {
778        self.run_read_snapshot(move |connection| {
779            ensure_queryable_code_scope(connection, &source_scope)?;
780            impact::analyze_impact_scope(connection, &source_scope, request, changes)
781        })
782    }
783
784    fn codebase_view_snapshot(
785        &self,
786        source_scope: String,
787        request: CodebaseViewRequest,
788        row_limit: usize,
789    ) -> StorageFuture<'_, CodebaseViewSnapshot> {
790        self.run_read_snapshot(move |connection| {
791            ensure_queryable_code_scope(connection, &source_scope)?;
792            views::snapshot(connection, &source_scope, &request, row_limit)
793        })
794    }
795
796    fn code_repository_totals(&self) -> StorageFuture<'_, CodeRepositoryTotals> {
797        self.run_read(report::repository_totals)
798    }
799
800    fn code_repository_report(
801        &self,
802        repository: String,
803    ) -> StorageFuture<'_, CodeRepositoryReport> {
804        self.run_read(move |connection| report::repository_report(connection, &repository))
805    }
806
807    fn code_repository_scope_symbol_generation_counts(
808        &self,
809        source_scope: String,
810    ) -> StorageFuture<'_, CodeSymbolGenerationCounts> {
811        self.run_read_snapshot(move |connection| {
812            ensure_queryable_code_scope(connection, &source_scope)?;
813            let counts = report::scope_symbol_generation_counts(connection, &source_scope)?;
814            Ok(CodeSymbolGenerationCounts {
815                handwritten_symbol_count: counts.handwritten,
816                generated_symbol_count: counts.generated,
817            })
818        })
819    }
820}
821
822impl BusinessKnowledgeStore for SqliteGraphStore {
823    fn replace_business_knowledge_projection(
824        &self,
825        input: BusinessKnowledgeProjectionInput,
826    ) -> StorageFuture<'_, BusinessKnowledgeStatus> {
827        self.run(move |connection| business::replace_projection(connection, input, None))
828    }
829
830    fn replace_business_knowledge_projection_with_fence(
831        &self,
832        input: BusinessKnowledgeProjectionInput,
833        fence: CodeIndexPublicationFence,
834    ) -> StorageFuture<'_, BusinessKnowledgeStatus> {
835        let authority_path = self.publication_authority_path.clone();
836        self.run(move |connection| {
837            let guard = lifecycle::publication_fence::prepare_guard(
838                connection,
839                fence,
840                authority_path.as_deref(),
841            )?;
842            business::replace_projection(connection, input, Some(&guard))
843        })
844    }
845
846    fn business_knowledge_projection_for_scope(
847        &self,
848        source_scope: String,
849        request: BusinessKnowledgeQueryRequest,
850    ) -> StorageFuture<'_, BusinessKnowledgeProjection> {
851        self.run_read_snapshot(move |connection| {
852            ensure_queryable_code_scope(connection, &source_scope)?;
853            business::projection_for_scope(connection, &source_scope, request)
854        })
855    }
856
857    fn business_knowledge_status(
858        &self,
859        source_scope: String,
860    ) -> StorageFuture<'_, Option<BusinessKnowledgeStatus>> {
861        self.run_read_snapshot(move |connection| {
862            business::status_for_scope(connection, &source_scope)
863        })
864    }
865}
866
867#[cfg(test)]
868#[path = "tests/read_snapshot_test_hook.rs"]
869mod read_snapshot_test_hook;