1use bincode::error::DecodeError;
9use bincode::error::EncodeError;
10use itertools::Itertools;
11use rusqlite::functions::FunctionFlags;
12use rusqlite::types::ValueRef;
13use rusqlite::Connection;
14use rusqlite::OptionalExtension;
15use rusqlite::Params;
16use rusqlite::Statement;
17use std::collections::HashSet;
18use std::path::Path;
19use std::path::PathBuf;
20use thiserror::Error;
21
22use crate::arena::Handle;
23use crate::graph::Degree;
24use crate::graph::File;
25use crate::graph::Node;
26use crate::graph::StackGraph;
27use crate::partial::PartialPath;
28use crate::partial::PartialPaths;
29use crate::partial::PartialSymbolStack;
30use crate::serde;
31use crate::serde::FileFilter;
32use crate::stitching::Database;
33use crate::stitching::ForwardCandidates;
34use crate::CancellationError;
35use crate::CancellationFlag;
36
37const VERSION: usize = 6;
38
39const SCHEMA: &str = r#"
40 CREATE TABLE metadata (
41 version INTEGER NOT NULL
42 ) STRICT;
43 CREATE TABLE graphs (
44 file TEXT PRIMARY KEY,
45 tag TEXT NOT NULL,
46 error TEXT,
47 value BLOB NOT NULL
48 ) STRICT;
49 CREATE TABLE file_paths (
50 file TEXT NOT NULL,
51 local_id INTEGER NOT NULL,
52 value BLOB NOT NULL,
53 FOREIGN KEY(file) REFERENCES graphs(file)
54 ) STRICT;
55 CREATE TABLE root_paths (
56 file TEXT NOT NULL,
57 symbol_stack TEXT NOT NULL,
58 value BLOB NOT NULL,
59 FOREIGN KEY(file) REFERENCES graphs(file)
60 ) STRICT;
61 "#;
62
63const INDEXES: &str = r#"
64 CREATE INDEX IF NOT EXISTS idx_graphs_file ON graphs(file);
65 CREATE INDEX IF NOT EXISTS idx_file_paths_local_id ON file_paths(file, local_id);
66 CREATE INDEX IF NOT EXISTS idx_root_paths_symbol_stack ON root_paths(symbol_stack);
67 "#;
68
69const PRAGMAS: &str = r#"
70 PRAGMA journal_mode = WAL;
71 PRAGMA foreign_keys = false;
72 PRAGMA secure_delete = false;
73 "#;
74
75pub static BINCODE_CONFIG: bincode::config::Configuration = bincode::config::standard();
76
77#[derive(Debug, Error)]
78pub enum StorageError {
79 #[error("cancelled at {0}")]
80 Cancelled(&'static str),
81 #[error("unsupported database version {0}")]
82 IncorrectVersion(usize),
83 #[error("database does not exist {0}")]
84 MissingDatabase(String),
85 #[error(transparent)]
86 Rusqlite(#[from] rusqlite::Error),
87 #[error(transparent)]
88 Serde(#[from] serde::Error),
89 #[error(transparent)]
90 SerializeFail(#[from] EncodeError),
91 #[error(transparent)]
92 DeserializeFail(#[from] DecodeError),
93}
94
95pub type Result<T> = std::result::Result<T, StorageError>;
96
97impl From<CancellationError> for StorageError {
98 fn from(value: CancellationError) -> Self {
99 Self::Cancelled(value.0)
100 }
101}
102
103pub enum FileStatus {
105 Missing,
106 Indexed,
107 Error(String),
108}
109
110impl<'a> From<ValueRef<'a>> for FileStatus {
111 fn from(value: ValueRef<'a>) -> Self {
112 match value {
113 ValueRef::Null => Self::Indexed,
114 ValueRef::Text(error) => Self::Error(
115 std::str::from_utf8(error)
116 .expect("invalid error encoding in database")
117 .to_string(),
118 ),
119 _ => panic!("invalid value type in database"),
120 }
121 }
122}
123
124pub struct FileEntry {
126 pub path: PathBuf,
127 pub tag: String,
128 pub status: FileStatus,
129}
130
131pub struct Files<'a, P: Params>(Statement<'a>, P);
133
134impl<'a, P: Params + Clone> Files<'a, P> {
135 pub fn try_iter<'b>(&'b mut self) -> Result<impl Iterator<Item = Result<FileEntry>> + 'b> {
136 let entries = self.0.query_map(self.1.clone(), |r| {
137 Ok(FileEntry {
138 path: PathBuf::from(r.get::<_, String>(0)?),
139 tag: r.get::<_, String>(1)?,
140 status: r.get_ref(2)?.into(),
141 })
142 })?;
143 let entries = entries.map(|r| -> Result<FileEntry> { Ok(r?) });
144 Ok(entries)
145 }
146}
147
148pub struct SQLiteWriter {
150 conn: Connection,
151}
152
153impl SQLiteWriter {
154 pub fn open_in_memory() -> Result<Self> {
156 let mut conn = Connection::open_in_memory()?;
157 Self::init(&mut conn)?;
158 init_indexes(&mut conn)?;
159 Ok(Self { conn })
160 }
161
162 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
165 let is_new = !path.as_ref().exists();
166 let mut conn = Connection::open(path)?;
167 set_pragmas_and_functions(&conn)?;
168 if is_new {
169 Self::init(&mut conn)?;
170 } else {
171 check_version(&conn)?;
172 }
173 init_indexes(&mut conn)?;
174 Ok(Self { conn })
175 }
176
177 fn init(conn: &mut Connection) -> Result<()> {
179 let tx = conn.transaction()?;
180 tx.execute_batch(SCHEMA)?;
181 tx.execute("INSERT INTO metadata (version) VALUES (?)", [VERSION])?;
182 tx.commit()?;
183 Ok(())
184 }
185
186 pub fn clean_all(&mut self) -> Result<usize> {
188 let tx = self.conn.transaction()?;
189 let count = Self::clean_all_inner(&tx)?;
190 tx.commit()?;
191 Ok(count)
192 }
193
194 fn clean_all_inner(conn: &Connection) -> Result<usize> {
198 {
199 let mut stmt = conn.prepare_cached("DELETE FROM file_paths")?;
200 stmt.execute([])?;
201 }
202 {
203 let mut stmt = conn.prepare_cached("DELETE FROM root_paths")?;
204 stmt.execute([])?;
205 }
206 let count = {
207 let mut stmt = conn.prepare_cached("DELETE FROM graphs")?;
208 stmt.execute([])?
209 };
210 Ok(count)
211 }
212
213 pub fn clean_file(&mut self, file: &Path) -> Result<usize> {
216 let tx = self.conn.transaction()?;
217 let count = Self::clean_file_inner(&tx, file)?;
218 tx.commit()?;
219 Ok(count)
220 }
221
222 fn clean_file_inner(conn: &Connection, file: &Path) -> Result<usize> {
226 let file = file.to_string_lossy();
227 {
228 let mut stmt = conn.prepare_cached("DELETE FROM file_paths WHERE file=?")?;
229 stmt.execute([&file])?;
230 }
231 {
232 let mut stmt = conn.prepare_cached("DELETE FROM root_paths WHERE file=?")?;
233 stmt.execute([&file])?;
234 }
235 let count = {
236 let mut stmt = conn.prepare_cached("DELETE FROM graphs WHERE file=?")?;
237 stmt.execute([&file])?
238 };
239 Ok(count)
240 }
241
242 pub fn clean_file_or_directory(&mut self, file_or_directory: &Path) -> Result<usize> {
245 let tx = self.conn.transaction()?;
246 let count = Self::clean_file_or_directory_inner(&tx, file_or_directory)?;
247 tx.commit()?;
248 Ok(count)
249 }
250
251 fn clean_file_or_directory_inner(conn: &Connection, file_or_directory: &Path) -> Result<usize> {
256 let file_or_directory = file_or_directory.to_string_lossy();
257 {
258 let mut stmt =
259 conn.prepare_cached("DELETE FROM file_paths WHERE path_descendant_of(file, ?)")?;
260 stmt.execute([&file_or_directory])?;
261 }
262 {
263 let mut stmt =
264 conn.prepare_cached("DELETE FROM root_paths WHERE path_descendant_of(file, ?)")?;
265 stmt.execute([&file_or_directory])?;
266 }
267 let count = {
268 let mut stmt =
269 conn.prepare_cached("DELETE FROM graphs WHERE path_descendant_of(file, ?)")?;
270 stmt.execute([&file_or_directory])?
271 };
272 Ok(count)
273 }
274
275 pub fn store_error_for_file(&mut self, file: &Path, tag: &str, error: &str) -> Result<()> {
277 let tx = self.conn.transaction()?;
278 Self::store_error_for_file_inner(&tx, file, tag, error)?;
279 tx.commit()?;
280 Ok(())
281 }
282
283 fn store_error_for_file_inner(
287 conn: &Connection,
288 file: &Path,
289 tag: &str,
290 error: &str,
291 ) -> Result<()> {
292 copious_debugging!("--> Store error for {}", file.display());
293 let mut stmt = conn
294 .prepare_cached("INSERT INTO graphs (file, tag, error, value) VALUES (?, ?, ?, ?)")?;
295 let graph = crate::serde::StackGraph::default();
296 let serialized = bincode::encode_to_vec(&graph, BINCODE_CONFIG)?;
297 stmt.execute((&file.to_string_lossy(), tag, error, serialized))?;
298 Ok(())
299 }
300
301 pub fn store_result_for_file<'a, IP>(
303 &mut self,
304 graph: &StackGraph,
305 file: Handle<File>,
306 tag: &str,
307 partials: &mut PartialPaths,
308 paths: IP,
309 ) -> Result<()>
310 where
311 IP: IntoIterator<Item = &'a PartialPath>,
312 {
313 let path = Path::new(graph[file].name());
314 let tx = self.conn.transaction()?;
315 Self::clean_file_inner(&tx, path)?;
316 Self::store_graph_for_file_inner(&tx, graph, file, tag)?;
317 Self::store_partial_paths_for_file_inner(&tx, graph, file, partials, paths)?;
318 tx.commit()?;
319 Ok(())
320 }
321
322 fn store_graph_for_file_inner(
326 conn: &Connection,
327 graph: &StackGraph,
328 file: Handle<File>,
329 tag: &str,
330 ) -> Result<()> {
331 let file_str = graph[file].name();
332 copious_debugging!("--> Store graph for {}", file_str);
333 let mut stmt =
334 conn.prepare_cached("INSERT INTO graphs (file, tag, value) VALUES (?, ?, ?)")?;
335 let graph = serde::StackGraph::from_graph_filter(graph, &FileFilter(file));
336 let serialized = bincode::encode_to_vec(&graph, BINCODE_CONFIG)?;
337 stmt.execute((file_str, tag, &serialized))?;
338 Ok(())
339 }
340
341 fn store_partial_paths_for_file_inner<'a, IP>(
345 conn: &Connection,
346 graph: &StackGraph,
347 file: Handle<File>,
348 partials: &mut PartialPaths,
349 paths: IP,
350 ) -> Result<()>
351 where
352 IP: IntoIterator<Item = &'a PartialPath>,
353 {
354 let file_str = graph[file].name();
355 let mut node_stmt =
356 conn.prepare_cached("INSERT INTO file_paths (file, local_id, value) VALUES (?, ?, ?)")?;
357 let mut root_stmt = conn.prepare_cached(
358 "INSERT INTO root_paths (file, symbol_stack, value) VALUES (?, ?, ?)",
359 )?;
360 #[cfg_attr(not(feature = "copious-debugging"), allow(unused))]
361 let mut node_path_count = 0usize;
362 #[cfg_attr(not(feature = "copious-debugging"), allow(unused))]
363 let mut root_path_count = 0usize;
364 for path in paths {
365 copious_debugging!(
366 "--> Add {} partial path {}",
367 file_str,
368 path.display(graph, partials)
369 );
370 let start_node = graph[path.start_node].id();
371 if start_node.is_root() {
372 copious_debugging!(
373 " * Add as root path with symbol stack {}",
374 path.symbol_stack_precondition.display(graph, partials),
375 );
376 let symbol_stack = path.symbol_stack_precondition.storage_key(graph, partials);
377 let path = serde::PartialPath::from_partial_path(graph, partials, path);
378 let serialized = bincode::encode_to_vec(&path, BINCODE_CONFIG)?;
379 root_stmt.execute((file_str, symbol_stack, serialized))?;
380 root_path_count += 1;
381 } else if start_node.is_in_file(file) {
382 copious_debugging!(
383 " * Add as node path from node {}",
384 path.start_node.display(graph),
385 );
386 let path = serde::PartialPath::from_partial_path(graph, partials, path);
387 let serialized = bincode::encode_to_vec(&path, BINCODE_CONFIG)?;
388 node_stmt.execute((file_str, path.start_node.local_id, serialized))?;
389 node_path_count += 1;
390 } else {
391 panic!(
392 "added path {} must start in given file {} or at root",
393 path.display(graph, partials),
394 graph[file].name()
395 );
396 }
397 copious_debugging!(
398 " * Added {} node paths and {} root paths",
399 node_path_count,
400 root_path_count,
401 );
402 }
403 Ok(())
404 }
405
406 pub fn status_for_file(&mut self, file: &str, tag: Option<&str>) -> Result<FileStatus> {
409 status_for_file(&self.conn, file, tag)
410 }
411
412 pub fn into_reader(self) -> SQLiteReader {
414 SQLiteReader {
415 conn: self.conn,
416 loaded_graphs: HashSet::new(),
417 loaded_node_paths: HashSet::new(),
418 loaded_root_paths: HashSet::new(),
419 graph: StackGraph::new(),
420 partials: PartialPaths::new(),
421 db: Database::new(),
422 stats: Stats::default(),
423 }
424 }
425}
426
427pub struct SQLiteReader {
429 conn: Connection,
430 loaded_graphs: HashSet<String>,
431 loaded_node_paths: HashSet<Handle<Node>>,
432 loaded_root_paths: HashSet<String>,
433 graph: StackGraph,
434 partials: PartialPaths,
435 db: Database,
436 stats: Stats,
437}
438
439impl SQLiteReader {
440 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
442 if !path.as_ref().exists() {
443 return Err(StorageError::MissingDatabase(
444 path.as_ref().to_string_lossy().to_string(),
445 ));
446 }
447 let mut conn = Connection::open(path)?;
448 set_pragmas_and_functions(&conn)?;
449 check_version(&conn)?;
450 init_indexes(&mut conn)?;
451 Ok(Self {
452 conn,
453 loaded_graphs: HashSet::new(),
454 loaded_node_paths: HashSet::new(),
455 loaded_root_paths: HashSet::new(),
456 graph: StackGraph::new(),
457 partials: PartialPaths::new(),
458 db: Database::new(),
459 stats: Stats::default(),
460 })
461 }
462
463 pub fn clear(&mut self) {
466 self.loaded_graphs.clear();
467 self.graph = StackGraph::new();
468
469 self.loaded_node_paths.clear();
470 self.loaded_root_paths.clear();
471 self.partials.clear();
472 self.db.clear();
473
474 self.stats.clear();
475 }
476
477 pub fn clear_paths(&mut self) {
481 self.loaded_node_paths.clear();
482 self.loaded_root_paths.clear();
483 self.partials.clear();
484 self.db.clear();
485
486 self.stats.clear_paths();
487 }
488
489 pub fn status_for_file<T: AsRef<str>>(
492 &mut self,
493 file: &str,
494 tag: Option<T>,
495 ) -> Result<FileStatus> {
496 status_for_file(&self.conn, file, tag)
497 }
498
499 pub fn list_all<'a>(&'a mut self) -> Result<Files<'a, ()>> {
501 self.conn
502 .prepare("SELECT file, tag, error FROM graphs")
503 .map(|stmt| Files(stmt, ()))
504 .map_err(|e| e.into())
505 }
506
507 pub fn list_file_or_directory<'a>(
510 &'a self,
511 file_or_directory: &Path,
512 ) -> Result<Files<'a, [String; 1]>> {
513 Self::list_file_or_directory_inner(&self.conn, file_or_directory)
514 }
515
516 fn list_file_or_directory_inner<'a>(
517 conn: &'a Connection,
518 file_or_directory: &Path,
519 ) -> Result<Files<'a, [String; 1]>> {
520 let file_or_directory = file_or_directory.to_string_lossy().to_string();
521 conn.prepare("SELECT file, tag, error FROM graphs WHERE path_descendant_of(file, ?)")
522 .map(|stmt| Files(stmt, [file_or_directory]))
523 .map_err(|e| e.into())
524 }
525
526 pub fn load_graph_for_file(&mut self, file: &str) -> Result<Handle<File>> {
528 Self::load_graph_for_file_inner(
529 file,
530 &mut self.graph,
531 &mut self.loaded_graphs,
532 &self.conn,
533 &mut self.stats,
534 )
535 }
536
537 fn load_graph_for_file_inner(
538 file: &str,
539 graph: &mut StackGraph,
540 loaded_graphs: &mut HashSet<String>,
541 conn: &Connection,
542 stats: &mut Stats,
543 ) -> Result<Handle<File>> {
544 copious_debugging!("--> Load graph for {}", file);
545 if !loaded_graphs.insert(file.to_string()) {
546 copious_debugging!(" * Already loaded");
547 stats.file_cached += 1;
548 return Ok(graph.get_file(file).expect("loaded file to exist"));
549 }
550 copious_debugging!(" * Load from database");
551 stats.file_loads += 1;
552 let mut stmt = conn.prepare_cached("SELECT value FROM graphs WHERE file = ?")?;
553 let value = stmt.query_row([file], |row| row.get::<_, Vec<u8>>(0))?;
554 let (file_graph, _): (serde::StackGraph, usize) =
555 bincode::decode_from_slice(&value, BINCODE_CONFIG)?;
556 file_graph.load_into(graph)?;
557 Ok(graph.get_file(file).expect("loaded file to exist"))
558 }
559
560 pub fn load_graphs_for_file_or_directory(
561 &mut self,
562 file_or_directory: &Path,
563 cancellation_flag: &dyn CancellationFlag,
564 ) -> Result<()> {
565 for file in Self::list_file_or_directory_inner(&self.conn, file_or_directory)?.try_iter()? {
566 cancellation_flag.check("loading graphs")?;
567 let file = file?;
568 Self::load_graph_for_file_inner(
569 &file.path.to_string_lossy(),
570 &mut self.graph,
571 &mut self.loaded_graphs,
572 &self.conn,
573 &mut self.stats,
574 )?;
575 }
576 Ok(())
577 }
578
579 fn load_paths_for_node(
581 &mut self,
582 node: Handle<Node>,
583 cancellation_flag: &dyn CancellationFlag,
584 ) -> Result<()> {
585 copious_debugging!(" * Load extensions from node {}", node.display(&self.graph));
586 if !self.loaded_node_paths.insert(node) {
587 copious_debugging!(" > Already loaded");
588 self.stats.node_path_cached += 1;
589 return Ok(());
590 }
591 self.stats.node_path_loads += 1;
592 let id = self.graph[node].id();
593 let file = id.file().expect("file node required");
594 let file = self.graph[file].name();
595 let mut stmt = self
596 .conn
597 .prepare_cached("SELECT file,value from file_paths WHERE file = ? AND local_id = ?")?;
598 let paths = stmt.query_map((file, id.local_id()), |row| {
599 let file = row.get::<_, String>(0)?;
600 let value = row.get::<_, Vec<u8>>(1)?;
601 Ok((file, value))
602 })?;
603 #[cfg_attr(not(feature = "copious-debugging"), allow(unused))]
604 let mut count = 0usize;
605 for path in paths {
606 cancellation_flag.check("loading node paths")?;
607 let (file, value) = path?;
608 Self::load_graph_for_file_inner(
609 &file,
610 &mut self.graph,
611 &mut self.loaded_graphs,
612 &self.conn,
613 &mut self.stats,
614 )?;
615 let (path, _): (serde::PartialPath, usize) =
616 bincode::decode_from_slice(&value, BINCODE_CONFIG)?;
617 let path = path.to_partial_path(&mut self.graph, &mut self.partials)?;
618 copious_debugging!(
619 " > Loaded {}",
620 path.display(&self.graph, &mut self.partials)
621 );
622 self.db
623 .add_partial_path(&self.graph, &mut self.partials, path);
624 count += 1;
625 }
626 copious_debugging!(" > Loaded {}", count);
627 Ok(())
628 }
629
630 fn load_paths_for_root(
632 &mut self,
633 symbol_stack: PartialSymbolStack,
634 cancellation_flag: &dyn CancellationFlag,
635 ) -> Result<()> {
636 copious_debugging!(
637 " * Load extensions from root with symbol stack {}",
638 symbol_stack.display(&self.graph, &mut self.partials)
639 );
640 let mut stmt = self.conn.prepare_cached(
641 "SELECT file,value from root_paths WHERE symbol_stack LIKE ? ESCAPE ?",
642 )?;
643 let (symbol_stack_patterns, escape) =
644 symbol_stack.storage_key_patterns(&self.graph, &mut self.partials);
645 for symbol_stack in symbol_stack_patterns {
646 copious_debugging!(
647 " * Load extensions from root with prefix symbol stack {}",
648 symbol_stack
649 );
650 if !self.loaded_root_paths.insert(symbol_stack.clone()) {
651 copious_debugging!(" > Already loaded");
652 self.stats.root_path_cached += 1;
653 continue;
654 }
655 self.stats.root_path_loads += 1;
656 let paths = stmt.query_map([symbol_stack, escape.clone()], |row| {
657 let file = row.get::<_, String>(0)?;
658 let value = row.get::<_, Vec<u8>>(1)?;
659 Ok((file, value))
660 })?;
661 #[cfg_attr(not(feature = "copious-debugging"), allow(unused))]
662 let mut count = 0usize;
663 for path in paths {
664 cancellation_flag.check("loading root paths")?;
665 let (file, value) = path?;
666 Self::load_graph_for_file_inner(
667 &file,
668 &mut self.graph,
669 &mut self.loaded_graphs,
670 &self.conn,
671 &mut self.stats,
672 )?;
673 let (path, _): (serde::PartialPath, usize) =
674 bincode::decode_from_slice(&value, BINCODE_CONFIG)?;
675 let path = path.to_partial_path(&mut self.graph, &mut self.partials)?;
676 copious_debugging!(
677 " > Loaded {}",
678 path.display(&self.graph, &mut self.partials)
679 );
680 self.db
681 .add_partial_path(&self.graph, &mut self.partials, path);
682 count += 1;
683 }
684 copious_debugging!(" > Loaded {}", count);
685 }
686 Ok(())
687 }
688
689 pub fn load_partial_path_extensions(
691 &mut self,
692 path: &PartialPath,
693 cancellation_flag: &dyn CancellationFlag,
694 ) -> Result<()> {
695 copious_debugging!(
696 "--> Load extensions for {}",
697 path.display(&self.graph, &mut self.partials)
698 );
699 let end_node = self.graph[path.end_node].id();
700 if self.graph[path.end_node].file().is_some() {
701 self.load_paths_for_node(path.end_node, cancellation_flag)?;
702 } else if end_node.is_root() {
703 self.load_paths_for_root(path.symbol_stack_postcondition, cancellation_flag)?;
704 }
705 Ok(())
706 }
707
708 pub fn get(&mut self) -> (&mut StackGraph, &mut PartialPaths, &mut Database) {
710 (&mut self.graph, &mut self.partials, &mut self.db)
711 }
712
713 pub fn stats(&self) -> Stats {
715 self.stats.clone()
716 }
717}
718
719impl PartialSymbolStack {
725 fn storage_key(self, graph: &StackGraph, partials: &mut PartialPaths) -> String {
727 let mut key = String::new();
728 match self.has_variable() {
729 true => key += "V\u{241E}",
730 false => key += "X\u{241E}",
731 }
732 key += &self
733 .iter(partials)
734 .map(|s| &graph[s.symbol])
735 .join("\u{241F}");
736 key
737 }
738
739 fn storage_key_patterns(
742 mut self,
743 graph: &StackGraph,
744 partials: &mut PartialPaths,
745 ) -> (Vec<String>, String) {
746 let mut key_patterns = Vec::new();
747 let mut symbols = String::new();
748 while let Some(symbol) = self.pop_front(partials) {
749 if !symbols.is_empty() {
750 symbols += "\u{241F}";
751 }
752 let symbol = graph[symbol.symbol]
753 .replace("%", "\\%")
754 .replace("_", "\\_")
755 .to_string();
756 symbols += &symbol;
757 key_patterns.push("V\u{241E}".to_string() + &symbols);
759 }
760 key_patterns.push("X\u{241E}".to_string() + &symbols);
762 if self.has_variable() {
763 key_patterns.push("_\u{241E}".to_string() + &symbols + "\u{241F}%");
765 }
766 (key_patterns, "\\".to_string())
767 }
768}
769
770impl ForwardCandidates<Handle<PartialPath>, PartialPath, Database, StorageError> for SQLiteReader {
771 fn load_forward_candidates(
772 &mut self,
773 path: &PartialPath,
774 cancellation_flag: &dyn CancellationFlag,
775 ) -> std::result::Result<(), StorageError> {
776 self.load_partial_path_extensions(path, cancellation_flag)
777 }
778
779 fn get_forward_candidates<R>(&mut self, path: &PartialPath, result: &mut R)
780 where
781 R: std::iter::Extend<Handle<PartialPath>>,
782 {
783 self.db
784 .find_candidate_partial_paths(&self.graph, &mut self.partials, path, result);
785 }
786
787 fn get_joining_candidate_degree(&self, path: &PartialPath) -> Degree {
788 self.db.get_incoming_path_degree(path.end_node)
789 }
790
791 fn get_graph_partials_and_db(&mut self) -> (&StackGraph, &mut PartialPaths, &Database) {
792 (&self.graph, &mut self.partials, &self.db)
793 }
794}
795
796#[derive(Clone, Debug, Default)]
797pub struct Stats {
798 pub file_loads: usize,
799 pub file_cached: usize,
800 pub root_path_loads: usize,
801 pub root_path_cached: usize,
802 pub node_path_loads: usize,
803 pub node_path_cached: usize,
804}
805
806impl Stats {
807 fn clear(&mut self) {
808 *self = Stats::default();
809 }
810
811 fn clear_paths(&mut self) {
812 *self = Stats {
813 file_loads: self.file_loads,
814 file_cached: self.file_cached,
815 ..Stats::default()
816 }
817 }
818}
819
820fn check_version(conn: &Connection) -> Result<()> {
822 let version = conn.query_row("SELECT version FROM metadata", [], |r| r.get::<_, usize>(0))?;
823 if version != VERSION {
824 return Err(StorageError::IncorrectVersion(version));
825 }
826 Ok(())
827}
828
829fn set_pragmas_and_functions(conn: &Connection) -> Result<()> {
830 conn.execute_batch(PRAGMAS)?;
831 conn.create_scalar_function(
832 "path_descendant_of",
833 2,
834 FunctionFlags::SQLITE_DETERMINISTIC | FunctionFlags::SQLITE_UTF8,
835 move |ctx| {
836 assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
837 let path = PathBuf::from(ctx.get::<String>(0)?);
838 let parent = PathBuf::from(ctx.get::<String>(1)?);
839 let result = path.starts_with(&parent);
840 Ok(result)
841 },
842 )?;
843 Ok(())
844}
845
846fn init_indexes(conn: &mut Connection) -> Result<()> {
847 let tx = conn.transaction()?;
848 tx.execute_batch(INDEXES)?;
849 tx.commit()?;
850 Ok(())
851}
852
853fn status_for_file<T: AsRef<str>>(
854 conn: &Connection,
855 file: &str,
856 tag: Option<T>,
857) -> Result<FileStatus> {
858 let result = if let Some(tag) = tag {
859 let mut stmt =
860 conn.prepare_cached("SELECT error FROM graphs WHERE file = ? AND tag = ?")?;
861 stmt.query_row([file, tag.as_ref()], |r| r.get_ref(0).map(FileStatus::from))
862 .optional()?
863 .unwrap_or(FileStatus::Missing)
864 } else {
865 let mut stmt = conn.prepare_cached("SELECT status FROM graphs WHERE file = ?")?;
866 stmt.query_row([file], |r| r.get_ref(0).map(FileStatus::from))
867 .optional()?
868 .unwrap_or(FileStatus::Missing)
869 };
870 Ok(result)
871}