1use std::collections::BTreeMap;
2
3use rusqlite::{Connection, params};
4
5#[path = "code_query.rs"]
6mod code_query;
7
8#[path = "code_query_hits.rs"]
9mod code_query_hits;
10
11#[path = "code_query_scope.rs"]
12mod code_query_scope;
13
14#[path = "code_impact.rs"]
15mod code_impact;
16
17#[path = "code_report.rs"]
18mod code_report;
19
20#[path = "code_schema.rs"]
21mod code_schema;
22
23#[path = "code_status.rs"]
24mod code_status;
25
26#[path = "code_batch.rs"]
27mod code_batch;
28
29#[path = "code_cleanup.rs"]
30mod code_cleanup;
31
32#[path = "code_tasks.rs"]
33mod code_tasks;
34
35#[path = "code_search.rs"]
36mod code_search;
37
38#[cfg(test)]
39#[path = "code_tests.rs"]
40mod code_tests;
41
42#[cfg(test)]
43#[path = "code_batch_finalize_tests.rs"]
44mod code_batch_finalize_tests;
45
46#[cfg(test)]
47#[path = "code_batch_finalize_typescript_tests.rs"]
48mod code_batch_finalize_typescript_tests;
49
50#[cfg(test)]
51#[path = "code_batch_search_tests.rs"]
52mod code_batch_search_tests;
53
54#[cfg(test)]
55#[path = "code_query_accuracy_tests.rs"]
56mod code_query_accuracy_tests;
57
58#[cfg(test)]
59#[path = "code_query_import_target_tests.rs"]
60mod code_query_import_target_tests;
61
62#[cfg(test)]
63#[path = "code_query_import_ranking_tests.rs"]
64mod code_query_import_ranking_tests;
65
66#[cfg(test)]
67#[path = "code_query_line_context_tests.rs"]
68mod code_query_line_context_tests;
69
70#[cfg(test)]
71#[path = "code_metadata_tests.rs"]
72mod code_metadata_tests;
73
74#[cfg(test)]
75#[path = "code_tasks_tests.rs"]
76mod code_tasks_tests;
77
78use crate::{
79 domain::{
80 CodeFileFingerprint, CodeImpactRequest, CodeIndexBatch, CodeIndexCheckpoint,
81 CodeIndexProgressSummary, CodeIndexSession, CodeIndexSnapshot, CodeIndexSummary,
82 CodeRepositoryRegistration, CodeRepositoryReport, CodeRepositoryStatus,
83 CodeRepositoryTotals, CodeRetrievalHit, CodeRetrievalRequest,
84 },
85 storage::{CodeImpactChanges, CodeRepositoryStore, StorageError, StorageFuture},
86};
87
88use super::SqliteGraphStore;
89use code_cleanup::{count_code_rows, delete_path_index, delete_path_indexes, delete_scope_index};
90pub(super) use code_search::SearchDocumentInserter;
91use code_search::insert_search_document;
92use code_status::{canonical_filter_values, canonical_path_filters, parse_json_list};
93
94pub(super) fn initialize_code_schema(connection: &Connection) -> Result<(), StorageError> {
95 code_schema::initialize_code_schema(connection)
96}
97
98impl CodeRepositoryStore for SqliteGraphStore {
99 fn upsert_code_repository(
100 &self,
101 registration: CodeRepositoryRegistration,
102 ) -> StorageFuture<'_, CodeRepositoryStatus> {
103 self.run(move |connection| code_status::upsert_repository(connection, registration))
104 }
105
106 fn code_repository_status(
107 &self,
108 repository: String,
109 ) -> StorageFuture<'_, Option<CodeRepositoryStatus>> {
110 self.run(move |connection| code_status::repository_status(connection, &repository))
111 }
112
113 fn code_repository_scope_status(
114 &self,
115 repository: String,
116 resolved_commit_sha: String,
117 path_filters: Vec<String>,
118 language_filters: Vec<String>,
119 ) -> StorageFuture<'_, Option<CodeRepositoryStatus>> {
120 self.run(move |connection| {
121 code_status::repository_scope_status(
122 connection,
123 &repository,
124 &resolved_commit_sha,
125 &path_filters,
126 &language_filters,
127 )
128 })
129 }
130
131 fn queue_code_index_task(
132 &self,
133 task: crate::storage::CodeIndexTaskSeed,
134 ) -> StorageFuture<'_, crate::domain::CodeIndexTaskRecord> {
135 self.run(move |connection| code_tasks::queue_task(connection, task))
136 }
137
138 fn claim_code_index_task(
139 &self,
140 request: crate::storage::CodeIndexTaskClaimRequest,
141 ) -> StorageFuture<'_, Option<crate::domain::CodeIndexTaskRecord>> {
142 self.run(move |connection| code_tasks::claim_task(connection, request))
143 }
144
145 fn complete_code_index_task(
146 &self,
147 request: crate::storage::CodeIndexTaskCompletion,
148 ) -> StorageFuture<'_, crate::domain::CodeIndexTaskRecord> {
149 self.run(move |connection| code_tasks::complete_task(connection, request))
150 }
151
152 fn fail_code_index_task(
153 &self,
154 request: crate::storage::CodeIndexTaskFailure,
155 ) -> StorageFuture<'_, crate::domain::CodeIndexTaskRecord> {
156 self.run(move |connection| code_tasks::fail_task(connection, request))
157 }
158
159 fn code_index_task(
160 &self,
161 task_id: String,
162 ) -> StorageFuture<'_, Option<crate::domain::CodeIndexTaskRecord>> {
163 self.run(move |connection| code_tasks::task_by_id(connection, &task_id))
164 }
165
166 fn active_code_index_task(
167 &self,
168 repository_id: String,
169 ) -> StorageFuture<'_, Option<crate::domain::CodeIndexTaskRecord>> {
170 self.run(move |connection| code_tasks::active_task(connection, &repository_id))
171 }
172
173 fn code_index_checkpoint(
174 &self,
175 source_scope: String,
176 ) -> StorageFuture<'_, Option<crate::domain::CodeIndexCheckpoint>> {
177 self.run(move |connection| code_tasks::checkpoint(connection, &source_scope))
178 }
179
180 fn code_scope_retention(
181 &self,
182 repository_id: String,
183 ) -> StorageFuture<'_, crate::domain::CodeScopeRetentionSummary> {
184 self.run(move |connection| code_tasks::retention_status(connection, &repository_id))
185 }
186
187 fn prune_code_repository_scopes(
188 &self,
189 request: crate::storage::CodeScopeRetentionRequest,
190 ) -> StorageFuture<'_, crate::domain::CodeScopeRetentionSummary> {
191 self.run(move |connection| code_tasks::prune_scopes(connection, request))
192 }
193
194 fn code_file_fingerprints(
195 &self,
196 repository_id: String,
197 ) -> StorageFuture<'_, Vec<CodeFileFingerprint>> {
198 self.run(move |connection| file_fingerprints(connection, &repository_id))
199 }
200
201 fn code_file_fingerprints_for_scope(
202 &self,
203 source_scope: String,
204 ) -> StorageFuture<'_, Vec<CodeFileFingerprint>> {
205 self.run(move |connection| file_fingerprints_for_scope(connection, &source_scope))
206 }
207
208 fn apply_code_index_snapshot(
209 &self,
210 snapshot: CodeIndexSnapshot,
211 ) -> StorageFuture<'_, CodeIndexSummary> {
212 self.run(move |connection| apply_snapshot(connection, snapshot))
213 }
214
215 fn begin_code_index_session(
216 &self,
217 session: CodeIndexSession,
218 ) -> StorageFuture<'_, CodeIndexCheckpoint> {
219 self.run(move |connection| code_batch::begin_session(connection, session))
220 }
221
222 fn apply_code_index_batch(
223 &self,
224 batch: CodeIndexBatch,
225 ) -> StorageFuture<'_, CodeIndexCheckpoint> {
226 self.run(move |connection| code_batch::apply_batch(connection, batch))
227 }
228
229 fn finalize_code_index_session(
230 &self,
231 session: CodeIndexSession,
232 ) -> StorageFuture<'_, CodeIndexSummary> {
233 self.run(move |connection| code_batch::finalize_session(connection, session))
234 }
235
236 fn search_code(
237 &self,
238 request: CodeRetrievalRequest,
239 ) -> StorageFuture<'_, Vec<CodeRetrievalHit>> {
240 self.run(move |connection| code_query::search_code(connection, request))
241 }
242
243 fn analyze_code_impact(
244 &self,
245 request: CodeImpactRequest,
246 changes: CodeImpactChanges,
247 ) -> StorageFuture<'_, Vec<CodeRetrievalHit>> {
248 self.run(move |connection| code_impact::analyze_impact(connection, request, changes))
249 }
250
251 fn code_repository_totals(&self) -> StorageFuture<'_, CodeRepositoryTotals> {
252 self.run(code_report::repository_totals)
253 }
254
255 fn code_repository_report(
256 &self,
257 repository: String,
258 ) -> StorageFuture<'_, CodeRepositoryReport> {
259 self.run(move |connection| code_report::repository_report(connection, &repository))
260 }
261}
262
263fn file_fingerprints(
264 connection: &mut Connection,
265 repository_id: &str,
266) -> Result<Vec<CodeFileFingerprint>, StorageError> {
267 let mut statement = connection.prepare(
268 "
269 SELECT path, blob_hash
270 FROM code_repository_files
271 WHERE repository_id = ?1
272 AND source_scope = (
273 SELECT last_indexed_scope_id FROM code_repositories WHERE repository_id = ?1
274 )
275 ORDER BY path ASC
276 ",
277 )?;
278 let rows = statement.query_map(params![repository_id], |row| {
279 Ok(CodeFileFingerprint {
280 path: row.get(0)?,
281 blob_hash: row.get(1)?,
282 })
283 })?;
284
285 rows.collect::<Result<Vec<_>, _>>()
286 .map_err(StorageError::from)
287}
288
289fn file_fingerprints_for_scope(
290 connection: &mut Connection,
291 source_scope: &str,
292) -> Result<Vec<CodeFileFingerprint>, StorageError> {
293 let mut statement = connection.prepare(
294 "
295 SELECT path, blob_hash
296 FROM code_repository_files
297 WHERE source_scope = ?1
298 ORDER BY path ASC
299 ",
300 )?;
301 let rows = statement.query_map(params![source_scope], |row| {
302 Ok(CodeFileFingerprint {
303 path: row.get(0)?,
304 blob_hash: row.get(1)?,
305 })
306 })?;
307
308 rows.collect::<Result<Vec<_>, _>>()
309 .map_err(StorageError::from)
310}
311
312fn apply_snapshot(
313 connection: &mut Connection,
314 snapshot: CodeIndexSnapshot,
315) -> Result<CodeIndexSummary, StorageError> {
316 let transaction = connection.transaction()?;
317 if snapshot.full_replace {
318 delete_scope_index(&transaction, &snapshot.source_scope)?;
319 } else {
320 clone_active_scope_for_incremental(&transaction, &snapshot)?;
321 for path in &snapshot.deleted_paths {
322 delete_path_index(&transaction, &snapshot.source_scope, path)?;
323 }
324 delete_path_indexes(
325 &transaction,
326 &snapshot.source_scope,
327 snapshot.files.iter().map(|file| file.path.as_str()),
328 )?;
329 }
330
331 for file in &snapshot.files {
332 transaction.execute(
333 "
334 INSERT INTO code_repository_files (
335 repository_id, source_scope, file_id, path, language_id, blob_hash, byte_len,
336 line_count, parse_status, degraded_reason
337 )
338 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)
339 ",
340 params![
341 file.repository_id,
342 file.source_scope,
343 file.file_id,
344 file.path,
345 file.language_id,
346 file.blob_hash,
347 file.byte_len,
348 file.line_count,
349 file.parse_status.as_str(),
350 file.degraded_reason,
351 ],
352 )?;
353 }
354 let file_languages_by_path = snapshot
355 .files
356 .iter()
357 .map(|file| (file.path.as_str(), file.language_id.as_str()))
358 .collect::<BTreeMap<_, _>>();
359 for symbol in &snapshot.symbols {
360 transaction.execute(
361 "
362 INSERT INTO code_repository_symbols (
363 repository_id, source_scope, symbol_snapshot_id, canonical_symbol_id,
364 file_id, path, language_id, name,
365 qualified_name, kind, signature, doc_comment, byte_start, byte_end,
366 line_start, line_end
367 )
368 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16)
369 ",
370 params![
371 symbol.repository_id,
372 symbol.source_scope,
373 symbol.symbol_snapshot_id,
374 symbol.canonical_symbol_id,
375 symbol.file_id,
376 symbol.path,
377 symbol.language_id,
378 symbol.name,
379 symbol.qualified_name,
380 symbol.kind,
381 symbol.signature,
382 symbol.doc_comment,
383 symbol.byte_range.start,
384 symbol.byte_range.end,
385 symbol.line_range.start,
386 symbol.line_range.end,
387 ],
388 )?;
389 insert_search_document(
390 &transaction,
391 &symbol.source_scope,
392 "symbol",
393 &symbol.symbol_snapshot_id,
394 &symbol.path,
395 &symbol.language_id,
396 [
397 symbol.name.as_str(),
398 symbol.qualified_name.as_str(),
399 symbol.kind.as_str(),
400 symbol.signature.as_str(),
401 symbol.doc_comment.as_deref().unwrap_or_default(),
402 symbol.path.as_str(),
403 ],
404 )?;
405 }
406 for reference in &snapshot.references {
407 transaction.execute(
408 "
409 INSERT INTO code_repository_references (
410 repository_id, source_scope, reference_id, file_id, path, name, kind,
411 target_symbol_snapshot_id, target_hint, resolution_state,
412 confidence_basis_points, confidence_tier,
413 byte_start, byte_end, line_start, line_end
414 )
415 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16)
416 ",
417 params![
418 reference.repository_id,
419 reference.source_scope,
420 reference.reference_id,
421 reference.file_id,
422 reference.path,
423 reference.name,
424 reference.kind,
425 reference.target_symbol_snapshot_id,
426 reference.target_hint,
427 reference.resolution_state,
428 reference.confidence_basis_points,
429 reference.confidence_tier,
430 reference.byte_range.start,
431 reference.byte_range.end,
432 reference.line_range.start,
433 reference.line_range.end,
434 ],
435 )?;
436 insert_search_document(
437 &transaction,
438 &reference.source_scope,
439 "reference",
440 &reference.reference_id,
441 &reference.path,
442 file_languages_by_path
443 .get(reference.path.as_str())
444 .copied()
445 .unwrap_or_default(),
446 [
447 reference.name.as_str(),
448 reference.kind.as_str(),
449 reference.target_hint.as_deref().unwrap_or_default(),
450 reference.path.as_str(),
451 ],
452 )?;
453 }
454 insert_imports_calls_chunks_diagnostics(&transaction, &snapshot)?;
455 update_repository_after_snapshot(&transaction, &snapshot)?;
456 transaction.commit()?;
457
458 let status =
459 code_status::repository_status(connection, &snapshot.repository_id)?.ok_or_else(|| {
460 StorageError::InvalidInput("code repository status is missing after index".to_owned())
461 })?;
462
463 Ok(CodeIndexSummary {
464 repository_id: snapshot.repository_id,
465 source_scope: snapshot.source_scope,
466 resolved_commit_sha: snapshot.resolved_commit_sha,
467 tree_hash: snapshot.tree_hash,
468 indexed_file_count: status.indexed_file_count,
469 changed_path_count: snapshot.changed_path_count,
470 skipped_unchanged_count: snapshot.skipped_unchanged_count,
471 deleted_path_count: snapshot.deleted_paths.len(),
472 symbol_count: status.symbol_count,
473 reference_count: status.reference_count,
474 chunk_count: status.chunk_count,
475 degraded_file_count: snapshot.diagnostics.len(),
476 progress: CodeIndexProgressSummary {
477 git_file_count: if snapshot.full_replace {
478 status.indexed_file_count
479 } else {
480 snapshot.changed_path_count
481 },
482 blob_read_count: snapshot.files.len(),
483 parsed_file_count: snapshot.files.len(),
484 sqlite_write_count: snapshot
485 .files
486 .len()
487 .saturating_add(snapshot.symbols.len())
488 .saturating_add(snapshot.references.len())
489 .saturating_add(snapshot.imports.len())
490 .saturating_add(snapshot.calls.len())
491 .saturating_add(snapshot.chunks.len())
492 .saturating_add(snapshot.diagnostics.len()),
493 skipped_file_count: snapshot.skipped_unchanged_count,
494 degraded_file_count: snapshot.diagnostics.len(),
495 batch_count: 1,
496 checkpoint_file_count: snapshot.files.len(),
497 resource_budget: crate::domain::CodeIndexResourceBudget::default(),
498 },
499 })
500}
501
502fn clone_active_scope_for_incremental(
503 transaction: &rusqlite::Transaction<'_>,
504 snapshot: &CodeIndexSnapshot,
505) -> Result<(), StorageError> {
506 let path_filters_json = serde_json::to_string(&snapshot.path_filters)
507 .map_err(|error| StorageError::InvalidInput(error.to_string()))?;
508 let language_filters_json = serde_json::to_string(&snapshot.language_filters)
509 .map_err(|error| StorageError::InvalidInput(error.to_string()))?;
510 let requested_path_filters = canonical_path_filters(&snapshot.path_filters);
511 let requested_language_filters = canonical_filter_values(&snapshot.language_filters);
512 let mut statement = transaction.prepare(
513 "
514 SELECT source_scope, path_filters_json, language_filters_json
515 FROM code_repository_scopes
516 WHERE repository_id = ?1
517 AND resolved_commit_sha = ?4
518 ORDER BY
519 CASE WHEN path_filters_json = ?2 AND language_filters_json = ?3 THEN 0 ELSE 1 END,
520 rowid DESC
521 ",
522 )?;
523 let base_commit = snapshot
524 .base_resolved_commit_sha
525 .as_deref()
526 .ok_or_else(|| {
527 StorageError::InvalidInput(format!(
528 "code repository '{}' incremental snapshot is missing its resolved base commit",
529 snapshot.repository_id
530 ))
531 })?;
532 let rows = statement.query_map(
533 params![
534 snapshot.repository_id,
535 path_filters_json,
536 language_filters_json,
537 base_commit
538 ],
539 |row| {
540 Ok((
541 row.get::<_, String>(0)?,
542 parse_json_list(row.get::<_, String>(1)?)?,
543 parse_json_list(row.get::<_, String>(2)?)?,
544 ))
545 },
546 )?;
547 let mut previous_scope = None;
548 for row in rows {
549 let (source_scope, stored_path_filters, stored_language_filters) = row?;
550 if canonical_path_filters(&stored_path_filters) == requested_path_filters
551 && canonical_filter_values(&stored_language_filters) == requested_language_filters
552 {
553 previous_scope = Some(source_scope);
554 break;
555 }
556 }
557 let previous_scope = previous_scope.ok_or_else(|| {
558 StorageError::InvalidInput(format!(
559 "code repository '{}' has no matching indexed scope for incremental filters at the current base commit",
560 snapshot.repository_id
561 ))
562 })?;
563 if previous_scope == snapshot.source_scope {
564 return Ok(());
565 }
566 delete_scope_index(transaction, &snapshot.source_scope)?;
567 clone_code_table(
568 transaction,
569 "code_repository_files",
570 "repository_id, source_scope, file_id, path, language_id, blob_hash, byte_len, line_count, parse_status, degraded_reason",
571 &previous_scope,
572 &snapshot.source_scope,
573 )?;
574 clone_code_table(
575 transaction,
576 "code_repository_symbols",
577 "repository_id, source_scope, symbol_snapshot_id, canonical_symbol_id, file_id, path, language_id, name, qualified_name, kind, signature, doc_comment, byte_start, byte_end, line_start, line_end",
578 &previous_scope,
579 &snapshot.source_scope,
580 )?;
581 clone_code_table(
582 transaction,
583 "code_repository_references",
584 "repository_id, source_scope, reference_id, file_id, path, name, kind, target_symbol_snapshot_id, target_hint, resolution_state, confidence_basis_points, confidence_tier, byte_start, byte_end, line_start, line_end",
585 &previous_scope,
586 &snapshot.source_scope,
587 )?;
588 clone_code_table(
589 transaction,
590 "code_repository_imports",
591 "repository_id, source_scope, import_id, file_id, path, module, target_hint, resolution_state, confidence_basis_points, confidence_tier, line_start, line_end",
592 &previous_scope,
593 &snapshot.source_scope,
594 )?;
595 clone_code_table(
596 transaction,
597 "code_repository_calls",
598 "repository_id, source_scope, call_id, file_id, path, caller_symbol_snapshot_id, caller_name, callee_symbol_snapshot_id, callee_name, target_hint, resolution_state, confidence_basis_points, confidence_tier, line_start, line_end",
599 &previous_scope,
600 &snapshot.source_scope,
601 )?;
602 clone_code_table(
603 transaction,
604 "code_repository_chunks",
605 "repository_id, source_scope, chunk_id, file_id, path, language_id, content, byte_start, byte_end, line_start, line_end, symbol_snapshot_id",
606 &previous_scope,
607 &snapshot.source_scope,
608 )?;
609 clone_code_table(
610 transaction,
611 "code_repository_file_diagnostics",
612 "repository_id, source_scope, path, parse_status, message",
613 &previous_scope,
614 &snapshot.source_scope,
615 )?;
616 clone_code_table(
617 transaction,
618 "code_repository_search",
619 "source_scope, document_kind, record_id, path, language_id, content",
620 &previous_scope,
621 &snapshot.source_scope,
622 )?;
623
624 Ok(())
625}
626
627fn clone_code_table(
628 transaction: &rusqlite::Transaction<'_>,
629 table: &'static str,
630 columns: &'static str,
631 previous_scope: &str,
632 next_scope: &str,
633) -> Result<(), StorageError> {
634 let selected_columns = columns.replacen("source_scope", "?2", 1);
635 transaction.execute(
636 &format!(
637 "INSERT INTO {table} ({columns}) SELECT {selected_columns} FROM {table} WHERE source_scope = ?1"
638 ),
639 params![previous_scope, next_scope],
640 )?;
641
642 Ok(())
643}
644
645fn insert_imports_calls_chunks_diagnostics(
646 transaction: &rusqlite::Transaction<'_>,
647 snapshot: &CodeIndexSnapshot,
648) -> Result<(), StorageError> {
649 let file_languages_by_path = snapshot
650 .files
651 .iter()
652 .map(|file| (file.path.as_str(), file.language_id.as_str()))
653 .collect::<BTreeMap<_, _>>();
654 for import in &snapshot.imports {
655 transaction.execute(
656 "
657 INSERT INTO code_repository_imports (
658 repository_id, source_scope, import_id, file_id, path, module, target_hint,
659 resolution_state, confidence_basis_points, confidence_tier, line_start, line_end
660 )
661 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)
662 ",
663 params![
664 import.repository_id,
665 import.source_scope,
666 import.import_id,
667 import.file_id,
668 import.path,
669 import.module,
670 import.target_hint,
671 import.resolution_state,
672 import.confidence_basis_points,
673 import.confidence_tier,
674 import.line_range.start,
675 import.line_range.end,
676 ],
677 )?;
678 insert_search_document(
679 transaction,
680 &import.source_scope,
681 "import",
682 &import.import_id,
683 &import.path,
684 file_languages_by_path
685 .get(import.path.as_str())
686 .copied()
687 .unwrap_or_default(),
688 [
689 import.module.as_str(),
690 import.target_hint.as_deref().unwrap_or_default(),
691 import.path.as_str(),
692 ],
693 )?;
694 }
695 for call in &snapshot.calls {
696 transaction.execute(
697 "
698 INSERT INTO code_repository_calls (
699 repository_id, source_scope, call_id, file_id, path, caller_symbol_snapshot_id,
700 caller_name, callee_symbol_snapshot_id, callee_name, target_hint,
701 resolution_state, confidence_basis_points, confidence_tier, line_start, line_end
702 )
703 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15)
704 ",
705 params![
706 call.repository_id,
707 call.source_scope,
708 call.call_id,
709 call.file_id,
710 call.path,
711 call.caller_symbol_snapshot_id,
712 call.caller_name,
713 call.callee_symbol_snapshot_id,
714 call.callee_name,
715 call.target_hint,
716 call.resolution_state,
717 call.confidence_basis_points,
718 call.confidence_tier,
719 call.line_range.start,
720 call.line_range.end,
721 ],
722 )?;
723 insert_search_document(
724 transaction,
725 &call.source_scope,
726 "call",
727 &call.call_id,
728 &call.path,
729 file_languages_by_path
730 .get(call.path.as_str())
731 .copied()
732 .unwrap_or_default(),
733 [
734 call.caller_name.as_deref().unwrap_or_default(),
735 call.callee_name.as_str(),
736 call.target_hint.as_deref().unwrap_or_default(),
737 call.path.as_str(),
738 ],
739 )?;
740 }
741 for chunk in &snapshot.chunks {
742 transaction.execute(
743 "
744 INSERT INTO code_repository_chunks (
745 repository_id, source_scope, chunk_id, file_id, path, language_id, content,
746 byte_start, byte_end, line_start, line_end, symbol_snapshot_id
747 )
748 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)
749 ",
750 params![
751 chunk.repository_id,
752 chunk.source_scope,
753 chunk.chunk_id,
754 chunk.file_id,
755 chunk.path,
756 chunk.language_id,
757 chunk.content,
758 chunk.byte_range.start,
759 chunk.byte_range.end,
760 chunk.line_range.start,
761 chunk.line_range.end,
762 chunk.symbol_snapshot_id,
763 ],
764 )?;
765 insert_search_document(
766 transaction,
767 &chunk.source_scope,
768 "chunk",
769 &chunk.chunk_id,
770 &chunk.path,
771 &chunk.language_id,
772 [
773 chunk.content.as_str(),
774 chunk.symbol_snapshot_id.as_deref().unwrap_or_default(),
775 chunk.path.as_str(),
776 ],
777 )?;
778 }
779 for diagnostic in &snapshot.diagnostics {
780 transaction.execute(
781 "
782 INSERT OR REPLACE INTO code_repository_file_diagnostics
783 (repository_id, source_scope, path, parse_status, message)
784 VALUES (?1, ?2, ?3, ?4, ?5)
785 ",
786 params![
787 diagnostic.repository_id,
788 diagnostic.source_scope,
789 diagnostic.path,
790 diagnostic.parse_status.as_str(),
791 diagnostic.message,
792 ],
793 )?;
794 }
795 for tombstone in &snapshot.tombstones {
796 transaction.execute(
797 "
798 INSERT OR REPLACE INTO code_repository_path_tombstones
799 (repository_id, source_scope, old_path, new_path, base_ref, head_ref)
800 VALUES (?1, ?2, ?3, ?4, ?5, ?6)
801 ",
802 params![
803 tombstone.repository_id,
804 tombstone.source_scope,
805 tombstone.old_path,
806 tombstone.new_path,
807 tombstone.base_ref,
808 tombstone.head_ref,
809 ],
810 )?;
811 }
812
813 Ok(())
814}
815
816fn update_repository_after_snapshot(
817 transaction: &rusqlite::Transaction<'_>,
818 snapshot: &CodeIndexSnapshot,
819) -> Result<(), StorageError> {
820 let file_count = count_code_rows(transaction, "code_repository_files", &snapshot.source_scope)?;
821 let symbol_count = count_code_rows(
822 transaction,
823 "code_repository_symbols",
824 &snapshot.source_scope,
825 )?;
826 let reference_count = count_code_rows(
827 transaction,
828 "code_repository_references",
829 &snapshot.source_scope,
830 )?;
831 let chunk_count = count_code_rows(
832 transaction,
833 "code_repository_chunks",
834 &snapshot.source_scope,
835 )?;
836 let degraded_file_count = count_code_rows(
837 transaction,
838 "code_repository_file_diagnostics",
839 &snapshot.source_scope,
840 )?;
841 let degraded_reason = (degraded_file_count > 0)
842 .then(|| format!("{degraded_file_count} file(s) degraded during code indexing"));
843 let path_filters_json = serde_json::to_string(&snapshot.path_filters)
844 .map_err(|error| StorageError::InvalidInput(error.to_string()))?;
845 let language_filters_json = serde_json::to_string(&snapshot.language_filters)
846 .map_err(|error| StorageError::InvalidInput(error.to_string()))?;
847 transaction.execute(
848 "
849 INSERT INTO code_repository_scopes (
850 source_scope, repository_id, resolved_commit_sha, tree_hash,
851 path_filters_json, language_filters_json, indexed_file_count,
852 symbol_count, reference_count, chunk_count, stale, degraded_reason
853 )
854 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, 0, ?11)
855 ON CONFLICT(source_scope) DO UPDATE SET
856 repository_id = excluded.repository_id,
857 resolved_commit_sha = excluded.resolved_commit_sha,
858 tree_hash = excluded.tree_hash,
859 path_filters_json = excluded.path_filters_json,
860 language_filters_json = excluded.language_filters_json,
861 indexed_file_count = excluded.indexed_file_count,
862 symbol_count = excluded.symbol_count,
863 reference_count = excluded.reference_count,
864 chunk_count = excluded.chunk_count,
865 stale = 0,
866 degraded_reason = excluded.degraded_reason
867 ",
868 params![
869 snapshot.source_scope,
870 snapshot.repository_id,
871 snapshot.resolved_commit_sha,
872 snapshot.tree_hash,
873 path_filters_json,
874 language_filters_json,
875 file_count,
876 symbol_count,
877 reference_count,
878 chunk_count,
879 degraded_reason,
880 ],
881 )?;
882 transaction.execute(
883 "
884 UPDATE code_repositories
885 SET last_indexed_scope_id = ?2,
886 last_indexed_commit = ?3,
887 tree_hash = ?4,
888 state = 'fresh',
889 indexed_file_count = ?5,
890 symbol_count = ?6,
891 reference_count = ?7,
892 chunk_count = ?8,
893 stale = 0,
894 degraded_reason = ?9
895 WHERE repository_id = ?1
896 ",
897 params![
898 snapshot.repository_id,
899 snapshot.source_scope,
900 snapshot.resolved_commit_sha,
901 snapshot.tree_hash,
902 file_count,
903 symbol_count,
904 reference_count,
905 chunk_count,
906 degraded_reason,
907 ],
908 )?;
909
910 Ok(())
911}