assetpack_core/sqlite/
pack.rs1use std::path::{Path, PathBuf};
2
3use rusqlite::{Connection, OpenFlags};
4
5use super::{MerkleProof, MerkleSummary, RusqliteStore, SqliteLayout, SqliteStoreOptions};
6use crate::{Codec, Hash32, ObjectRecord, ObjectSource, Result, VerifiedObject};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum SqlitePackJournalMode {
10 Delete,
11 Wal,
12}
13
14pub struct SqlitePack {
15 connection: Connection,
16 path: PathBuf,
17 journal_mode: SqlitePackJournalMode,
18 options: SqliteStoreOptions,
19}
20
21impl SqlitePack {
22 pub fn open(path: impl AsRef<Path>) -> Result<Self> {
23 Self::open_with_journal(path, SqlitePackJournalMode::Delete)
24 }
25
26 pub fn open_with_journal(path: impl AsRef<Path>, journal_mode: SqlitePackJournalMode) -> Result<Self> {
27 let path = path.as_ref().to_path_buf();
28 if let Some(parent) = path.parent() {
29 std::fs::create_dir_all(parent)?;
30 }
31 let connection = Connection::open(&path)?;
32 connection.pragma_update(
33 None,
34 "journal_mode",
35 match journal_mode {
36 SqlitePackJournalMode::Delete => "DELETE",
37 SqlitePackJournalMode::Wal => "WAL",
38 },
39 )?;
40 let options = SqliteStoreOptions::default();
41 RusqliteStore::ensure_schema_with_options(&connection, &options)?;
42 Ok(Self {
43 connection,
44 path,
45 journal_mode,
46 options,
47 })
48 }
49
50 pub fn open_readonly(path: impl AsRef<Path>) -> Result<Self> {
51 let path = path.as_ref().to_path_buf();
52 let connection = Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_ONLY)?;
53 Ok(Self {
54 connection,
55 path,
56 journal_mode: SqlitePackJournalMode::Delete,
57 options: SqliteStoreOptions::default(),
58 })
59 }
60
61 pub fn path(&self) -> &Path {
62 &self.path
63 }
64
65 pub fn close(self) -> Result<()> {
66 if self.journal_mode == SqlitePackJournalMode::Wal {
67 self.connection.execute_batch("PRAGMA wal_checkpoint(TRUNCATE)")?;
68 }
69 self.connection.close().map_err(|(_, error)| error)?;
70 Ok(())
71 }
72
73 pub fn transaction<T>(&mut self, operation: impl FnOnce(&RusqliteStore<'_>) -> Result<T>) -> Result<T> {
74 let transaction = self.connection.transaction()?;
75 let result = {
76 let layout = SqliteLayout::new(self.options.namespace.as_deref())?;
77 let store = RusqliteStore::view(&transaction, self.options.clone(), layout);
78 operation(&store)?
79 };
80 transaction.commit()?;
81 Ok(result)
82 }
83
84 pub fn put_objects_batch(&self, objects: &[ObjectRecord]) -> Result<()> {
85 self.store()?.put_objects_batch(objects)
86 }
87
88 pub fn put_file_recipe_cache_batch(&self, entries: &[(Hash32, Hash32)]) -> Result<()> {
89 self.store()?.put_file_recipe_cache_batch(entries)
90 }
91
92 pub fn put_chunk(&self, hash: Hash32, content: &[u8], codec: Codec) -> Result<()> {
93 self.store()?.put_chunk(hash, content, codec)
94 }
95
96 pub fn put_recipe(&self, hash: Hash32, content: &[u8], codec: Codec) -> Result<()> {
97 self.store()?.put_recipe(hash, content, codec)
98 }
99
100 pub fn existing_hashes(&self, hashes: &[Hash32]) -> Result<std::collections::HashSet<Hash32>> {
101 self.store()?.existing_hashes(hashes)
102 }
103
104 pub fn chunk_stored_bytes(&self, hash: &Hash32) -> Result<Option<u64>> {
105 self.store()?.chunk_stored_bytes(hash)
106 }
107
108 pub fn chunk_sizes(&self) -> Result<Vec<(Hash32, u64)>> {
109 self.store()?.chunk_sizes()
110 }
111
112 pub fn file_recipe_cache(&self) -> Result<std::collections::HashMap<Hash32, Hash32>> {
113 self.store()?.file_recipe_cache()
114 }
115
116 pub fn recipe_for_file_hash(&self, hash: &Hash32) -> Result<Option<Hash32>> {
117 self.store()?.recipe_for_file_hash(hash)
118 }
119
120 pub fn all_objects(&self) -> Result<Vec<VerifiedObject>> {
121 self.store()?.all_objects()
122 }
123
124 pub fn object_records(&self, offset: u64, limit: usize) -> Result<Vec<ObjectRecord>> {
125 self.store()?.object_records(offset, limit)
126 }
127
128 pub fn rebuild_merkle_index(&self) -> Result<MerkleSummary> {
129 self.store()?.rebuild_merkle_index()
130 }
131
132 pub fn merkle_summary(&self) -> Result<MerkleSummary> {
133 self.store()?.merkle_summary()
134 }
135
136 pub fn recompute_merkle_summary(&self) -> Result<MerkleSummary> {
137 self.store()?.recompute_merkle_summary()
138 }
139
140 pub fn prove_membership(&self, hash: &Hash32) -> Result<Option<MerkleProof>> {
141 self.store()?.prove_membership(hash)
142 }
143
144 pub fn prove_memberships_batch(&self, hashes: &[Hash32]) -> Result<Vec<Option<MerkleProof>>> {
145 self.store()?.prove_memberships_batch(hashes)
146 }
147
148 pub fn verify_proof(target: &Hash32, proof: &MerkleProof, expected_root: &Hash32, leaf_count: u64) -> bool {
149 RusqliteStore::verify_proof(target, proof, expected_root, leaf_count)
150 }
151
152 pub fn integrity_check(&self) -> Result<()> {
153 self.store()?.integrity_check()
154 }
155
156 pub fn verified_object_hashes(&self) -> Result<Vec<Hash32>> {
157 self.store()?.verified_object_hashes()
158 }
159
160 fn store(&self) -> Result<RusqliteStore<'_>> {
161 let layout = SqliteLayout::new(self.options.namespace.as_deref())?;
162 Ok(RusqliteStore::view(&self.connection, self.options.clone(), layout))
163 }
164}
165
166impl ObjectSource for SqlitePack {
167 fn read_object(&self, hash: &Hash32) -> Result<Option<VerifiedObject>> {
168 self.store()?.read_object(hash)
169 }
170}