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