assetpack_core/sqlite/
mod.rs1mod common;
2#[cfg(feature = "sqlite-pack")]
3mod pack;
4#[cfg(any(feature = "sqlite-pack", feature = "rusqlite-store"))]
5mod rusqlite;
6#[cfg(feature = "sqlx-store")]
7mod sqlx;
8mod statements;
9
10pub use common::{MerkleMode, MerkleProof, MerkleSummary, SqliteStoreOptions};
11pub(in crate::sqlite) use common::{
12 SqliteLayout, StoredObjectRow, codec_to_str, hash_pair, merkle_root_from_hashes, object_record_from_row, prepare_object,
13 schema_statements, verified_object_from_row,
14};
15#[cfg(feature = "sqlite-pack")]
16pub use pack::{SqlitePack, SqlitePackJournalMode};
17#[cfg(any(feature = "sqlite-pack", feature = "rusqlite-store"))]
18pub use rusqlite::RusqliteStore;
19#[cfg(feature = "sqlx-store")]
20pub use sqlx::SqlxStore;
21
22#[cfg(all(test, feature = "rusqlite-store"))]
23mod tests {
24 use super::{RusqliteStore, SqliteStoreOptions};
25 use crate::{Codec, Hash32, ObjectKind, ObjectRecord, ObjectSource};
26
27 fn record(bytes: &[u8]) -> ObjectRecord {
28 ObjectRecord {
29 hash: Hash32::sha3_256(bytes),
30 kind: ObjectKind::Chunk,
31 decoded_len: bytes.len() as u64,
32 codec: Codec::Raw,
33 stored_bytes: bytes.to_vec(),
34 }
35 }
36
37 #[test]
38 fn rusqlite_store_joins_host_commit_and_rollback() {
39 let mut connection = rusqlite::Connection::open_in_memory().unwrap();
40 connection
41 .execute("CREATE TABLE app_history (id INTEGER PRIMARY KEY, label TEXT NOT NULL)", [])
42 .unwrap();
43 RusqliteStore::ensure_schema(&connection).unwrap();
44 let object = record(b"transaction-object");
45
46 {
47 let transaction = connection.transaction().unwrap();
48 transaction
49 .execute("INSERT INTO app_history (label) VALUES ('rollback')", [])
50 .unwrap();
51 RusqliteStore::from_connection(&transaction)
52 .unwrap()
53 .put_objects_batch(std::slice::from_ref(&object))
54 .unwrap();
55 transaction.rollback().unwrap();
56 }
57 assert_eq!(
58 connection
59 .query_row("SELECT COUNT(*) FROM app_history", [], |row| row.get::<_, i64>(0))
60 .unwrap(),
61 0
62 );
63 assert!(
64 RusqliteStore::from_connection(&connection)
65 .unwrap()
66 .read_object(&object.hash)
67 .unwrap()
68 .is_none()
69 );
70
71 {
72 let transaction = connection.transaction().unwrap();
73 transaction
74 .execute("INSERT INTO app_history (label) VALUES ('commit')", [])
75 .unwrap();
76 RusqliteStore::from_connection(&transaction)
77 .unwrap()
78 .put_objects_batch(std::slice::from_ref(&object))
79 .unwrap();
80 transaction.commit().unwrap();
81 }
82 assert_eq!(
83 connection
84 .query_row("SELECT COUNT(*) FROM app_history", [], |row| row.get::<_, i64>(0))
85 .unwrap(),
86 1
87 );
88 assert_eq!(
89 RusqliteStore::from_connection(&connection)
90 .unwrap()
91 .read_object(&object.hash)
92 .unwrap()
93 .unwrap()
94 .bytes,
95 b"transaction-object"
96 );
97 }
98
99 #[cfg(feature = "sqlx-store")]
100 #[tokio::test]
101 async fn sqlite_schema_and_objects_roundtrip_across_drivers_with_prefix() {
102 use sqlx::{Row, sqlite::SqliteConnectOptions};
103
104 use super::SqlxStore;
105 use crate::AsyncObjectSource;
106
107 let directory = tempfile::tempdir().unwrap();
108 let path = directory.path().join("cross-driver.db");
109 let first = record(b"written-by-rusqlite");
110 {
111 let connection = rusqlite::Connection::open(&path).unwrap();
112 let store = RusqliteStore::from_connection_with_options(
113 &connection,
114 SqliteStoreOptions {
115 namespace: Some("asset".into()),
116 ..Default::default()
117 },
118 )
119 .unwrap();
120 store.put_objects_batch(std::slice::from_ref(&first)).unwrap();
121 let summary = store.rebuild_merkle_index().unwrap();
122 assert_eq!(summary.leaf_count, 1);
123 }
124
125 let pool = sqlx::SqlitePool::connect_with(SqliteConnectOptions::new().filename(&path))
126 .await
127 .unwrap();
128 let store = SqlxStore::from_pool_with_options(
129 pool.clone(),
130 SqliteStoreOptions {
131 namespace: Some("asset".into()),
132 ..Default::default()
133 },
134 )
135 .await
136 .unwrap();
137 assert_eq!(store.read_object(&first.hash).await.unwrap().unwrap().bytes, b"written-by-rusqlite");
138 let schema = sqlx::query("SELECT name FROM sqlite_master WHERE type IN ('table', 'index') AND name LIKE 'asset_%' ORDER BY name")
139 .fetch_all(&pool)
140 .await
141 .unwrap()
142 .into_iter()
143 .map(|row| row.get::<String, _>(0))
144 .collect::<Vec<_>>();
145 assert_eq!(
146 schema,
147 [
148 "asset_file_recipe_cache",
149 "asset_merkle_leaves",
150 "asset_merkle_meta",
151 "asset_merkle_nodes",
152 "asset_objects",
153 ]
154 );
155 let second = record(b"written-by-sqlx");
156 let mut transaction = pool.begin().await.unwrap();
157 store
158 .put_objects_batch_tx(&mut transaction, std::slice::from_ref(&second))
159 .await
160 .unwrap();
161 transaction.commit().await.unwrap();
162 pool.close().await;
163
164 let connection = rusqlite::Connection::open(&path).unwrap();
165 let store = RusqliteStore::from_connection_with_options(
166 &connection,
167 SqliteStoreOptions {
168 namespace: Some("asset".into()),
169 ..Default::default()
170 },
171 )
172 .unwrap();
173 assert_eq!(store.read_object(&second.hash).unwrap().unwrap().bytes, b"written-by-sqlx");
174 assert_eq!(store.merkle_summary().unwrap().leaf_count, 1);
175 }
176}