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