turtle_build/infrastructure/database/
log.rs1mod hash;
2mod header_input;
3mod output;
4mod utility;
5
6use self::{hash::HashLog, header_input::HeaderInputLog, output::OutputLog};
7use crate::{
8 build_hash::BuildHash,
9 infrastructure::{Database, DatabaseError},
10 ir::BuildId,
11 path_pool::PathPool,
12};
13use alloc::sync::Arc;
14use async_trait::async_trait;
15use std::{fs::create_dir_all, path::Path};
16use tokio::try_join;
17
18const HASH_FILENAME: &str = "hashes";
19const HEADER_INPUT_DIRECTORY: &str = "header_inputs";
20const OUTPUT_FILENAME: &str = "outputs";
21
22pub struct LogDatabase {
24 hash_log: HashLog,
25 header_input_log: HeaderInputLog,
26 output_log: OutputLog,
27}
28
29impl LogDatabase {
30 pub async fn new(directory: &Path, path_pool: &PathPool) -> Result<Self, DatabaseError> {
32 create_dir_all(directory)?;
33
34 let hash_path = directory.join(HASH_FILENAME);
35 let header_input_path = directory.join(HEADER_INPUT_DIRECTORY);
36 let output_path = directory.join(OUTPUT_FILENAME);
37 let (hash_log, header_input_log, output_log) = try_join!(
38 HashLog::new(&hash_path),
39 HeaderInputLog::new(&header_input_path, path_pool),
40 OutputLog::new(&output_path)
41 )?;
42
43 Ok(Self {
44 hash_log,
45 header_input_log,
46 output_log,
47 })
48 }
49}
50
51#[async_trait]
52impl Database for LogDatabase {
53 async fn get_hash(&self, id: BuildId) -> Result<Option<BuildHash>, DatabaseError> {
54 Ok(self.hash_log.get(id))
55 }
56
57 async fn set_hash(&self, id: BuildId, hash: BuildHash) -> Result<(), DatabaseError> {
58 self.hash_log.set(id, hash).await
59 }
60
61 async fn get_header_inputs(&self, id: BuildId) -> Result<Vec<Arc<str>>, DatabaseError> {
62 Ok(self.header_input_log.get(id))
63 }
64
65 async fn set_header_inputs(
66 &self,
67 id: BuildId,
68 inputs: &[Arc<str>],
69 ) -> Result<(), DatabaseError> {
70 self.header_input_log.set(id, inputs).await
71 }
72
73 async fn get_outputs(&self) -> Result<Vec<String>, DatabaseError> {
74 Ok(self.output_log.get())
75 }
76
77 async fn set_output(&self, path: &str, source: Option<&str>) -> Result<(), DatabaseError> {
78 self.output_log.set(path, source).await
79 }
80
81 async fn get_source(&self, output: &str) -> Result<Option<String>, DatabaseError> {
82 Ok(self.output_log.get_source(output))
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use pretty_assertions::assert_eq;
90 use std::fs::{exists, write};
91 use tempfile::{TempDir, tempdir};
92
93 async fn open() -> (LogDatabase, TempDir) {
94 let directory = tempdir().unwrap();
95
96 (reopen(&directory).await, directory)
97 }
98
99 async fn reopen(directory: &TempDir) -> LogDatabase {
100 LogDatabase::new(directory.path(), &Default::default())
101 .await
102 .unwrap()
103 }
104
105 #[tokio::test]
106 async fn new() {
107 let (_database, directory) = open().await;
108
109 assert!(exists(directory.path().join("hashes")).unwrap());
110 assert!(exists(directory.path().join("header_inputs").join("paths")).unwrap());
111 assert!(exists(directory.path().join("header_inputs").join("indices")).unwrap());
112 assert!(exists(directory.path().join("outputs")).unwrap());
113 }
114
115 #[tokio::test]
116 async fn new_in_missing_directory() {
117 let directory = tempdir().unwrap();
118
119 LogDatabase::new(
120 &directory.path().join("foo").join("bar"),
121 &Default::default(),
122 )
123 .await
124 .unwrap();
125 }
126
127 #[tokio::test]
128 async fn hash() {
129 let (database, _directory) = open().await;
130
131 database
132 .set_hash(BuildId::new(0), BuildHash::new(1, 2))
133 .await
134 .unwrap();
135
136 assert_eq!(
137 database.get_hash(BuildId::new(0)).await.unwrap(),
138 Some(BuildHash::new(1, 2))
139 );
140 }
141
142 #[tokio::test]
143 async fn get_no_hash() {
144 let (database, _directory) = open().await;
145
146 database
147 .set_hash(BuildId::new(0), BuildHash::new(1, 2))
148 .await
149 .unwrap();
150
151 assert_eq!(database.get_hash(BuildId::new(1)).await.unwrap(), None);
152 }
153
154 #[tokio::test]
155 async fn header_inputs() {
156 let (database, _directory) = open().await;
157
158 database
159 .set_header_inputs(BuildId::new(0), &["foo".into(), "bar".into()])
160 .await
161 .unwrap();
162
163 assert_eq!(
164 database.get_header_inputs(BuildId::new(0)).await.unwrap(),
165 vec!["foo".into(), "bar".into()]
166 );
167 }
168
169 #[tokio::test]
170 async fn get_no_header_inputs() {
171 let (database, _directory) = open().await;
172
173 database
174 .set_header_inputs(BuildId::new(0), &["foo".into()])
175 .await
176 .unwrap();
177
178 assert_eq!(
179 database.get_header_inputs(BuildId::new(1)).await.unwrap(),
180 Vec::<Arc<str>>::new()
181 );
182 }
183
184 #[tokio::test]
185 async fn intern_header_inputs() {
186 let (database, directory) = open().await;
187
188 database
189 .set_header_inputs(BuildId::new(0), &["foo".into()])
190 .await
191 .unwrap();
192
193 drop(database);
194
195 let path_pool = PathPool::new();
196 let database = LogDatabase::new(directory.path(), &path_pool)
197 .await
198 .unwrap();
199
200 assert!(Arc::ptr_eq(
201 &database.get_header_inputs(BuildId::new(0)).await.unwrap()[0],
202 &path_pool.intern("foo")
203 ));
204 }
205
206 #[tokio::test]
207 async fn set_output() {
208 let (database, _directory) = open().await;
209
210 database.set_output("foo", None).await.unwrap();
211
212 assert_eq!(database.get_outputs().await.unwrap(), ["foo"]);
213 }
214
215 #[tokio::test]
216 async fn get_no_output() {
217 let (database, _directory) = open().await;
218
219 assert_eq!(database.get_outputs().await.unwrap(), Vec::<String>::new());
220 }
221
222 #[tokio::test]
223 async fn get_output_with_source() {
224 let (database, _directory) = open().await;
225
226 database.set_output("foo", Some("bar")).await.unwrap();
227
228 assert_eq!(database.get_outputs().await.unwrap(), ["foo"]);
229 }
230
231 #[tokio::test]
232 async fn get_source() {
233 let (database, _directory) = open().await;
234
235 database.set_output("foo", Some("bar")).await.unwrap();
236
237 assert_eq!(
238 database.get_source("foo").await.unwrap(),
239 Some("bar".into())
240 );
241 }
242
243 #[tokio::test]
244 async fn get_no_source() {
245 let (database, _directory) = open().await;
246
247 database.set_output("foo", Some("bar")).await.unwrap();
248
249 assert_eq!(database.get_source("baz").await.unwrap(), None);
250 }
251
252 #[tokio::test]
253 async fn reopen_database() {
254 let (database, directory) = open().await;
255
256 database
257 .set_hash(BuildId::new(0), BuildHash::new(1, 2))
258 .await
259 .unwrap();
260 database
261 .set_header_inputs(BuildId::new(0), &["foo".into()])
262 .await
263 .unwrap();
264 database.set_output("foo", Some("bar")).await.unwrap();
265
266 drop(database);
267
268 let database = reopen(&directory).await;
269
270 assert_eq!(
271 database.get_hash(BuildId::new(0)).await.unwrap(),
272 Some(BuildHash::new(1, 2))
273 );
274 assert_eq!(
275 database.get_header_inputs(BuildId::new(0)).await.unwrap(),
276 vec!["foo".into()]
277 );
278 assert_eq!(database.get_outputs().await.unwrap(), ["foo"]);
279 assert_eq!(
280 database.get_source("foo").await.unwrap(),
281 Some("bar".into())
282 );
283 }
284
285 #[tokio::test]
286 async fn fail_to_open_invalid_log() {
287 let directory = tempdir().unwrap();
288
289 write(directory.path().join("outputs"), [0xff, b'\n']).unwrap();
290
291 assert!(
292 LogDatabase::new(directory.path(), &Default::default())
293 .await
294 .is_err()
295 );
296 }
297}